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

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