Symfony Forms Cookbook: Difference between revisions

From Littledamien Wiki
Jump to navigation Jump to search
No edit summary
 
(7 intermediate revisions by 2 users not shown)
Line 30: Line 30:
             'form' => $form->createView(),
             'form' => $form->createView(),
         ));
         ));
    }
}
</syntaxhighlight>
=== Form classes ===
The convention is to put form classes in `src/AppBundle/Form/Type/`.
<syntaxhighlight lang="php">
// src/AppBundle/Form/Type/TaskType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'))
->add('save', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task',
));
}
public function getName()
{
return 'task';
}
}
</syntaxhighlight>
`getName()` should return a unique identifier to be used by the controller.
To use the form class in the controller:
<syntaxhighlight lang="php" highlight="4,9">
// src/AppBundle/Controller/DefaultController.php
// add this new use statement at the top of the class
use AppBundle\Form\Type\TaskType;
public function newAction()
{
    $task = ...;
    $form = $this->createForm(new TaskType(), $task);
    // ...
}
</syntaxhighlight>
=== Services for forms ===
Once the form class is built, it can be set up as a service.
<syntaxhighlight lang="yaml">
# src/AppBundle/Resources/config/services.yml
services:
    acme_demo.form.type.task:
        class: AppBundle\Form\Type\TaskType
        tags:
            - { name: form.type, alias: task }
</syntaxhighlight>
To use the form service in a controller (note that the `use` statement isn't necessary now):
<syntaxhighlight lang="php" highlight="8">
// src/AppBundle/Controller/DefaultController.php
// ...
public function newAction()
{
    $task = ...;
    $form = $this->createForm('task', $task);
    // ...
}
</syntaxhighlight>
Or to use the form service in another form class:
<syntaxhighlight lang="php" highlight="11">
// src/AppBundle/Form/Type/ListType.php
// ...
class ListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ...
        $builder->add('someTask', 'task');
     }
     }
}
}
Line 62: Line 164:
** `form.entity.property.label`
** `form.entity.property.label`
** <span style="color:orange;">''^^ Actually I'm not 100% sure that's how it works. Try it out to confirm.''</span>
** <span style="color:orange;">''^^ Actually I'm not 100% sure that's how it works. Try it out to confirm.''</span>
=== See also ===
* [[Customizing Symfony Form Templates]]
* [[Inserting a Hidden Date Field in a Symfony Form]]


== Handling form submissions ==
== Handling form submissions ==
Line 210: Line 317:
</syntaxhighlight>
</syntaxhighlight>


=== Multiple checkboxes without an entity class ===
=== See also ===
 
'''Goal:''' To display a list of records, select the records with checkboxes, then apply a batch update to the records when the form is submitted.
 
==== Controller ====
 
<syntaxhighlight lang="php" highlight="18,23-34,37,39-42,50">
// ...
use Symfony\Component\HttpFoundation\Request;
 
//...
/**
* @Route("/pending", name="_pending")
*/
public function pendingAction(Request $request)
{
// retrieve pending tutorials from database
$groups = $this->getDoctrine()
->getRepository('AppBundle:TutorialGroupTemp')
->findAll();
$tutorial_data = array();
foreach($groups as $group) {
$group->getTutorials();
foreach ($group->tutorials as $tutorial) {
$tutorial_data[$tutorial->getId()] = $tutorial->getName();
}
}
 
// define form inputs
$fb = $this->createFormBuilder();
foreach($tutorial_data as $key => $label) {
$fb->add('tutorialId', 'choice', array(
'label' => 'Tutorials',
'multiple' => true,
'expanded' => true,
'choices' => $tutorial_data,
'required' => false
));
}
$fb->add('update', 'submit', array('label' => 'save selected'));
$form = $fb->getForm();
// process form submission
$form->handleRequest($request);
if ($form->isValid()) {
// collect form data
$data = $form->getData();
/** @todo process form data */
}
 
// display listings view
return $this->render("tutorials/pending.html.twig", array(
'groups' => $groups,
'form' => $form->createView()
));
}
// ...
</syntaxhighlight>
 
