Skip to content

Commit 2950097

Browse files
committed
Refactored docs to use adders/removers
1 parent b022d23 commit 2950097

File tree

1 file changed

+87
-43
lines changed

1 file changed

+87
-43
lines changed

cookbook/form/form_collections.rst

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ objects. Start by creating a simple ``Task`` class::
5353
{
5454
return $this->tags;
5555
}
56-
57-
public function setTags(ArrayCollection $tags)
58-
{
59-
$this->tags = $tags;
60-
}
6156
}
6257

6358
.. note::
@@ -240,9 +235,9 @@ zero tags when first created).
240235

241236
<!-- ... -->
242237

243-
When the user submits the form, the submitted data for the ``Tags`` fields
244-
are used to construct an ArrayCollection of ``Tag`` objects, which is then
245-
set on the ``tag`` field of the ``Task`` instance.
238+
When the user submits the form, the submitted data for the ``Tags`` fields are
239+
used to construct an ``ArrayCollection`` of ``Tag`` objects, which is then set
240+
on the ``tag`` field of the ``Task`` instance.
246241

247242
The ``Tags`` collection is accessible naturally via ``$task->getTags()``
248243
and can be persisted to the database or used however you need.
@@ -286,7 +281,6 @@ add the ``allow_add`` option to your collection field::
286281
// src/Acme/TaskBundle/Form/Type/TaskType.php
287282

288283
// ...
289-
290284
use Symfony\Component\Form\FormBuilderInterface;
291285

292286
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -296,18 +290,59 @@ add the ``allow_add`` option to your collection field::
296290
$builder->add('tags', 'collection', array(
297291
'type' => new TagType(),
298292
'allow_add' => true,
299-
'by_reference' => false,
300293
));
301294
}
302295

303-
Note that ``'by_reference' => false`` was also added. Normally, the form
304-
framework would modify the tags on a `Task` object *without* actually
305-
ever calling `setTags`. By setting :ref:`by_reference<reference-form-types-by-reference>`
306-
to `false`, `setTags` will be called. This will be important later as you'll
307-
see.
296+
Now that the form type knows tags can be added, the ``Tasks`` class needs to
297+
add methods to make editing the tags possible. This is done by creating an
298+
"adder". In this case, the adder will be ``addTag``::
299+
300+
// src/Acme/TaskBundle/Entity/Task.php
301+
namespace Acme\TaskBundle\Entity;
302+
303+
// ...
304+
class Task
305+
{
306+
// ...
307+
308+
public function addTag($tag)
309+
{
310+
$this->tags->add($tag);
311+
}
312+
313+
public function removeTag($tag)
314+
{
315+
// ...
316+
}
317+
}
318+
319+
But, the form type will still use ``getTags`` now. You need to set the
320+
``by_reference`` option to ``false``, otherwise the form type will get the
321+
object by using the getter and change that object.
322+
323+
.. caution::
324+
325+
If no ``addTag`` **and** ``removeTag`` method is found, the form will
326+
still ``setTag`` when setting ``by_reference`` to ``false``. You'll learn
327+
more about the ``removeTag`` method later in this article.
328+
329+
.. code-block:: php
330+
331+
// src/Acme/TaskBundle/Form/Type/TaskType.php
332+
333+
// ...
334+
public function buildForm(FormBuilderInterface $builder, array $options)
335+
{
336+
// ...
337+
338+
$builder->add('tags', 'collection', array(
339+
// ...
340+
'by_reference' => false,
341+
));
342+
}
308343
309344
In addition to telling the field to accept any number of submitted objects, the
310-
``allow_add`` also makes a "prototype" variable available to you. This "prototype"
345+
``allow_add`` also makes a *"prototype"* variable available to you. This "prototype"
311346
is a little "template" that contains all the HTML to be able to render any
312347
new "tag" forms. To render it, make the following change to your template:
313348

@@ -321,7 +356,9 @@ new "tag" forms. To render it, make the following change to your template:
321356

322357
.. code-block:: html+php
323358

324-
<ul class="tags" data-prototype="<?php echo $view->escape($view['form']->row($form['tags']->vars['prototype'])) ?>">
359+
<ul class="tags" data-prototype="<?php
360+
echo $view->escape($view['form']->row($form['tags']->vars['prototype']))
361+
?>">
325362
...
326363
</ul>
327364

@@ -430,13 +467,14 @@ into new ``Tag`` objects and added to the ``tags`` property of the ``Task`` obje
430467

431468
.. sidebar:: Doctrine: Cascading Relations and saving the "Inverse" side
432469

