vendor/symfony/validator/ValidatorBuilder.php line 197

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\Validator;
  11. use Doctrine\Common\Annotations\AnnotationReader;
  12. use Doctrine\Common\Annotations\PsrCachedReader;
  13. use Doctrine\Common\Annotations\Reader;
  14. use Psr\Cache\CacheItemPoolInterface;
  15. use Psr\Container\ContainerInterface;
  16. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  17. use Symfony\Component\Validator\Context\ExecutionContextFactory;
  18. use Symfony\Component\Validator\Exception\LogicException;
  19. use Symfony\Component\Validator\Exception\ValidatorException;
  20. use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
  21. use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
  22. use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
  23. use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
  24. use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
  25. use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
  26. use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
  27. use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
  28. use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
  29. use Symfony\Component\Validator\Validator\RecursiveValidator;
  30. use Symfony\Component\Validator\Validator\ValidatorInterface;
  31. use Symfony\Contracts\Translation\LocaleAwareInterface;
  32. use Symfony\Contracts\Translation\TranslatorInterface;
  33. use Symfony\Contracts\Translation\TranslatorTrait;
  34. // Help opcache.preload discover always-needed symbols
  35. class_exists(TranslatorInterface::class);
  36. class_exists(LocaleAwareInterface::class);
  37. class_exists(TranslatorTrait::class);
  38. /**
  39. * @author Bernhard Schussek <bschussek@gmail.com>
  40. */
  41. class ValidatorBuilder
  42. {
  43. private array $initializers = [];
  44. private array $loaders = [];
  45. private array $xmlMappings = [];
  46. private array $yamlMappings = [];
  47. private array $methodMappings = [];
  48. private ?Reader $annotationReader = null;
  49. private bool $enableAttributeMapping = false;
  50. private ?MetadataFactoryInterface $metadataFactory = null;
  51. private ConstraintValidatorFactoryInterface $validatorFactory;
  52. private ?ContainerInterface $groupProviderLocator = null;
  53. private ?CacheItemPoolInterface $mappingCache = null;
  54. private ?TranslatorInterface $translator = null;
  55. private ?string $translationDomain = null;
  56. /**
  57. * Adds an object initializer to the validator.
  58. *
  59. * @return $this
  60. */
  61. public function addObjectInitializer(ObjectInitializerInterface $initializer): static
  62. {
  63. $this->initializers[] = $initializer;
  64. return $this;
  65. }
  66. /**
  67. * Adds a list of object initializers to the validator.
  68. *
  69. * @param ObjectInitializerInterface[] $initializers
  70. *
  71. * @return $this
  72. */
  73. public function addObjectInitializers(array $initializers): static
  74. {
  75. $this->initializers = array_merge($this->initializers, $initializers);
  76. return $this;
  77. }
  78. /**
  79. * Adds an XML constraint mapping file to the validator.
  80. *
  81. * @return $this
  82. */
  83. public function addXmlMapping(string $path): static
  84. {
  85. if (null !== $this->metadataFactory) {
  86. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  87. }
  88. $this->xmlMappings[] = $path;
  89. return $this;
  90. }
  91. /**
  92. * Adds a list of XML constraint mapping files to the validator.
  93. *
  94. * @param string[] $paths The paths to the mapping files
  95. *
  96. * @return $this
  97. */
  98. public function addXmlMappings(array $paths): static
  99. {
  100. if (null !== $this->metadataFactory) {
  101. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  102. }
  103. $this->xmlMappings = array_merge($this->xmlMappings, $paths);
  104. return $this;
  105. }
  106. /**
  107. * Adds a YAML constraint mapping file to the validator.
  108. *
  109. * @param string $path The path to the mapping file
  110. *
  111. * @return $this
  112. */
  113. public function addYamlMapping(string $path): static
  114. {
  115. if (null !== $this->metadataFactory) {
  116. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  117. }
  118. $this->yamlMappings[] = $path;
  119. return $this;
  120. }
  121. /**
  122. * Adds a list of YAML constraint mappings file to the validator.
  123. *
  124. * @param string[] $paths The paths to the mapping files
  125. *
  126. * @return $this
  127. */
  128. public function addYamlMappings(array $paths): static
  129. {
  130. if (null !== $this->metadataFactory) {
  131. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  132. }
  133. $this->yamlMappings = array_merge($this->yamlMappings, $paths);
  134. return $this;
  135. }
  136. /**
  137. * Enables constraint mapping using the given static method.
  138. *
  139. * @return $this
  140. */
  141. public function addMethodMapping(string $methodName): static
  142. {
  143. if (null !== $this->metadataFactory) {
  144. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  145. }
  146. $this->methodMappings[] = $methodName;
  147. return $this;
  148. }
  149. /**
  150. * Enables constraint mapping using the given static methods.
  151. *
  152. * @param string[] $methodNames The names of the methods
  153. *
  154. * @return $this
  155. */
  156. public function addMethodMappings(array $methodNames): static
  157. {
  158. if (null !== $this->metadataFactory) {
  159. throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.');
  160. }
  161. $this->methodMappings = array_merge($this->methodMappings, $methodNames);
  162. return $this;
  163. }
  164. /**
  165. * @deprecated since Symfony 6.4, use "enableAttributeMapping()" instead.
  166. *
  167. * @return $this
  168. */
  169. public function enableAnnotationMapping(): static
  170. {
  171. trigger_deprecation('symfony/validator', '6.4', 'Method "%s()" is deprecated, use "enableAttributeMapping()" instead.', __METHOD__);
  172. return $this->enableAttributeMapping();
  173. }
  174. /**
  175. * Enables attribute-based constraint mapping.
  176. *
  177. * @return $this
  178. */
  179. public function enableAttributeMapping(): static
  180. {
  181. if (null !== $this->metadataFactory) {
  182. throw new ValidatorException('You cannot enable attribute mapping after setting a custom metadata factory. Configure your metadata factory instead.');
  183. }
  184. $this->enableAttributeMapping = true;
  185. return $this;
  186. }
  187. /**
  188. * @deprecated since Symfony 6.4, use "disableAttributeMapping()" instead
  189. *
  190. * @return $this
  191. */
  192. public function disableAnnotationMapping(): static
  193. {
  194. trigger_deprecation('symfony/validator', '6.4', 'Method "%s()" is deprecated, use "disableAttributeMapping()" instead.', __METHOD__);
  195. return $this->disableAttributeMapping();
  196. }
  197. /**
  198. * Disables attribute-based constraint mapping.
  199. *
  200. * @return $this
  201. */
  202. public function disableAttributeMapping(): static
  203. {
  204. $this->annotationReader = null;
  205. $this->enableAttributeMapping = false;
  206. return $this;
  207. }
  208. /**
  209. * @deprecated since Symfony 6.4 without replacement
  210. *
  211. * @return $this
  212. */
  213. public function setDoctrineAnnotationReader(?Reader $reader): static
  214. {
  215. trigger_deprecation('symfony/validator', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__);
  216. $this->annotationReader = $reader;
  217. return $this;
  218. }
  219. /**
  220. * @deprecated since Symfony 6.4 without replacement
  221. *
  222. * @return $this
  223. */
  224. public function addDefaultDoctrineAnnotationReader(): static
  225. {
  226. trigger_deprecation('symfony/validator', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__);
  227. $this->annotationReader = $this->createAnnotationReader();
  228. return $this;
  229. }
  230. /**
  231. * Sets the class metadata factory used by the validator.
  232. *
  233. * @return $this
  234. */
  235. public function setMetadataFactory(MetadataFactoryInterface $metadataFactory): static
  236. {
  237. if (\count($this->xmlMappings) > 0 || \count($this->yamlMappings) > 0 || \count($this->methodMappings) > 0 || $this->enableAttributeMapping) {
  238. throw new ValidatorException('You cannot set a custom metadata factory after adding custom mappings. You should do either of both.');
  239. }
  240. $this->metadataFactory = $metadataFactory;
  241. return $this;
  242. }
  243. /**
  244. * Sets the cache for caching class metadata.
  245. *
  246. * @return $this
  247. */
  248. public function setMappingCache(CacheItemPoolInterface $cache): static
  249. {
  250. if (null !== $this->metadataFactory) {
  251. throw new ValidatorException('You cannot set a custom mapping cache after setting a custom metadata factory. Configure your metadata factory instead.');
  252. }
  253. $this->mappingCache = $cache;
  254. return $this;
  255. }
  256. /**
  257. * Sets the constraint validator factory used by the validator.
  258. *
  259. * @return $this
  260. */
  261. public function setConstraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory): static
  262. {
  263. $this->validatorFactory = $validatorFactory;
  264. return $this;
  265. }
  266. /**
  267. * @return $this
  268. */
  269. public function setGroupProviderLocator(ContainerInterface $groupProviderLocator): static
  270. {
  271. $this->groupProviderLocator = $groupProviderLocator;
  272. return $this;
  273. }
  274. /**
  275. * Sets the translator used for translating violation messages.
  276. *
  277. * @return $this
  278. */
  279. public function setTranslator(TranslatorInterface $translator): static
  280. {
  281. $this->translator = $translator;
  282. return $this;
  283. }
  284. /**
  285. * Sets the default translation domain of violation messages.
  286. *
  287. * The same message can have different translations in different domains.
  288. * Pass the domain that is used for violation messages by default to this
  289. * method.
  290. *
  291. * @return $this
  292. */
  293. public function setTranslationDomain(?string $translationDomain): static
  294. {
  295. $this->translationDomain = $translationDomain;
  296. return $this;
  297. }
  298. /**
  299. * @return $this
  300. */
  301. public function addLoader(LoaderInterface $loader): static
  302. {
  303. $this->loaders[] = $loader;
  304. return $this;
  305. }
  306. /**
  307. * @return LoaderInterface[]
  308. */
  309. public function getLoaders(): array
  310. {
  311. $loaders = [];
  312. foreach ($this->xmlMappings as $xmlMapping) {
  313. $loaders[] = new XmlFileLoader($xmlMapping);
  314. }
  315. foreach ($this->yamlMappings as $yamlMappings) {
  316. $loaders[] = new YamlFileLoader($yamlMappings);
  317. }
  318. foreach ($this->methodMappings as $methodName) {
  319. $loaders[] = new StaticMethodLoader($methodName);
  320. }
  321. if ($this->enableAttributeMapping && $this->annotationReader) {
  322. $loaders[] = new AnnotationLoader($this->annotationReader);
  323. } elseif ($this->enableAttributeMapping) {
  324. $loaders[] = new AttributeLoader();
  325. }
  326. return array_merge($loaders, $this->loaders);
  327. }
  328. /**
  329. * Builds and returns a new validator object.
  330. */
  331. public function getValidator(): ValidatorInterface
  332. {
  333. $metadataFactory = $this->metadataFactory;
  334. if (!$metadataFactory) {
  335. $loaders = $this->getLoaders();
  336. $loader = null;
  337. if (\count($loaders) > 1) {
  338. $loader = new LoaderChain($loaders);
  339. } elseif (1 === \count($loaders)) {
  340. $loader = $loaders[0];
  341. }
  342. $metadataFactory = new LazyLoadingMetadataFactory($loader, $this->mappingCache);
  343. }
  344. $validatorFactory = $this->validatorFactory ?? new ConstraintValidatorFactory();
  345. $translator = $this->translator;
  346. if (null === $translator) {
  347. $translator = new class implements TranslatorInterface, LocaleAwareInterface {
  348. use TranslatorTrait;
  349. };
  350. // Force the locale to be 'en' when no translator is provided rather than relying on the Intl default locale
  351. // This avoids depending on Intl or the stub implementation being available. It also ensures that Symfony
  352. // validation messages are pluralized properly even when the default locale gets changed because they are in
  353. // English.
  354. $translator->setLocale('en');
  355. }
  356. $contextFactory = new ExecutionContextFactory($translator, $this->translationDomain);
  357. return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers, $this->groupProviderLocator);
  358. }
  359. private function createAnnotationReader(): Reader
  360. {
  361. if (!class_exists(AnnotationReader::class)) {
  362. throw new LogicException('Enabling annotation based constraint mapping requires the packages doctrine/annotations and symfony/cache to be installed.');
  363. }
  364. if (class_exists(ArrayAdapter::class)) {
  365. return new PsrCachedReader(new AnnotationReader(), new ArrayAdapter());
  366. }
  367. throw new LogicException('Enabling annotation based constraint mapping requires the packages doctrine/annotations and symfony/cache to be installed.');
  368. }
  369. }