4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Form\Extension\Validator\Constraints
;
14 use Symfony\Component\Form\ClickableInterface
;
15 use Symfony\Component\Form\FormInterface
;
16 use Symfony\Component\Form\Extension\Validator\Util\ServerParams
;
17 use Symfony\Component\Validator\Constraint
;
18 use Symfony\Component\Validator\ConstraintValidator
;
21 * @author Bernhard Schussek <bschussek@gmail.com>
23 class FormValidator
extends ConstraintValidator
28 private $serverParams;
31 * Creates a validator with the given server parameters.
33 * @param ServerParams $params The server parameters. Default
34 * parameters are created if null.
36 public function __construct(ServerParams
$params = null)
38 $this->serverParams
= $params ?: new ServerParams();
44 public function validate($form, Constraint
$constraint)
46 if (!$form instanceof FormInterface
) {
50 /* @var FormInterface $form */
51 $config = $form->getConfig();
53 if ($form->isSynchronized()) {
54 // Validate the form data only if transformation succeeded
55 $groups = self
::getValidationGroups($form);
57 // Validate the data against its own constraints
58 if (self
::allowDataWalking($form)) {
59 foreach ($groups as $group) {
60 $this->context
->validate($form->getData(), 'data', $group, true);
64 // Validate the data against the constraints defined
66 $constraints = $config->getOption('constraints');
67 foreach ($constraints as $constraint) {
68 foreach ($groups as $group) {
69 if (in_array($group, $constraint->groups
)) {
70 $this->context
->validateValue($form->getData(), $constraint, 'data', $group);
72 // Prevent duplicate validation
78 $childrenSynchronized = true;
80 foreach ($form as $child) {
81 if (!$child->isSynchronized()) {
82 $childrenSynchronized = false;
87 // Mark the form with an error if it is not synchronized BUT all
88 // of its children are synchronized. If any child is not
89 // synchronized, an error is displayed there already and showing
90 // a second error in its parent form is pointless, or worse, may
91 // lead to duplicate errors if error bubbling is enabled on the
93 // See also https://github.com/symfony/symfony/issues/4359
94 if ($childrenSynchronized) {
95 $clientDataAsString = is_scalar($form->getViewData())
96 ? (string) $form->getViewData()
97 : gettype($form->getViewData());
99 $this->context
->addViolation(
100 $config->getOption('invalid_message'),
101 array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')),
102 $form->getViewData(),
109 // Mark the form with an error if it contains extra fields
110 if (count($form->getExtraData()) > 0) {
111 $this->context
->addViolation(
112 $config->getOption('extra_fields_message'),
113 array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))),
114 $form->getExtraData()
118 // Mark the form with an error if the uploaded size was too large
119 $length = $this->serverParams
->getContentLength();
121 if ($form->isRoot() && null !== $length) {
122 $max = $this->serverParams
->getPostMaxSize();
124 if (!empty($max) && $length > $max) {
125 $this->context
->addViolation(
126 $config->getOption('post_max_size_message'),
127 array('{{ max }}' => $this->serverParams
->getNormalizedIniPostMaxSize()),
135 * Returns whether the data of a form may be walked.
137 * @param FormInterface $form The form to test.
139 * @return Boolean Whether the graph walker may walk the data.
141 private static function allowDataWalking(FormInterface
$form)
143 $data = $form->getData();
145 // Scalar values cannot have mapped constraints
146 if (!is_object($data) && !is_array($data)) {
150 // Root forms are always validated
151 if ($form->isRoot()) {
155 // Non-root forms are validated if validation cascading
156 // is enabled in all ancestor forms
157 while (null !== ($form = $form->getParent())) {
158 if (!$form->getConfig()->getOption('cascade_validation')) {
167 * Returns the validation groups of the given form.
169 * @param FormInterface $form The form.
171 * @return array The validation groups.
173 private static function getValidationGroups(FormInterface
$form)
175 $button = self
::findClickedButton($form->getRoot());
177 if (null !== $button) {
178 $groups = $button->getConfig()->getOption('validation_groups');
180 if (null !== $groups) {
181 return self
::resolveValidationGroups($groups, $form);
186 $groups = $form->getConfig()->getOption('validation_groups');
188 if (null !== $groups) {
189 return self
::resolveValidationGroups($groups, $form);
192 $form = $form->getParent();
193 } while (null !== $form);
195 return array(Constraint
::DEFAULT_GROUP
);
199 * Extracts a clicked button from a form tree, if one exists.
201 * @param FormInterface $form The root form.
203 * @return ClickableInterface|null The clicked button or null.
205 private static function findClickedButton(FormInterface
$form)
207 if ($form instanceof ClickableInterface
&& $form->isClicked()) {
211 foreach ($form as $child) {
212 if (null !== ($button = self
::findClickedButton($child))) {
221 * Post-processes the validation groups option for a given form.
223 * @param array|callable $groups The validation groups.
224 * @param FormInterface $form The validated form.
226 * @return array The validation groups.
228 private static function resolveValidationGroups($groups, FormInterface
$form)
230 if (!is_string($groups) && is_callable($groups)) {
231 $groups = call_user_func($groups, $form);
234 return (array) $groups;