# Store all the record values in an array to be assigned to the checkbox inputs elements.
# Use a form builder object to define the form.
## Call the `add()` method for each checkbox to be added the form.
### Checkboxes representing an array of choices are created using `choice` as the form element
### The options 'multiple=true' and 'expanded=true' are what make it render as checkboxes as opposed to dropdowns.
### The values of the checkboxes are passed to the form builder with the `choices` option.
## In this case submit buttons are repeated throughout the form for the convenience of not having to scroll through all the listings to find them.
### The `form_widget()` twig routine will only render an input once because generally they are all assigned `id` attributes.
### The submit button must still be added to the form builder in order to validate the form data.
# The form data is collected and processed with the `handleRequest()` and `getData()` calls. (`getData()` wouldn't be used if the form data was being assigned to an entity class.)
# The form is validated with the `isValid()` call. If form data is not being submitted, this block is quietly skipped over.
# It's necessary to pass the form view to the template when rendering the view.
 
<span style="color:orange;">'''N.B.''' There are probably other things that I could do (form class? services?) that would be better practices. As documented here it works. Update with any new practices.</span>
 
==== Template ====
 
<syntaxhighlight lang="twig">
{{ 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) }}
</syntaxhighlight>
 
# The checkboxes are rendered with `form_widget()`.
## `tutorialId` corresponds to the index of the checkbox collection added to the form builder object.
## The individual checkboxes in the collection are accessed through a numeric index. Twig provides the `loop.index0` helper.
# 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.
# `form_end()` will render any fields that have not explicitly rendered in the form already. `form.update.setRendered` prevents this.
 
==== Processing the form data ====
 
After the call to `$form->getData()`, the submitted form data will be accessible through an array:
 
<syntaxhighlight lang="text">
array (size=1)
  'tutorialID' =>
    array (size=3)
      0 => int 3
      1 => int 6
      2 => int 9
</syntaxhighlight>


==== Examples ====
* [[Forms With Checkboxes For Each Entity in Symfony]]


