Config.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. /**
  3. * Validation configuration files handler
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\Validator;
  9. use Magento\Framework\Validator\Constraint\Option;
  10. use Magento\Framework\Validator\Constraint\Option\Callback;
  11. use Magento\Framework\Validator\Constraint\OptionInterface;
  12. class Config extends \Magento\Framework\Config\AbstractXml
  13. {
  14. /**#@+
  15. * Constraints types
  16. */
  17. const CONSTRAINT_TYPE_ENTITY = 'entity';
  18. const CONSTRAINT_TYPE_PROPERTY = 'property';
  19. /**#@-*/
  20. /**#@-*/
  21. protected $_defaultBuilderClass = \Magento\Framework\Validator\Builder::class;
  22. /**
  23. * @var \Magento\Framework\Validator\UniversalFactory
  24. */
  25. protected $_builderFactory;
  26. /**
  27. * @param array $configFiles
  28. * @param \Magento\Framework\Config\DomFactory $domFactory
  29. * @param \Magento\Framework\Validator\UniversalFactory $builderFactory
  30. */
  31. public function __construct(
  32. $configFiles,
  33. \Magento\Framework\Config\DomFactory $domFactory,
  34. \Magento\Framework\Validator\UniversalFactory $builderFactory
  35. ) {
  36. $this->_builderFactory = $builderFactory;
  37. parent::__construct($configFiles, $domFactory);
  38. }
  39. /**
  40. * Create validator builder instance based on entity and group.
  41. *
  42. * @param string $entityName
  43. * @param string $groupName
  44. * @param array|null $builderConfig
  45. * @throws \InvalidArgumentException
  46. * @return \Magento\Framework\Validator\Builder
  47. */
  48. public function createValidatorBuilder($entityName, $groupName, array $builderConfig = null)
  49. {
  50. if (!isset($this->_data[$entityName])) {
  51. throw new \InvalidArgumentException(sprintf('Unknown validation entity "%s"', $entityName));
  52. }
  53. if (!isset($this->_data[$entityName][$groupName])) {
  54. throw new \InvalidArgumentException(
  55. sprintf('Unknown validation group "%s" in entity "%s"', $groupName, $entityName)
  56. );
  57. }
  58. $builderClass = isset(
  59. $this->_data[$entityName][$groupName]['builder']
  60. ) ? $this->_data[$entityName][$groupName]['builder'] : $this->_defaultBuilderClass;
  61. if (!class_exists($builderClass)) {
  62. throw new \InvalidArgumentException(sprintf('Builder class "%s" was not found', $builderClass));
  63. }
  64. $builder = $this->_builderFactory->create(
  65. $builderClass,
  66. ['constraints' => $this->_data[$entityName][$groupName]['constraints']]
  67. );
  68. if (!$builder instanceof \Magento\Framework\Validator\Builder) {
  69. throw new \InvalidArgumentException(
  70. sprintf('Builder "%s" must extend \Magento\Framework\Validator\Builder', $builderClass)
  71. );
  72. }
  73. if ($builderConfig) {
  74. $builder->addConfigurations($builderConfig);
  75. }
  76. return $builder;
  77. }
  78. /**
  79. * Create validator based on entity and group.
  80. *
  81. * @param string $entityName
  82. * @param string $groupName
  83. * @param array|null $builderConfig
  84. * @return \Magento\Framework\Validator
  85. */
  86. public function createValidator($entityName, $groupName, array $builderConfig = null)
  87. {
  88. return $this->createValidatorBuilder($entityName, $groupName, $builderConfig)->createValidator();
  89. }
  90. /**
  91. * Extract configuration data from the DOM structure
  92. *
  93. * @param \DOMDocument $dom
  94. * @return array
  95. */
  96. protected function _extractData(\DOMDocument $dom)
  97. {
  98. $result = [];
  99. /** @var \DOMElement $entity */
  100. foreach ($dom->getElementsByTagName('entity') as $entity) {
  101. $result[$entity->getAttribute('name')] = $this->_extractEntityGroupsConstraintsData($entity);
  102. }
  103. return $result;
  104. }
  105. /**
  106. * Extract constraints associated with entity group using rules
  107. *
  108. * @param \DOMElement $entity
  109. * @return array
  110. */
  111. protected function _extractEntityGroupsConstraintsData(\DOMElement $entity)
  112. {
  113. $result = [];
  114. $rulesConstraints = $this->_extractRulesConstraintsData($entity);
  115. /** @var \DOMElement $group */
  116. foreach ($entity->getElementsByTagName('group') as $group) {
  117. $groupConstraints = [];
  118. /** @var \DOMElement $use */
  119. foreach ($group->getElementsByTagName('use') as $use) {
  120. $ruleName = $use->getAttribute('rule');
  121. if (isset($rulesConstraints[$ruleName])) {
  122. $groupConstraints = array_merge($groupConstraints, $rulesConstraints[$ruleName]);
  123. }
  124. }
  125. $result[$group->getAttribute('name')] = ['constraints' => $groupConstraints];
  126. if ($group->hasAttribute('builder')) {
  127. $result[$group->getAttribute('name')]['builder'] = $group->getAttribute('builder');
  128. }
  129. }
  130. unset($groupConstraints);
  131. unset($rulesConstraints);
  132. return $result;
  133. }
  134. /**
  135. * Extract constraints associated with rules
  136. *
  137. * @param \DOMElement $entity
  138. * @return array
  139. */
  140. protected function _extractRulesConstraintsData(\DOMElement $entity)
  141. {
  142. $rules = [];
  143. /** @var \DOMElement $rule */
  144. foreach ($entity->getElementsByTagName('rule') as $rule) {
  145. $ruleName = $rule->getAttribute('name');
  146. /** @var \DOMElement $propertyConstraints */
  147. foreach ($rule->getElementsByTagName('property_constraints') as $propertyConstraints) {
  148. /** @var \DOMElement $property */
  149. foreach ($propertyConstraints->getElementsByTagName('property') as $property) {
  150. /** @var \DOMElement $constraint */
  151. foreach ($property->getElementsByTagName('constraint') as $constraint) {
  152. $rules[$ruleName][] = [
  153. 'alias' => $constraint->getAttribute('alias'),
  154. 'class' => $constraint->getAttribute('class'),
  155. 'options' => $this->_extractConstraintOptions($constraint),
  156. 'property' => $property->getAttribute('name'),
  157. 'type' => self::CONSTRAINT_TYPE_PROPERTY,
  158. ];
  159. }
  160. }
  161. }
  162. /** @var \DOMElement $entityConstraints */
  163. foreach ($rule->getElementsByTagName('entity_constraints') as $entityConstraints) {
  164. /** @var \DOMElement $constraint */
  165. foreach ($entityConstraints->getElementsByTagName('constraint') as $constraint) {
  166. $rules[$ruleName][] = [
  167. 'alias' => $constraint->getAttribute('alias'),
  168. 'class' => $constraint->getAttribute('class'),
  169. 'options' => $this->_extractConstraintOptions($constraint),
  170. 'type' => self::CONSTRAINT_TYPE_ENTITY,
  171. ];
  172. }
  173. }
  174. }
  175. return $rules;
  176. }
  177. /**
  178. * Extract constraint options.
  179. *
  180. * @param \DOMElement $constraint
  181. * @return array|null
  182. */
  183. protected function _extractConstraintOptions(\DOMElement $constraint)
  184. {
  185. if (!$constraint->hasChildNodes()) {
  186. return null;
  187. }
  188. $options = [];
  189. $children = $this->_collectChildren($constraint);
  190. /**
  191. * Read constructor arguments
  192. *
  193. * <constraint class="Constraint">
  194. * <argument>
  195. * <option name="minValue">123</option>
  196. * <option name="maxValue">234</option>
  197. * </argument>
  198. * <argument>0</argument>
  199. * <argument>
  200. * <callback class="Class" method="method" />
  201. * </argument>
  202. * </constraint>
  203. */
  204. $arguments = $this->_readArguments($children);
  205. if ($arguments) {
  206. $options['arguments'] = $arguments;
  207. }
  208. /**
  209. * Read constraint configurator callback
  210. *
  211. * <constraint class="Constraint">
  212. * <callback class="Magento\Foo\Helper\Data" method="configureValidator"/>
  213. * </constraint>
  214. */
  215. $callback = $this->_readCallback($children);
  216. if ($callback) {
  217. $options['callback'] = $callback;
  218. }
  219. /**
  220. * Read constraint method configuration
  221. */
  222. $methods = $this->_readMethods($children);
  223. if ($methods) {
  224. $options['methods'] = $methods;
  225. }
  226. return $options;
  227. }
  228. /**
  229. * Get element children.
  230. *
  231. * @param \DOMElement $element
  232. * @return array
  233. */
  234. protected function _collectChildren($element)
  235. {
  236. $children = [];
  237. /** @var $node \DOMElement */
  238. foreach ($element->childNodes as $node) {
  239. if (!$node instanceof \DOMElement) {
  240. continue;
  241. }
  242. $nodeName = strtolower($node->nodeName);
  243. if (!array_key_exists($nodeName, $children)) {
  244. $children[$nodeName] = [];
  245. }
  246. $children[$nodeName][] = $node;
  247. }
  248. return $children;
  249. }
  250. /**
  251. * Get arguments.
  252. *
  253. * @param array $children
  254. * @return OptionInterface[]|null
  255. */
  256. protected function _readArguments($children)
  257. {
  258. if (array_key_exists('argument', $children)) {
  259. $arguments = [];
  260. /** @var $node \DOMElement */
  261. foreach ($children['argument'] as $node) {
  262. $nodeChildren = $this->_collectChildren($node);
  263. $callback = $this->_readCallback($nodeChildren);
  264. $options = $this->_readOptions($nodeChildren);
  265. if ($callback) {
  266. $arguments[] = $callback[0];
  267. } elseif ($options) {
  268. $arguments[] = $options;
  269. } else {
  270. $argument = $node->textContent;
  271. $arguments[] = new Option(trim($argument));
  272. }
  273. }
  274. return $arguments;
  275. }
  276. return null;
  277. }
  278. /**
  279. * Get callback rules.
  280. *
  281. * @param array $children
  282. * @return Callback[]|null
  283. */
  284. protected function _readCallback($children)
  285. {
  286. if (array_key_exists('callback', $children)) {
  287. $callbacks = [];
  288. /** @var $callbackData \DOMElement */
  289. foreach ($children['callback'] as $callbackData) {
  290. $callbacks[] = new Callback(
  291. [trim($callbackData->getAttribute('class')), trim($callbackData->getAttribute('method'))],
  292. null,
  293. true
  294. );
  295. }
  296. return $callbacks;
  297. }
  298. return null;
  299. }
  300. /**
  301. * Get options array.
  302. *
  303. * @param array $children
  304. * @return Option|null
  305. */
  306. protected function _readOptions($children)
  307. {
  308. if (array_key_exists('option', $children)) {
  309. $data = [];
  310. /** @var $option \DOMElement */
  311. foreach ($children['option'] as $option) {
  312. $value = trim($option->textContent);
  313. if ($option->hasAttribute('name')) {
  314. $data[$option->getAttribute('name')] = $value;
  315. } else {
  316. $data[] = $value;
  317. }
  318. }
  319. return new Option($data);
  320. }
  321. return null;
  322. }
  323. /**
  324. * Get methods configuration.
  325. *
  326. * Example of method configuration:
  327. * <constraint class="Constraint">
  328. * <method name="setMaxValue">
  329. * <argument>
  330. * <option name="minValue">123</option>
  331. * <option name="maxValue">234</option>
  332. * </argument>
  333. * <argument>0</argument>
  334. * <argument>
  335. * <callback class="Class" method="method" />
  336. * </argument>
  337. * </method>
  338. * </constraint>
  339. *
  340. * @param array $children
  341. * @return array|null
  342. */
  343. protected function _readMethods($children)
  344. {
  345. if (array_key_exists('method', $children)) {
  346. $methods = [];
  347. /** @var $method \DOMElement */
  348. foreach ($children['method'] as $method) {
  349. $children = $this->_collectChildren($method);
  350. $methodName = $method->getAttribute('name');
  351. $methodOptions = ['method' => $methodName];
  352. $arguments = $this->_readArguments($children);
  353. if ($arguments) {
  354. $methodOptions['arguments'] = $arguments;
  355. }
  356. $methods[$methodName] = $methodOptions;
  357. }
  358. return $methods;
  359. }
  360. return null;
  361. }
  362. /**
  363. * Get absolute path to validation.xsd
  364. *
  365. * @return string
  366. */
  367. public function getSchemaFile()
  368. {
  369. return __DIR__ . '/etc/validation.xsd';
  370. }
  371. /**
  372. * Get initial XML of a valid document.
  373. *
  374. * @return string
  375. */
  376. protected function _getInitialXml()
  377. {
  378. return '<?xml version="1.0" encoding="UTF-8"?>' .
  379. '<validation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></validation>';
  380. }
  381. /**
  382. * Define id attributes for entities
  383. *
  384. * @return array
  385. */
  386. protected function _getIdAttributes()
  387. {
  388. return [
  389. '/validation/entity' => 'name',
  390. '/validation/entity/rules/rule' => 'name',
  391. '/validation/entity/rules/rule/entity_constraints/constraint' => 'class',
  392. '/validation/entity/rules/rule/property_constraints/property/constraint' => 'class',
  393. '/validation/entity/rules/rule/property_constraints/property' => 'name',
  394. '/validation/entity/groups/group' => 'name',
  395. '/validation/entity/groups/group/uses/use' => 'rule'
  396. ];
  397. }
  398. }