diff --git a/form/form_collections.rst b/form/form_collections.rst index fa650cbfa82..3441db6e799 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -12,27 +12,27 @@ that Task, right inside the same form. .. note:: In this article, it's loosely assumed that you're using Doctrine as your - database store. But if you're not using Doctrine (e.g. Propel or just - a database connection), it's all very similar. There are only a few parts - of this tutorial that really care about "persistence". + database store. But if you're not using Doctrine, it's all very similar. + There are only a few parts of this tutorial that really care about + persistence. If you *are* using Doctrine, you'll need to add the Doctrine metadata, - including the ``ManyToMany`` association mapping definition on the Task's - ``tags`` property. + including the ``ManyToMany`` association mapping definition on the + ``tags`` property of ``Task``. First, suppose that each ``Task`` belongs to multiple ``Tag`` objects. Start by creating a simple ``Task`` class:: - // src/AppBundle/Entity/Task.php - namespace AppBundle\Entity; + // src/App/Entity/Task.php + namespace App\Entity; use Doctrine\Common\Collections\ArrayCollection; class Task { - protected $description; + private $description; - protected $tags; + private $tags; public function __construct() { @@ -64,8 +64,8 @@ by creating a simple ``Task`` class:: Now, create a ``Tag`` class. As you saw above, a ``Task`` can have many ``Tag`` objects:: - // src/AppBundle/Entity/Tag.php - namespace AppBundle\Entity; + // src/App/Entity/Tag.php + namespace App\Entity; class Tag { @@ -84,10 +84,10 @@ objects:: Then, create a form class so that a ``Tag`` object can be modified by the user:: - // src/AppBundle/Form/Type/TagType.php - namespace AppBundle\Form\Type; + // src/App/Form/TagType.php + namespace App\Form; - use AppBundle\Entity\Tag; + use App\Entity\Tag; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -114,14 +114,15 @@ form itself, create a form for the ``Task`` class. Notice that you embed a collection of ``TagType`` forms using the :doc:`CollectionType ` field:: - // src/AppBundle/Form/Type/TaskType.php - namespace AppBundle\Form\Type; + // src/App/Form/TaskType.php + namespace App\Form; - use AppBundle\Entity\Task; + use App\Entity\Task; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\CollectionType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; class TaskType extends AbstractType { @@ -133,6 +134,7 @@ Notice that you embed a collection of ``TagType`` forms using the 'entry_type' => TagType::class, 'entry_options' => array('label' => false), )); + $builder->add('submit', SubmitType::class); } public function configureOptions(OptionsResolver $resolver) @@ -145,18 +147,23 @@ Notice that you embed a collection of ``TagType`` forms using the In your controller, you'll create a new form from the ``TaskType``:: - // src/AppBundle/Controller/TaskController.php - namespace AppBundle\Controller; + // src/App/Controller/TaskController.php + namespace App\Controller; - use AppBundle\Entity\Task; - use AppBundle\Entity\Tag; - use AppBundle\Form\Type\TaskType; use Symfony\Component\HttpFoundation\Request; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class TaskController extends Controller + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Doctrine\ORM\EntityManagerInterface; + use App\Entity\Task; + use App\Entity\Tag; + use App\Form\TaskType; + + class TaskController extends AbstractController { - public function newAction(Request $request) + /** + * @Route("/task/new") + */ + public function new(Request $request, EntityManagerInterface $em) { $task = new Task(); @@ -175,7 +182,9 @@ In your controller, you'll create a new form from the ``TaskType``:: $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - // ... maybe do some form processing, like saving the Task and Tag objects + // ... maybe do some form processing, like saving the Task and Tag objects: + $em->persist($task); + $em->flush(); } return $this->render('task/new.html.twig', array( @@ -186,16 +195,17 @@ In your controller, you'll create a new form from the ``TaskType``:: The corresponding template is now able to render both the ``description`` field for the task form as well as all the ``TagType`` forms for any tags -that are already related to this ``Task``. In the above controller, I added -some dummy code so that you can see this in action (since a ``Task`` has -zero tags when first created). +that are already related to this ``Task``. The above controller has some +dummy code so that you can see this in action (since a newly created ``Task`` +has zero tags). .. code-block:: html+twig - {# app/Resources/views/task/new.html.twig #} + {# templates/task/new.html.twig #} {# ... #} - + +

New Task

{{ form_start(form) }} {# render the task's only field: description #} {{ form_row(form.description) }} @@ -211,7 +221,7 @@ zero tags when first created). {# ... #} -When the user submits the form, the submitted data for the ``tags`` field are +When the user submits the form, the submitted data for the ``tags`` field is used to construct an ``ArrayCollection`` of ``Tag`` objects, which is then set on the ``tag`` field of the ``Task`` instance. @@ -220,12 +230,12 @@ and can be persisted to the database or used however you need. So far, this works great, but this doesn't allow you to dynamically add new tags or delete existing tags. So, while editing existing tags will work -great, your user can't actually add any new tags yet. +great, your users can't actually add any new tags yet. .. caution:: In this article, you embed only one collection, but you are not limited - to this. You can also embed nested collection as many levels down as you + to this. You can also embed nested collections as many levels down as you like. But if you use Xdebug in your development setup, you may receive a ``Maximum function nesting level of '100' reached, aborting!`` error. This is due to the ``xdebug.max_nesting_level`` PHP setting, which defaults @@ -245,47 +255,47 @@ Allowing "new" Tags with the "Prototype" Allowing the user to dynamically add new tags means that you'll need to use some JavaScript. Previously you added two tags to your form in the controller. -Now let the user add as many tag forms as they need directly in the browser. -This will be done through a bit of JavaScript. +Now let the users add as many tag forms as they need directly in the browser. The first thing you need to do is to let the form collection know that it will receive an unknown number of tags. So far you've added two tags and the form -type expects to receive exactly two, otherwise an error will be thrown: -``This form should not contain extra fields``. To make this flexible, +type expects to receive exactly two. If it gets more, the following error will be +thrown: ``This form should not contain extra fields``. To make the number flexible, add the ``allow_add`` option to your collection field:: - // src/AppBundle/Form/Type/TaskType.php + // src/App/Form/TaskType.php // ... - use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('description'); - + // ... $builder->add('tags', CollectionType::class, array( - 'entry_type' => TagType::class, - 'entry_options' => array('label' => false), + // ... 'allow_add' => true, )); } -In addition to telling the field to accept any number of submitted objects, the -``allow_add`` also makes a *"prototype"* variable available to you. This "prototype" -is a little "template" that contains all the HTML to be able to render any -new "tag" forms. To render it, make the following change to your template: +In addition to telling the field to accept any number of submitted objects, +``allow_add`` also makes a ``prototype`` variable available to you. This +"prototype" is a little "template" that contains all the HTML needed to dynamically +render any new "tag" forms with JavaScript. To render the prototype, add the following +``data-prototype`` attribute to the existing ``