* [http://tutorials.dbarchowsky.com/app_dev.php/pending Pending Tutorials], Tutorial Database
== See also ==


* [[Listings Filters Form With Symfony]]
== Notes ==
== Notes ==
<references />
<references />

Latest revision as of 22:51, 18 February 2015

Basic form building[edit]

Build a form object and render it in a template from within a controller:[1]

// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    public function newAction(Request $request)
    {
        // create a task and give it some dummy data for this example
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTime('tomorrow'));

        $form = $this->createFormBuilder($task)
            ->add('task', 'text')
            ->add('dueDate', 'date')
            ->add('save', 'submit', array('label' => 'Create Task'))
            ->getForm();

        return $this->render('Default/new.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

Form classes[edit]

The convention is to put form classes in src/AppBundle/Form/Type/.

// src/AppBundle/Form/Type/TaskType.php
namespace AppBundle\Form\Type;

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

class TaskType extends AbstractType
{
	public function buildForm(FormBuilderInterface $builder, array $options)
	{
		$builder
			->add('task')
			->add('dueDate', null, array('widget' => 'single_text'))
			->add('save', 'submit');
	}

	public function setDefaultOptions(OptionsResolverInterface $resolver)
	{
		$resolver->setDefaults(array(
			'data_class' => 'AppBundle\Entity\Task',
		));
	}

	public function getName()
	{
		return 'task';
	}
}

getName() should return a unique identifier to be used by the controller.

To use the form class in the controller:

// src/AppBundle/Controller/DefaultController.php

// add this new use statement at the top of the class
use AppBundle\Form\Type\TaskType;

public function newAction()
{
    $task = ...;
    $form = $this->createForm(new TaskType(), $task);

    // ...
}

Services for forms[edit]

Once the form class is built, it can be set up as a service.

# src/AppBundle/Resources/config/services.yml
services:
    acme_demo.form.type.task:
        class: AppBundle\Form\Type\TaskType
        tags:
            - { name: form.type, alias: task }

To use the form service in a controller (note that the use statement isn't necessary now):

// src/AppBundle/Controller/DefaultController.php

// ...

public function newAction()
{
    $task = ...;
    $form = $this->createForm('task', $task);

    // ...
}

Or to use the form service in another form class:

// src/AppBundle/Form/Type/ListType.php

// ...

class ListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ...

        $builder->add('someTask', 'task');
    }
}

Render a form[edit]

After passing a form "view" object to a Twig template, the form can be rendered with form helper functions:[2]

{# app/Resources/views/Default/new.html.twig #}
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

form_end(form) renders the form's end tag along with hidden fields, including CSRF protection. It also renders any fields that have not already been rendered.

Rendering form fields[edit]

  • form_row(form.property) renders a form input along with a label and error messages specific to the form field. form is the name of the form variable, and property is the name of the property being rendered, e.g. form.id, form.name, etc.
  • The components wrapped up in form_row() can be rendered separately:
    • form_label(form.property) renders just the label for the field.
      • Override the label text with form_label(form.property, 'Custom Label Text')
    • form_errors(form.property) renders errors for the field.
    • form_widget(form.property) renders the form input.
      • Set properties for the input with form_widget(form.property, {attr: {'class': 'my-custom-class'}})
  • Individual properties of the fields can be accessed with
    • form.entity.property.id
    • form.entity.property.name
    • form.entity.property.label
    • ^^ Actually I'm not 100% sure that's how it works. Try it out to confirm.

See also[edit]

Handling form submissions[edit]

In the controller, the form object translates user data submitted with the form.[3]

public function newAction(Request $request)
{
    // just setup a fresh $task object (remove the dummy data)
    $task = new Task();

    $form = $this->createFormBuilder($task)
        ->add('task', 'text')
        ->add('dueDate', 'date')
        ->add('save', 'submit', array('label' => 'Create Task'))
        ->getForm();

    $form->handleRequest($request);

    if ($form->isValid()) {

        // perform some action, such as saving the task to the database

        return $this->redirect($this->generateUrl('task_success'));
    }

    // ...
}
  1. When the page is first loaded, the form is created and rendered.
    1. handleRequest() recognizes that the form was not submitted and does nothing.
    2. isValid() returns false if the form was not submitted.
  2. When the form is submitted, handleRequest() translates the data
    1. handleRequest() updates the corresponding properties of the entity object ($task in this case) with the form data.
    2. isValid() returns false if the form data isn't valid.
      1. Execution skips that block where the data would be processed, and instead goes to the original view, which is rendered with the submitted form data and error messages.

Multiple submit buttons[edit]

Add the buttons to the form builder in the controller:[4]

$form = $this->createFormBuilder($task)
    ->add('task', 'text')
    ->add('dueDate', 'date')
    ->add('save', 'submit', array('label' => 'Create Task'))
    ->add('saveAndAdd', 'submit', array('label' => 'Save and Add'))
    ->getForm();

Test which button was clicked in the controller using the button's isClicked() method:

if ($form->isValid()) {
    // ... perform some action, such as saving the task to the database

    $nextAction = $form->get('saveAndAdd')->isClicked()
        ? 'task_new'
        : 'task_success';

    return $this->redirect($this->generateUrl($nextAction));
}

Form validation[edit]

With Symfony, it isn't the form that is validated, rather it is the entity object that is tested to confirm that it contains valid data. This is does by defining a set of rules, or constraints, for the entity class.[5]

# AppBundle/Resources/config/validation.yml
AppBundle\Entity\Task:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type: \DateTime

The above configuration specifies that the task field cannot be empty and the dueDate field cannot be empty and must be a valid DateTime object.

Validation groups[edit]

Different groups can be defined to validate the underlying object, either in the controller:

$form = $this->createFormBuilder($users, array(
    'validation_groups' => array('registration'),
))->add(...);

Or in a form class:

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => array('registration'),
    ));
}

Disabling validation[edit]

Set the validation_groups option to false:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => false,
    ));
}

Forms without classes[edit]

Example of how to set this up in a controller:[6]

// make sure you've imported the Request namespace above the class
use Symfony\Component\HttpFoundation\Request;
// ...

public function contactAction(Request $request)
{
    $defaultData = array('message' => 'Type your message here');
    $form = $this->createFormBuilder($defaultData)
        ->add('name', 'text')
        ->add('email', 'email')
        ->add('message', 'textarea')
        ->add('send', 'submit')
        ->getForm();

    $form->handleRequest($request);

    if ($form->isValid()) {
        // data is an array with "name", "email", and "message" keys
        $data = $form->getData();
    }

    // ... render the form
}

See also[edit]

See also[edit]

Notes[edit]

  1. Building The Form, Symfony forms documentation
  2. Rendering The Form, Symfony forms documentation
  3. Handling Form Submissions, Symfony forms documentation
  4. Submitting Forms With Multiple Buttons, Symfony forms documentation
  5. Form Validation, Symfony forms documentation
  6. Using a Form Without a Class, Symfony forms documentation