/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php

https://github.com/omarali/symfony · PHP · 267 lines · 189 code · 49 blank · 29 comment · 29 complexity · 9e99360fd4d688f040fa5bda67b99992 MD5 · raw file

  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\Validator;
  11. use Symfony\Component\Form\FormInterface;
  12. use Symfony\Component\Form\FormValidatorInterface;
  13. use Symfony\Component\Form\FormError;
  14. use Symfony\Component\Form\Util\VirtualFormAwareIterator;
  15. use Symfony\Component\Form\Exception\FormException;
  16. use Symfony\Component\Validator\ValidatorInterface;
  17. use Symfony\Component\Validator\ExecutionContext;
  18. use Symfony\Component\Form\Util\PropertyPath;
  19. class DelegatingValidator implements FormValidatorInterface
  20. {
  21. private $validator;
  22. public function __construct(ValidatorInterface $validator)
  23. {
  24. $this->validator = $validator;
  25. }
  26. /**
  27. * Validates the form and its domain object.
  28. *
  29. * @param FormInterface $form A FormInterface instance
  30. */
  31. public function validate(FormInterface $form)
  32. {
  33. if ($form->isRoot()) {
  34. $mapping = array();
  35. $forms = array();
  36. $this->buildFormPathMapping($form, $mapping);
  37. $this->buildDataPathMapping($form, $mapping);
  38. $this->buildNamePathMapping($form, $forms);
  39. $this->resolveMappingPlaceholders($mapping, $forms);
  40. // Validate the form in group "Default"
  41. // Validation of the data in the custom group is done by validateData(),
  42. // which is constrained by the Execute constraint
  43. if ($form->hasAttribute('validation_constraint')) {
  44. $violations = $this->validator->validateValue(
  45. $form->getData(),
  46. $form->getAttribute('validation_constraint'),
  47. self::getFormValidationGroups($form)
  48. );
  49. if ($violations) {
  50. foreach ($violations as $violation) {
  51. $propertyPath = new PropertyPath($violation->getPropertyPath());
  52. $template = $violation->getMessageTemplate();
  53. $parameters = $violation->getMessageParameters();
  54. $error = new FormError($template, $parameters);
  55. $child = $form;
  56. foreach ($propertyPath->getElements() as $element) {
  57. $children = $child->getChildren();
  58. if (!isset($children[$element])) {
  59. $form->addError($error);
  60. break;
  61. }
  62. $child = $children[$element];
  63. }
  64. $child->addError($error);
  65. }
  66. }
  67. } elseif ($violations = $this->validator->validate($form)) {
  68. foreach ($violations as $violation) {
  69. $propertyPath = $violation->getPropertyPath();
  70. $template = $violation->getMessageTemplate();
  71. $parameters = $violation->getMessageParameters();
  72. $error = new FormError($template, $parameters);
  73. foreach ($mapping as $mappedPath => $child) {
  74. if (preg_match($mappedPath, $propertyPath)) {
  75. $child->addError($error);
  76. continue 2;
  77. }
  78. }
  79. $form->addError($error);
  80. }
  81. }
  82. }
  83. }
  84. /**
  85. * Validates the data of a form
  86. *
  87. * This method is called automatically during the validation process.
  88. *
  89. * @param FormInterface $form The validated form
  90. * @param ExecutionContext $context The current validation context
  91. */
  92. static public function validateFormData(FormInterface $form, ExecutionContext $context)
  93. {
  94. if (is_object($form->getData()) || is_array($form->getData())) {
  95. $propertyPath = $context->getPropertyPath();
  96. $graphWalker = $context->getGraphWalker();
  97. // The Execute constraint is called on class level, so we need to
  98. // set the property manually
  99. $context->setCurrentProperty('data');
  100. // Adjust the property path accordingly
  101. if (!empty($propertyPath)) {
  102. $propertyPath .= '.';
  103. }
  104. $propertyPath .= 'data';
  105. foreach (self::getFormValidationGroups($form) as $group) {
  106. $graphWalker->walkReference($form->getData(), $group, $propertyPath, true);
  107. }
  108. }
  109. }
  110. static protected function getFormValidationGroups(FormInterface $form)
  111. {
  112. $groups = null;
  113. if ($form->hasAttribute('validation_groups')) {
  114. $groups = $form->getAttribute('validation_groups');
  115. if (is_callable($groups)) {
  116. $groups = (array) call_user_func($groups, $form);
  117. }
  118. }
  119. $currentForm = $form;
  120. while (!$groups && $currentForm->hasParent()) {
  121. $currentForm = $currentForm->getParent();
  122. if ($currentForm->hasAttribute('validation_groups')) {
  123. $groups = $currentForm->getAttribute('validation_groups');
  124. if (is_callable($groups)) {
  125. $groups = (array) call_user_func($groups, $currentForm);
  126. }
  127. }
  128. }
  129. if (null === $groups) {
  130. $groups = array('Default');
  131. }
  132. return (array) $groups;
  133. }
  134. private function buildFormPathMapping(FormInterface $form, array &$mapping, $formPath = 'children', $namePath = '')
  135. {
  136. foreach ($form->getAttribute('error_mapping') as $nestedDataPath => $nestedNamePath)
  137. {
  138. $mapping['/^'.preg_quote($formPath.'.data.'.$nestedDataPath).'(?!\w)/'] = $namePath.'.'.$nestedNamePath;
  139. }
  140. $iterator = new VirtualFormAwareIterator($form->getChildren());
  141. $iterator = new \RecursiveIteratorIterator($iterator);
  142. foreach ($iterator as $child) {
  143. $path = (string)$child->getAttribute('property_path');
  144. $parts = explode('.', $path, 2);
  145. $nestedNamePath = $namePath.'.'.$child->getName();
  146. if ($child->hasChildren() || isset($parts[1])) {
  147. $nestedFormPath = $formPath.'['.trim($parts[0], '[]').']';
  148. }
  149. else {
  150. $nestedFormPath = $formPath.'.data.'.$parts[0];
  151. }
  152. if (isset($parts[1])) {
  153. $nestedFormPath .= '.data.'.$parts[1];
  154. }
  155. if ($child->hasChildren()) {
  156. $this->buildFormPathMapping($child, $mapping, $nestedFormPath, $nestedNamePath);
  157. }
  158. $mapping['/^'.preg_quote($nestedFormPath, '/').'(?!\w)/'] = $child;
  159. }
  160. }
  161. private function buildDataPathMapping(FormInterface $form, array &$mapping, $dataPath = 'data', $namePath = '')
  162. {
  163. foreach ($form->getAttribute('error_mapping') as $nestedDataPath => $nestedNamePath)
  164. {
  165. $mapping['/^'.preg_quote($dataPath.'.'.$nestedDataPath).'(?!\w)/'] = $namePath.'.'.$nestedNamePath;
  166. }
  167. $iterator = new VirtualFormAwareIterator($form->getChildren());
  168. $iterator = new \RecursiveIteratorIterator($iterator);
  169. foreach ($iterator as $child) {
  170. $path = (string)$child->getAttribute('property_path');
  171. $nestedNamePath = $namePath.'.'.$child->getName();
  172. if (strpos($path, '[') === 0) {
  173. $nestedDataPaths = array($dataPath.$path);
  174. } else {
  175. $nestedDataPaths = array($dataPath.'.'.$path);
  176. if ($child->hasChildren()) {
  177. $nestedDataPaths[] = $dataPath.'['.$path.']';
  178. }
  179. }
  180. if ($child->hasChildren()) {
  181. // Needs when collection implements the Iterator
  182. // or for array used the Valid validator.
  183. if (is_array($child->getData()) || $child->getData() instanceof \Traversable) {
  184. $this->buildDataPathMapping($child, $mapping, $dataPath, $nestedNamePath);
  185. }
  186. foreach ($nestedDataPaths as $nestedDataPath) {
  187. $this->buildDataPathMapping($child, $mapping, $nestedDataPath, $nestedNamePath);
  188. }
  189. }
  190. foreach ($nestedDataPaths as $nestedDataPath) {
  191. $mapping['/^'.preg_quote($nestedDataPath, '/').'(?!\w)/'] = $child;
  192. }
  193. }
  194. }
  195. private function buildNamePathMapping(FormInterface $form, array &$forms, $namePath = '')
  196. {
  197. $iterator = new VirtualFormAwareIterator($form->getChildren());
  198. $iterator = new \RecursiveIteratorIterator($iterator);
  199. foreach ($iterator as $child) {
  200. $nestedNamePath = $namePath.'.'.$child->getName();
  201. $forms[$nestedNamePath] = $child;
  202. if ($child->hasChildren()) {
  203. $this->buildNamePathMapping($child, $forms, $nestedNamePath);
  204. }
  205. }
  206. }
  207. private function resolveMappingPlaceholders(array &$mapping, array $forms)
  208. {
  209. foreach ($mapping as $pattern => $form) {
  210. if (is_string($form)) {
  211. if (!isset($forms[$form])) {
  212. throw new FormException(sprintf('The child form with path "%s" does not exist', $form));
  213. }
  214. $mapping[$pattern] = $forms[$form];
  215. }
  216. }
  217. }
  218. }