diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 4b634802426b0..140f05242e1d3 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -24,6 +24,7 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Util\FormUtil; +use Symfony\Component\Form\Util\InheritDataAwareFilter; use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\Form\Util\OrderedHashMap; use Symfony\Component\PropertyAccess\PropertyPath; @@ -281,13 +282,20 @@ public function setData(mixed $modelData): static } $this->lockSetData = true; - $dispatcher = $this->config->getEventDispatcher(); + + // Collecting $this + all children with "inherit_data" option TRUE + $thisModelDataAwareFormsIterator = new \AppendIterator(); + $thisModelDataAwareFormsIterator->append(new \ArrayIterator([$this])); + $thisModelDataAwareFormsIterator->append(new InheritDataAwareFilter(new InheritDataAwareIterator($this->children))); // Hook to change content of the model data before transformation and mapping children - if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { - $event = new PreSetDataEvent($this, $modelData); - $dispatcher->dispatch($event, FormEvents::PRE_SET_DATA); - $modelData = $event->getData(); + foreach ($thisModelDataAwareFormsIterator as $form) { + $dispatcher = $form->getConfig()->getEventDispatcher(); + if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { + $event = new PreSetDataEvent($form, $modelData); + $dispatcher->dispatch($event, FormEvents::PRE_SET_DATA); + $modelData = $event->getData(); + } } // Treat data as strings unless a transformer exists @@ -323,9 +331,12 @@ public function setData(mixed $modelData): static $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children))); } - if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { - $event = new PostSetDataEvent($this, $modelData); - $dispatcher->dispatch($event, FormEvents::POST_SET_DATA); + foreach ($thisModelDataAwareFormsIterator as $form) { + $dispatcher = $form->getConfig()->getEventDispatcher(); + if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { + $event = new PostSetDataEvent($form, $modelData); + $dispatcher->dispatch($event, FormEvents::POST_SET_DATA); + } } return $this; @@ -465,7 +476,13 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, array given.'); } - $dispatcher = $this->config->getEventDispatcher(); + $thisModelDataAwareFormsIterator = new \AppendIterator(); + + // Collecting $this + all children with "inherit_data" option TRUE only if $this has "inherit_data" option FALSE to avoid to dispatch events two times. + if (!$this->getConfig()->getInheritData()) { + $thisModelDataAwareFormsIterator->append(new \ArrayIterator(['' => $this])); + $thisModelDataAwareFormsIterator->append(new InheritDataAwareFilter(new InheritDataAwareIterator($this->children))); + } $modelData = null; $normData = null; @@ -477,10 +494,19 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static } // Hook to change content of the data submitted by the browser - if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { - $event = new PreSubmitEvent($this, $submittedData); - $dispatcher->dispatch($event, FormEvents::PRE_SUBMIT); - $submittedData = $event->getData(); + foreach ($thisModelDataAwareFormsIterator as $name => $form) { + $dispatcher = $form->getConfig()->getEventDispatcher(); + if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { + $eventSubmittedData = null; + if (empty($name)) { + $eventSubmittedData = $submittedData; + } elseif (\array_key_exists($name, $submittedData)) { + $eventSubmittedData = $submittedData[$name]; + } + $event = new PreSubmitEvent($form, $eventSubmittedData); + $dispatcher->dispatch($event, FormEvents::PRE_SUBMIT); + $submittedData = $event->getData(); + } } // Check whether the form is compound. @@ -563,10 +589,13 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static // Hook to change content of the data in the normalized // representation - if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { - $event = new SubmitEvent($this, $normData); - $dispatcher->dispatch($event, FormEvents::SUBMIT); - $normData = $event->getData(); + foreach ($thisModelDataAwareFormsIterator as $form) { + $dispatcher = $form->getConfig()->getEventDispatcher(); + if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { + $event = new SubmitEvent($form, $normData); + $dispatcher->dispatch($event, FormEvents::SUBMIT); + $normData = $event->getData(); + } } // Synchronize representations - must not change the content! @@ -590,9 +619,12 @@ public function submit(mixed $submittedData, bool $clearMissing = true): static $this->normData = $normData; $this->viewData = $viewData; - if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { - $event = new PostSubmitEvent($this, $viewData); - $dispatcher->dispatch($event, FormEvents::POST_SUBMIT); + foreach ($thisModelDataAwareFormsIterator as $form) { + $dispatcher = $form->getConfig()->getEventDispatcher(); + if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { + $event = new PostSubmitEvent($form, $viewData); + $dispatcher->dispatch($event, FormEvents::POST_SUBMIT); + } } return $this; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 3701b653f855e..11b1410d80fc9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Event\PostSubmitEvent; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\Exception\TransformationFailedException; @@ -23,6 +24,8 @@ use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\Forms; use Symfony\Component\Form\Tests\Fixtures\Author; use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; @@ -859,6 +862,51 @@ public function testSortingViewChildrenBasedOnPriorityOption() ]; $this->assertSame($expected, array_keys($view->children)); } + + public function testSetDataEventsDispatchedToChildrenWithInheritDataConfigured() + { + $data = ['child' => 'foo']; + $calledEvents = []; + $form = $this->factory->createNamedBuilder('form', self::TESTED_TYPE, $data) + ->add( + $this->factory->createNamedBuilder('inherit_data_type', self::TESTED_TYPE, null, [ + 'inherit_data' => true, + ]) + ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use (&$calledEvents) { + $calledEvents[] = FormEvents::PRE_SET_DATA; + $event->getForm()->add('child', self::TESTED_TYPE); + }) + ->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use (&$calledEvents) { + $calledEvents[] = FormEvents::POST_SET_DATA; + $event->getForm()->add('child2', self::TESTED_TYPE, ['data' => $event->getData()['child']]); + }) + ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use (&$calledEvents) { + $calledEvents[] = FormEvents::PRE_SUBMIT; + $this->assertNotNull($event->getData()); + }) + ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use (&$calledEvents) { + $calledEvents[] = FormEvents::SUBMIT; + $this->assertNotNull($event->getData()); + }) + ->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) use (&$calledEvents) { + $calledEvents[] = FormEvents::POST_SUBMIT; + $this->assertNotNull($event->getData()); + }) + ) + ->getForm(); + $this->assertTrue($form->get('inherit_data_type')->has('child')); + $this->assertTrue($form->get('inherit_data_type')->has('child2')); + $this->assertSame('foo', $form->get('inherit_data_type')->get('child')->getData()); + $this->assertSame('foo', $form->get('inherit_data_type')->get('child2')->getData()); + $errorMessage = '"%s" event has not been called on form child with "inherit_data" option.'; + $form->submit($data); + $this->assertContains(FormEvents::PRE_SET_DATA, $calledEvents, sprintf($errorMessage, FormEvents::PRE_SET_DATA)); + $this->assertContains(FormEvents::POST_SET_DATA, $calledEvents, sprintf($errorMessage, FormEvents::POST_SET_DATA)); + $this->assertContains(FormEvents::PRE_SUBMIT, $calledEvents, sprintf($errorMessage, FormEvents::PRE_SUBMIT)); + $this->assertContains(FormEvents::SUBMIT, $calledEvents, sprintf($errorMessage, FormEvents::SUBMIT)); + $this->assertContains(FormEvents::POST_SUBMIT, $calledEvents, sprintf($errorMessage, FormEvents::POST_SUBMIT)); + $this->assertCount(5, $calledEvents); + } } class Money diff --git a/src/Symfony/Component/Form/Util/InheritDataAwareFilter.php b/src/Symfony/Component/Form/Util/InheritDataAwareFilter.php new file mode 100644 index 0000000000000..916e4e0efb2e6 --- /dev/null +++ b/src/Symfony/Component/Form/Util/InheritDataAwareFilter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * @author Cristoforo Cervino + */ +namespace Symfony\Component\Form\Util; + +class InheritDataAwareFilter extends \FilterIterator +{ + public function accept(): bool + { + return (bool) $this->current()->getConfig()->getInheritData(); + } +}