vendor/symfony/form/Extension/Validator/Constraints/FormValidator.php line 35

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <[email protected]>
  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\Validator\Constraints;
  11. use Symfony\Component\Form\FormInterface;
  12. use Symfony\Component\Validator\Constraint;
  13. use Symfony\Component\Validator\Constraints\Composite;
  14. use Symfony\Component\Validator\Constraints\GroupSequence;
  15. use Symfony\Component\Validator\Constraints\Valid;
  16. use Symfony\Component\Validator\ConstraintValidator;
  17. use Symfony\Component\Validator\Exception\UnexpectedTypeException;
  18. /**
  19.  * @author Bernhard Schussek <[email protected]>
  20.  */
  21. class FormValidator extends ConstraintValidator
  22. {
  23.     /**
  24.      * @var \SplObjectStorage<FormInterface, array<int, string|string[]|GroupSequence>>
  25.      */
  26.     private $resolvedGroups;
  27.     /**
  28.      * {@inheritdoc}
  29.      */
  30.     public function validate($formConstraint $formConstraint)
  31.     {
  32.         if (!$formConstraint instanceof Form) {
  33.             throw new UnexpectedTypeException($formConstraintForm::class);
  34.         }
  35.         if (!$form instanceof FormInterface) {
  36.             return;
  37.         }
  38.         /* @var FormInterface $form */
  39.         $config $form->getConfig();
  40.         $validator $this->context->getValidator()->inContext($this->context);
  41.         if ($form->isSubmitted() && $form->isSynchronized()) {
  42.             // Validate the form data only if transformation succeeded
  43.             $groups $this->getValidationGroups($form);
  44.             if (!$groups) {
  45.                 return;
  46.             }
  47.             $data $form->getData();
  48.             // Validate the data against its own constraints
  49.             $validateDataGraph $form->isRoot()
  50.                 && (\is_object($data) || \is_array($data))
  51.                 && (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups))
  52.             ;
  53.             // Validate the data against the constraints defined in the form
  54.             /** @var Constraint[] $constraints */
  55.             $constraints $config->getOption('constraints', []);
  56.             $hasChildren $form->count() > 0;
  57.             if ($hasChildren && $form->isRoot()) {
  58.                 $this->resolvedGroups = new \SplObjectStorage();
  59.             }
  60.             if ($groups instanceof GroupSequence) {
  61.                 // Validate the data, the form AND nested fields in sequence
  62.                 $violationsCount $this->context->getViolations()->count();
  63.                 foreach ($groups->groups as $group) {
  64.                     if ($validateDataGraph) {
  65.                         $validator->atPath('data')->validate($datanull$group);
  66.                     }
  67.                     if ($groupedConstraints self::getConstraintsInGroups($constraints$group)) {
  68.                         $validator->atPath('data')->validate($data$groupedConstraints$group);
  69.                     }
  70.                     foreach ($form->all() as $field) {
  71.                         if ($field->isSubmitted()) {
  72.                             // remember to validate this field in one group only
  73.                             // otherwise resolving the groups would reuse the same
  74.                             // sequence recursively, thus some fields could fail
  75.                             // in different steps without breaking early enough
  76.                             $this->resolvedGroups[$field] = (array) $group;
  77.                             $fieldFormConstraint = new Form();
  78.                             $fieldFormConstraint->groups $group;
  79.                             $this->context->setNode($this->context->getValue(), $field$this->context->getMetadata(), $this->context->getPropertyPath());
  80.                             $validator->atPath(sprintf('children[%s]'$field->getName()))->validate($field$fieldFormConstraint$group);
  81.                         }
  82.                     }
  83.                     if ($violationsCount $this->context->getViolations()->count()) {
  84.                         break;
  85.                     }
  86.                 }
  87.             } else {
  88.                 if ($validateDataGraph) {
  89.                     $validator->atPath('data')->validate($datanull$groups);
  90.                 }
  91.                 $groupedConstraints = [];
  92.                 foreach ($constraints as $constraint) {
  93.                     // For the "Valid" constraint, validate the data in all groups
  94.                     if ($constraint instanceof Valid) {
  95.                         if (\is_object($data) || \is_array($data)) {
  96.                             $validator->atPath('data')->validate($data$constraint$groups);
  97.                         }
  98.                         continue;
  99.                     }
  100.                     // Otherwise validate a constraint only once for the first
  101.                     // matching group
  102.                     foreach ($groups as $group) {
  103.                         if (\in_array($group$constraint->groups)) {
  104.                             $groupedConstraints[$group][] = $constraint;
  105.                             // Prevent duplicate validation
  106.                             if (!$constraint instanceof Composite) {
  107.                                 continue 2;
  108.                             }
  109.                         }
  110.                     }
  111.                 }
  112.                 foreach ($groupedConstraints as $group => $constraint) {
  113.                     $validator->atPath('data')->validate($data$constraint$group);
  114.                 }
  115.                 foreach ($form->all() as $field) {
  116.                     if ($field->isSubmitted()) {
  117.                         $this->resolvedGroups[$field] = $groups;
  118.                         $this->context->setNode($this->context->getValue(), $field$this->context->getMetadata(), $this->context->getPropertyPath());
  119.                         $validator->atPath(sprintf('children[%s]'$field->getName()))->validate($field$formConstraint);
  120.                     }
  121.                 }
  122.             }
  123.             if ($hasChildren && $form->isRoot()) {
  124.                 // destroy storage to avoid memory leaks
  125.                 $this->resolvedGroups = new \SplObjectStorage();
  126.             }
  127.         } elseif (!$form->isSynchronized()) {
  128.             $childrenSynchronized true;
  129.             /** @var FormInterface $child */
  130.             foreach ($form as $child) {
  131.                 if (!$child->isSynchronized()) {
  132.                     $childrenSynchronized false;
  133.                     $this->context->setNode($this->context->getValue(), $child$this->context->getMetadata(), $this->context->getPropertyPath());
  134.                     $validator->atPath(sprintf('children[%s]'$child->getName()))->validate($child$formConstraint);
  135.                 }
  136.             }
  137.             // Mark the form with an error if it is not synchronized BUT all
  138.             // of its children are synchronized. If any child is not
  139.             // synchronized, an error is displayed there already and showing
  140.             // a second error in its parent form is pointless, or worse, may
  141.             // lead to duplicate errors if error bubbling is enabled on the
  142.             // child.
  143.             // See also https://github.com/symfony/symfony/issues/4359
  144.             if ($childrenSynchronized) {
  145.                 $clientDataAsString \is_scalar($form->getViewData())
  146.                     ? (string) $form->getViewData()
  147.                     : get_debug_type($form->getViewData());
  148.                 $failure $form->getTransformationFailure();
  149.                 $this->context->setConstraint($formConstraint);
  150.                 $this->context->buildViolation($failure->getInvalidMessage() ?? $config->getOption('invalid_message'))
  151.                     ->setParameters(array_replace(
  152.                         ['{{ value }}' => $clientDataAsString],
  153.                         $config->getOption('invalid_message_parameters'),
  154.                         $failure->getInvalidMessageParameters()
  155.                     ))
  156.                     ->setInvalidValue($form->getViewData())
  157.                     ->setCode(Form::NOT_SYNCHRONIZED_ERROR)
  158.                     ->setCause($failure)
  159.                     ->addViolation();
  160.             }
  161.         }
  162.         // Mark the form with an error if it contains extra fields
  163.         if (!$config->getOption('allow_extra_fields') && \count($form->getExtraData()) > 0) {
  164.             $this->context->setConstraint($formConstraint);
  165.             $this->context->buildViolation($config->getOption('extra_fields_message'''))
  166.                 ->setParameter('{{ extra_fields }}''"'.implode('", "'array_keys($form->getExtraData())).'"')
  167.                 ->setPlural(\count($form->getExtraData()))
  168.                 ->setInvalidValue($form->getExtraData())
  169.                 ->setCode(Form::NO_SUCH_FIELD_ERROR)
  170.                 ->addViolation();
  171.         }
  172.     }
  173.     /**
  174.      * Returns the validation groups of the given form.
  175.      *
  176.      * @return string|GroupSequence|array<string|GroupSequence>
  177.      */
  178.     private function getValidationGroups(FormInterface $form)
  179.     {
  180.         // Determine the clicked button of the complete form tree
  181.         $clickedButton null;
  182.         if (method_exists($form'getClickedButton')) {
  183.             $clickedButton $form->getClickedButton();
  184.         }
  185.         if (null !== $clickedButton) {
  186.             $groups $clickedButton->getConfig()->getOption('validation_groups');
  187.             if (null !== $groups) {
  188.                 return self::resolveValidationGroups($groups$form);
  189.             }
  190.         }
  191.         do {
  192.             $groups $form->getConfig()->getOption('validation_groups');
  193.             if (null !== $groups) {
  194.                 return self::resolveValidationGroups($groups$form);
  195.             }
  196.             if (isset($this->resolvedGroups[$form])) {
  197.                 return $this->resolvedGroups[$form];
  198.             }
  199.             $form $form->getParent();
  200.         } while (null !== $form);
  201.         return [Constraint::DEFAULT_GROUP];
  202.     }
  203.     /**
  204.      * Post-processes the validation groups option for a given form.
  205.      *
  206.      * @param string|GroupSequence|array<string|GroupSequence>|callable $groups The validation groups
  207.      *
  208.      * @return GroupSequence|array<string|GroupSequence>
  209.      */
  210.     private static function resolveValidationGroups($groupsFormInterface $form)
  211.     {
  212.         if (!\is_string($groups) && \is_callable($groups)) {
  213.             $groups $groups($form);
  214.         }
  215.         if ($groups instanceof GroupSequence) {
  216.             return $groups;
  217.         }
  218.         return (array) $groups;
  219.     }
  220.     private static function getConstraintsInGroups($constraints$group)
  221.     {
  222.         $groups = (array) $group;
  223.         return array_filter($constraints, static function (Constraint $constraint) use ($groups) {
  224.             foreach ($groups as $group) {
  225.                 if (\in_array($group$constraint->groupstrue)) {
  226.                     return true;
  227.                 }
  228.             }
  229.             return false;
  230.         });
  231.     }
  232. }