Forms With Checkboxes For Each Entity in Symfony

From Littledamien Wiki
Jump to navigation Jump to search

Goal[edit]

To display a list of records, select the records with checkboxes, then apply a batch update to the records when the form is submitted.[1]

Form class[edit]

// src/AppBundle/Form/Type/PendingTutorialOptionType.php

// ...

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PendingTutorialOptionType extends AbstractType
{
	public function buildForm(FormBuilderInterface $builder, array $options)
	{
		$builder->add('tutorialId', 'entity', array(
			'required' => false,
			'class' => 'AppBundle:TutorialTemp',
			'property' => 'id',
			'property_path' => '[id]',
			'multiple' => true,
			'expanded' => true
		));
		$builder->add('update', 'submit', array('label' => 'save selected'));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null
        ));
    }

    public function getName()
    {
        return 'pending_tutorials';
    }
}
  • In the buildForm() routine,
    • Use entity as the field type.
    • Assign the entity type with the class option.
    • The property and property_path options assign the id field of the entity to the checkbox elements.
    • multiple and expanded options set to true cause the form to render with a series of checkboxes as opposed to text fields (or dropdowns if the field type was choice).
  • Important to set data_class to null in setDefaultOptions().
  • Value returned by getName() is used to identify the form in the Twig template and to collect the form data after it is submitted.

Controller[edit]

// src\AppBundle\Controller\DefaultController.php

//...
	public function pendingAction(Request $request)
	{
		// get all the available options
		$pending_options = $this->get('app.pending_tutorial_options')
			->getTutorialOptionsList();

		// get a form object, passing it the available options
		$form = $this->createForm(new PendingTutorialOptionType(), $pending_options);

		// process form submission
		$form->handleRequest($request);

		// check if form data was submitted
		if ($form->isValid()) {
			
			// collect selected entities from form data
			$data = $form['tutorialId']->getData();

			$selected_ids = array();
			foreach($data as $tutorial) {
				// $data is a collection of PendingTutorial objects with all their data
				array_push($selected_ids, $tutorial->getId();
			}

			// do something with the matching records...
// ...

$form->getData() returns a collection of all the available options in the form. $form['tutorialId']->getData() returns the selected options as entity objects.

N.B. I was having difficulty with isValid() because I was manually inserting the submit buttons in the form, with a name value of form[update]. The form class returns pending_options as its name; the correct value for the submit button's name should have been pending_options[update].

Template[edit]

{{ form_start(form) }}
{{ form_errors(form) }}
<button type="submit" class="btn btn-default" name="form[update]">save selected</button>
{% for tutorial in tutorials %}
{{ form_widget(form.tutorialId[loop.index0] ) }}
{% endfor %}
<button type="submit" class="btn btn-default" name="form[update]">save selected</button>
{% do form.update.setRendered %}{# compensates for manually inserting this multiple times in the form #}
{{ form_end(form) }}
  1. The checkboxes are rendered with form_widget().
    1. tutorialId corresponds to the index of the checkbox collection added to the form builder object.
    2. The individual checkboxes in the collection are accessed through a numeric index. Twig provides the loop.index0 helper.
  2. The html for the submit buttons are is manually inserted into the template. The Symfony helper functions will only render a form element once to avoid duplicate id values.
  3. form_end() will render any fields that have not explicitly rendered in the form already. form.update.setRendered prevents this.

Processing the form data[edit]

After the call to $form['tutorialId']->getData(), the submitted form data will be accessible through a Doctrine ArrayCollection of entity objects:

object(Doctrine\Common\Collections\ArrayCollection)[1583]
      private '_elements' => 
        array (size=6)
          0 => 
            object(AppBundle\Entity\PendingTutorial)[652]
            ...
          1 => 
            object(AppBundle\Entity\PendingTutorial)[660]
            ...
          2 => 
            object(AppBundle\Entity\PendingTutorial)[664]
            ...

Examples[edit]

Notes[edit]

  1. Build a Form Having a Checkbox For Each Entity in a Doctrine Collection, Stackoverflow
    Take note of the following answer that points out how to correctly access the collection in the form data with the current version of Symfony.