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

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 in 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.                             $fieldFormConstraint->groups $group;
  76.                             $this->context->setNode($this->context->getValue(), $field$this->context->getMetadata(), $this->context->getPropertyPath());
  77.                             $validator->atPath(sprintf('children[%s]'$field->getName()))->validate($field$fieldFormConstraint$group);
  78.                         }
  79.                     }
  80.                     if ($violationsCount $this->context->getViolations()->count()) {
  81.                         break;
  82.                     }
  83.                 }
  84.             } else {
  85.                 if ($validateDataGraph) {
  86.                     $validator->atPath('data')->validate($datanull$groups);
  87.                 }
  88.                 $groupedConstraints = [];
  89.                 foreach ($constraints as $constraint) {
  90.                     // For the "Valid" constraint, validate the data in all groups
  91.                     if ($constraint instanceof Valid) {
  92.                         if (\is_object($data)) {
  93.                             $validator->atPath('data')->validate($data$constraint$groups);
  94.                         }
  95.                         continue;
  96.                     }
  97.                     // Otherwise validate a constraint only once for the first
  98.                     // matching group
  99.                     foreach ($groups as $group) {
  100.                         if (\in_array($group$constraint->groups)) {
  101.                             $groupedConstraints[$group][] = $constraint;
  102.                             // Prevent duplicate validation
  103.                             if (!$constraint instanceof Composite) {
  104.                                 continue 2;
  105.                             }
  106.                         }
  107.                     }
  108.                 }
  109.                 foreach ($groupedConstraints as $group => $constraint) {
  110.                     $validator->atPath('data')->validate($data$constraint$group);
  111.                 }
  112.                 foreach ($form->all() as $field) {
  113.                     if ($field->isSubmitted()) {
  114.                         $this->resolvedGroups[$field] = $groups;
  115.                         $this->context->setNode($this->context->getValue(), $field$this->context->getMetadata(), $this->context->getPropertyPath());
  116.                         $validator->atPath(sprintf('children[%s]'$field->getName()))->validate($field$formConstraint);
  117.                     }
  118.                 }
  119.             }
  120.             if ($hasChildren && $form->isRoot()) {
  121.                 // destroy storage to avoid memory leaks
  122.                 $this->resolvedGroups = new \SplObjectStorage();
  123.             }
  124.         } elseif (!$form->isSynchronized()) {
  125.             $childrenSynchronized true;
  126.             /** @var FormInterface $child */
  127.             foreach ($form as $child) {
  128.                 if (!$child->isSynchronized()) {
  129.                     $childrenSynchronized false;
  130.                     $this->context->setNode($this->context->getValue(), $child$this->context->getMetadata(), $this->context->getPropertyPath());
  131.                     $validator->atPath(sprintf('children[%s]'$child->getName()))->validate($child$formConstraint);
  132.                 }
  133.             }
  134.             // Mark the form with an error if it is not synchronized BUT all
  135.             // of its children are synchronized. If any child is not
  136.             // synchronized, an error is displayed there already and showing
  137.             // a second error in its parent form is pointless, or worse, may
  138.             // lead to duplicate errors if error bubbling is enabled on the
  139.             // child.
  140.             // See also https://github.com/symfony/symfony/issues/4359
  141.             if ($childrenSynchronized) {
  142.                 $clientDataAsString is_scalar($form->getViewData())
  143.                     ? (string) $form->getViewData()
  144.                     : \gettype($form->getViewData());
  145.                 $failure $form->getTransformationFailure();
  146.                 $this->context->setConstraint($formConstraint);
  147.                 $this->context->buildViolation($failure->getInvalidMessage() ?? $config->getOption('invalid_message'))
  148.                     ->setParameters(array_replace(
  149.                         ['{{ value }}' => $clientDataAsString],
  150.                         $config->getOption('invalid_message_parameters'),
  151.                         $failure->getInvalidMessageParameters()
  152.                     ))
  153.                     ->setInvalidValue($form->getViewData())
  154.                     ->setCode(Form::NOT_SYNCHRONIZED_ERROR)
  155.                     ->setCause($failure)
  156.                     ->addViolation();
  157.             }
  158.         }
  159.         // Mark the form with an error if it contains extra fields
  160.         if (!$config->getOption('allow_extra_fields') && \count($form->getExtraData()) > 0) {
  161.             $this->context->setConstraint($formConstraint);
  162.             $this->context->buildViolation($config->getOption('extra_fields_message'''))
  163.                 ->setParameter('{{ extra_fields }}''"'.implode('", "'array_keys($form->getExtraData())).'"')
  164.                 ->setInvalidValue($form->getExtraData())
  165.                 ->setCode(Form::NO_SUCH_FIELD_ERROR)
  166.                 ->addViolation();
  167.         }
  168.     }
  169.     /**
  170.      * Returns the validation groups of the given form.
  171.      *
  172.      * @return string|GroupSequence|array<string|GroupSequence> The validation groups
  173.      */
  174.     private function getValidationGroups(FormInterface $form)
  175.     {
  176.         // Determine the clicked button of the complete form tree
  177.         $clickedButton null;
  178.         if (method_exists($form'getClickedButton')) {
  179.             $clickedButton $form->getClickedButton();
  180.         }
  181.         if (null !== $clickedButton) {
  182.             $groups $clickedButton->getConfig()->getOption('validation_groups');
  183.             if (null !== $groups) {
  184.                 return self::resolveValidationGroups($groups$form);
  185.             }
  186.         }
  187.         do {
  188.             $groups $form->getConfig()->getOption('validation_groups');
  189.             if (null !== $groups) {
  190.                 return self::resolveValidationGroups($groups$form);
  191.             }
  192.             if (isset($this->resolvedGroups[$form])) {
  193.                 return $this->resolvedGroups[$form];
  194.             }
  195.             $form $form->getParent();
  196.         } while (null !== $form);
  197.         return [Constraint::DEFAULT_GROUP];
  198.     }
  199.     /**
  200.      * Post-processes the validation groups option for a given form.
  201.      *
  202.      * @param string|GroupSequence|array<string|GroupSequence>|callable $groups The validation groups
  203.      *
  204.      * @return GroupSequence|array<string|GroupSequence> The validation groups
  205.      */
  206.     private static function resolveValidationGroups($groupsFormInterface $form)
  207.     {
  208.         if (!\is_string($groups) && \is_callable($groups)) {
  209.             $groups $groups($form);
  210.         }
  211.         if ($groups instanceof GroupSequence) {
  212.             return $groups;
  213.         }
  214.         return (array) $groups;
  215.     }
  216.     private static function getConstraintsInGroups($constraints$group)
  217.     {
  218.         return array_filter($constraints, static function (Constraint $constraint) use ($group) {
  219.             return \in_array($group$constraint->groupstrue);
  220.         });
  221.     }
  222. }