vendor/symfony/form/Extension/DataCollector/FormDataCollector.php line 266

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Form\Extension\DataCollector;
  11. use Symfony\Component\Form\FormInterface;
  12. use Symfony\Component\Form\FormView;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\DataCollector\DataCollector;
  16. use Symfony\Component\Validator\ConstraintViolationInterface;
  17. use Symfony\Component\VarDumper\Caster\Caster;
  18. use Symfony\Component\VarDumper\Caster\ClassStub;
  19. use Symfony\Component\VarDumper\Caster\StubCaster;
  20. use Symfony\Component\VarDumper\Cloner\Data;
  21. use Symfony\Component\VarDumper\Cloner\Stub;
  22. /**
  23.  * Data collector for {@link FormInterface} instances.
  24.  *
  25.  * @author Robert Schönthal <robert.schoenthal@gmail.com>
  26.  * @author Bernhard Schussek <bschussek@gmail.com>
  27.  *
  28.  * @final
  29.  */
  30. class FormDataCollector extends DataCollector implements FormDataCollectorInterface
  31. {
  32.     private FormDataExtractorInterface $dataExtractor;
  33.     /**
  34.      * Stores the collected data per {@link FormInterface} instance.
  35.      *
  36.      * Uses the hashes of the forms as keys. This is preferable over using
  37.      * {@link \SplObjectStorage}, because in this way no references are kept
  38.      * to the {@link FormInterface} instances.
  39.      */
  40.     private array $dataByForm;
  41.     /**
  42.      * Stores the collected data per {@link FormView} instance.
  43.      *
  44.      * Uses the hashes of the views as keys. This is preferable over using
  45.      * {@link \SplObjectStorage}, because in this way no references are kept
  46.      * to the {@link FormView} instances.
  47.      */
  48.     private array $dataByView;
  49.     /**
  50.      * Connects {@link FormView} with {@link FormInterface} instances.
  51.      *
  52.      * Uses the hashes of the views as keys and the hashes of the forms as
  53.      * values. This is preferable over storing the objects directly, because
  54.      * this way they can safely be discarded by the GC.
  55.      */
  56.     private array $formsByView;
  57.     public function __construct(FormDataExtractorInterface $dataExtractor)
  58.     {
  59.         if (!class_exists(ClassStub::class)) {
  60.             throw new \LogicException(sprintf('The VarDumper component is needed for using the "%s" class. Install symfony/var-dumper version 3.4 or above.'__CLASS__));
  61.         }
  62.         $this->dataExtractor $dataExtractor;
  63.         $this->reset();
  64.     }
  65.     /**
  66.      * Does nothing. The data is collected during the form event listeners.
  67.      */
  68.     public function collect(Request $requestResponse $response\Throwable $exception null)
  69.     {
  70.     }
  71.     public function reset()
  72.     {
  73.         $this->data = [
  74.             'forms' => [],
  75.             'forms_by_hash' => [],
  76.             'nb_errors' => 0,
  77.         ];
  78.     }
  79.     public function associateFormWithView(FormInterface $formFormView $view)
  80.     {
  81.         $this->formsByView[spl_object_hash($view)] = spl_object_hash($form);
  82.     }
  83.     public function collectConfiguration(FormInterface $form)
  84.     {
  85.         $hash spl_object_hash($form);
  86.         if (!isset($this->dataByForm[$hash])) {
  87.             $this->dataByForm[$hash] = [];
  88.         }
  89.         $this->dataByForm[$hash] = array_replace(
  90.             $this->dataByForm[$hash],
  91.             $this->dataExtractor->extractConfiguration($form)
  92.         );
  93.         foreach ($form as $child) {
  94.             $this->collectConfiguration($child);
  95.         }
  96.     }
  97.     public function collectDefaultData(FormInterface $form)
  98.     {
  99.         $hash spl_object_hash($form);
  100.         if (!isset($this->dataByForm[$hash])) {
  101.             // field was created by form event
  102.             $this->collectConfiguration($form);
  103.         }
  104.         $this->dataByForm[$hash] = array_replace(
  105.             $this->dataByForm[$hash],
  106.             $this->dataExtractor->extractDefaultData($form)
  107.         );
  108.         foreach ($form as $child) {
  109.             $this->collectDefaultData($child);
  110.         }
  111.     }
  112.     public function collectSubmittedData(FormInterface $form)
  113.     {
  114.         $hash spl_object_hash($form);
  115.         if (!isset($this->dataByForm[$hash])) {
  116.             // field was created by form event
  117.             $this->collectConfiguration($form);
  118.             $this->collectDefaultData($form);
  119.         }
  120.         $this->dataByForm[$hash] = array_replace(
  121.             $this->dataByForm[$hash],
  122.             $this->dataExtractor->extractSubmittedData($form)
  123.         );
  124.         // Count errors
  125.         if (isset($this->dataByForm[$hash]['errors'])) {
  126.             $this->data['nb_errors'] += \count($this->dataByForm[$hash]['errors']);
  127.         }
  128.         foreach ($form as $child) {
  129.             $this->collectSubmittedData($child);
  130.             // Expand current form if there are children with errors
  131.             if (empty($this->dataByForm[$hash]['has_children_error'])) {
  132.                 $childData $this->dataByForm[spl_object_hash($child)];
  133.                 $this->dataByForm[$hash]['has_children_error'] = !empty($childData['has_children_error']) || !empty($childData['errors']);
  134.             }
  135.         }
  136.     }
  137.     public function collectViewVariables(FormView $view)
  138.     {
  139.         $hash spl_object_hash($view);
  140.         if (!isset($this->dataByView[$hash])) {
  141.             $this->dataByView[$hash] = [];
  142.         }
  143.         $this->dataByView[$hash] = array_replace(
  144.             $this->dataByView[$hash],
  145.             $this->dataExtractor->extractViewVariables($view)
  146.         );
  147.         foreach ($view->children as $child) {
  148.             $this->collectViewVariables($child);
  149.         }
  150.     }
  151.     public function buildPreliminaryFormTree(FormInterface $form)
  152.     {
  153.         $this->data['forms'][$form->getName()] = &$this->recursiveBuildPreliminaryFormTree($form$this->data['forms_by_hash']);
  154.     }
  155.     public function buildFinalFormTree(FormInterface $formFormView $view)
  156.     {
  157.         $this->data['forms'][$form->getName()] = &$this->recursiveBuildFinalFormTree($form$view$this->data['forms_by_hash']);
  158.     }
  159.     public function getName(): string
  160.     {
  161.         return 'form';
  162.     }
  163.     public function getData(): array|Data
  164.     {
  165.         return $this->data;
  166.     }
  167.     /**
  168.      * @internal
  169.      */
  170.     public function __sleep(): array
  171.     {
  172.         foreach ($this->data['forms_by_hash'] as &$form) {
  173.             if (isset($form['type_class']) && !$form['type_class'] instanceof ClassStub) {
  174.                 $form['type_class'] = new ClassStub($form['type_class']);
  175.             }
  176.         }
  177.         $this->data $this->cloneVar($this->data);
  178.         return parent::__sleep();
  179.     }
  180.     protected function getCasters(): array
  181.     {
  182.         return parent::getCasters() + [
  183.             \Exception::class => function (\Exception $e, array $aStub $s) {
  184.                 foreach (["\0Exception\0previous""\0Exception\0trace"] as $k) {
  185.                     if (isset($a[$k])) {
  186.                         unset($a[$k]);
  187.                         ++$s->cut;
  188.                     }
  189.                 }
  190.                 return $a;
  191.             },
  192.             FormInterface::class => function (FormInterface $f, array $a) {
  193.                 return [
  194.                     Caster::PREFIX_VIRTUAL.'name' => $f->getName(),
  195.                     Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(\get_class($f->getConfig()->getType()->getInnerType())),
  196.                 ];
  197.             },
  198.             FormView::class => StubCaster::cutInternals(...),
  199.             ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) {
  200.                 return [
  201.                     Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(),
  202.                     Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(),
  203.                     Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(),
  204.                 ];
  205.             },
  206.         ];
  207.     }
  208.     private function &recursiveBuildPreliminaryFormTree(FormInterface $form, array &$outputByHash)
  209.     {
  210.         $hash spl_object_hash($form);
  211.         $output = &$outputByHash[$hash];
  212.         $output $this->dataByForm[$hash]
  213.             ?? [];
  214.         $output['children'] = [];
  215.         foreach ($form as $name => $child) {
  216.             $output['children'][$name] = &$this->recursiveBuildPreliminaryFormTree($child$outputByHash);
  217.         }
  218.         return $output;
  219.     }
  220.     private function &recursiveBuildFinalFormTree(FormInterface $form nullFormView $view, array &$outputByHash)
  221.     {
  222.         $viewHash spl_object_hash($view);
  223.         $formHash null;
  224.         if (null !== $form) {
  225.             $formHash spl_object_hash($form);
  226.         } elseif (isset($this->formsByView[$viewHash])) {
  227.             // The FormInterface instance of the CSRF token is never contained in
  228.             // the FormInterface tree of the form, so we need to get the
  229.             // corresponding FormInterface instance for its view in a different way
  230.             $formHash $this->formsByView[$viewHash];
  231.         }
  232.         if (null !== $formHash) {
  233.             $output = &$outputByHash[$formHash];
  234.         }
  235.         $output $this->dataByView[$viewHash]
  236.             ?? [];
  237.         if (null !== $formHash) {
  238.             $output array_replace(
  239.                 $output,
  240.                 $this->dataByForm[$formHash]
  241.                     ?? []
  242.             );
  243.         }
  244.         $output['children'] = [];
  245.         foreach ($view->children as $name => $childView) {
  246.             // The CSRF token, for example, is never added to the form tree.
  247.             // It is only present in the view.
  248.             $childForm $form?->has($name) ? $form->get($name) : null;
  249.             $output['children'][$name] = &$this->recursiveBuildFinalFormTree($childForm$childView$outputByHash);
  250.         }
  251.         return $output;
  252.     }
  253. }