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

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