433-
To get the new tags to save in Doctrine, you need to consider a couple
434-
more things. First, unless you iterate over all of the new ``Tag`` objects
435-
and call ``$em->persist($tag)`` on each, you'll receive an error from
470+
To save the new tags with Doctrine, you need to consider a couple more
471+
things. First, unless you iterate over all of the new ``Tag`` objects and
472+
call ``$em->persist($tag)`` on each, you'll receive an error from
436473
Doctrine:
437474

438-
A new entity was found through the relationship `Acme\TaskBundle\Entity\Task#tags`
439-
that was not configured to cascade persist operations for entity...
475+
A new entity was found through the relationship
476+
``Acme\TaskBundle\Entity\Task#tags`` that was not configured to
477+
cascade persist operations for entity...
440478

441479
To fix this, you may choose to "cascade" the persist operation automatically
442480
from the ``Task`` object to any related tags. To do this, add the ``cascade``
@@ -492,29 +530,25 @@ into new ``Tag`` objects and added to the ``tags`` property of the ``Task`` obje
492530
of the relationship is modified.
493531

494532
The trick is to make sure that the single "Task" is set on each "Tag".
495-
One easy way to do this is to add some extra logic to ``setTags()``,
496-
which is called by the form framework since :ref:`by_reference<reference-form-types-by-reference>`
497-
is set to ``false``::
533+
One easy way to do this is to add some extra logic to ``addTag()``,
534+
which is called by the form type since ``by_reference`` is set to
535+
``false``::
498536

499537
// src/Acme/TaskBundle/Entity/Task.php
500538

501539
// ...
502-
503-
public function setTags(ArrayCollection $tags)
540+
public function addTag(ArrayCollection $tag)
504541
{
505-
foreach ($tags as $tag) {
506-
$tag->addTask($this);
507-
}
542+
$tag->addTask($this);
508543

509-
$this->tags = $tags;
544+
$this->tags->add($tag);
510545
}
511546

512547
Inside ``Tag``, just make sure you have an ``addTask`` method::
513548

514549
// src/Acme/TaskBundle/Entity/Tag.php
515550

516551
// ...
517-
518552
public function addTask(Task $task)
519553
{
520554
if (!$this->tasks->contains($task)) {
@@ -523,7 +557,7 @@ into new ``Tag`` objects and added to the ``tags`` property of the ``Task`` obje
523557
}
524558

525559
If you have a ``OneToMany`` relationship, then the workaround is similar,
526-
except that you can simply call ``setTask`` from inside ``setTags``.
560+
except that you can simply call ``setTask`` from inside ``addTag``.
527561

528562
.. _cookbook-form-collections-remove:
529563

@@ -538,20 +572,31 @@ Start by adding the ``allow_delete`` option in the form Type::
538572
// src/Acme/TaskBundle/Form/Type/TaskType.php
539573

540574
// ...
541-
use Symfony\Component\Form\FormBuilderInterface;
542-
543575
public function buildForm(FormBuilderInterface $builder, array $options)
544576
{
545-
$builder->add('description');
577+
// ...
546578

547579
$builder->add('tags', 'collection', array(
548-
'type' => new TagType(),
549-
'allow_add' => true,
580+
// ...
550581
'allow_delete' => true,
551-
'by_reference' => false,
552582
));
553583
}
554584

585+
Now, you need to put some code into the ``removeTag`` method of ``Task``::
586+
587+
// src/Acme/TaskBundle/Entity/Task.php
588+
589+
// ...
590+
class Task
591+
{
592+
// ...
593+
594+
public function removeTag($tag)
595+
{
596+
$this->tags->removeElement($tag);
597+
}
598+
}
599+
555600
Templates Modifications
556601
~~~~~~~~~~~~~~~~~~~~~~~
557602

@@ -604,11 +649,11 @@ the relationship between the removed ``Tag`` and ``Task`` object.
604649
.. sidebar:: Doctrine: Ensuring the database persistence
605650

606651
When removing objects in this way, you may need to do a little bit more
607-
work to ensure that the relationship between the Task and the removed Tag
608-
is properly removed.
652+
work to ensure that the relationship between the ``Task`` and the removed
653+
``Tag`` is properly removed.
609654

610655
In Doctrine, you have two side of the relationship: the owning side and the
611-
inverse side. Normally in this case you'll have a ManyToMany relation
656+
inverse side. Normally in this case you'll have a ``ManyToMany`` relation
612657
and the deleted tags will disappear and persist correctly (adding new
613658
tags also works effortlessly).
614659

@@ -623,7 +668,6 @@ the relationship between the removed ``Tag`` and ``Task`` object.
623668
// src/Acme/TaskBundle/Controller/TaskController.php
624669

625670
// ...
626-
627671
public function editAction($id, Request $request)
628672
{
629673
$em = $this->getDoctrine()->getManager();

0 commit comments

Comments
 (0)