Builder.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. /**
  3. * Magento Validator Builder
  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\OptionInterface;
  10. class Builder
  11. {
  12. /**
  13. * @var array
  14. */
  15. protected $_constraints;
  16. /**
  17. * @var \Magento\Framework\Validator\ConstraintFactory
  18. */
  19. protected $_constraintFactory;
  20. /**
  21. * @var \Magento\Framework\ValidatorFactory
  22. */
  23. protected $_validatorFactory;
  24. /**
  25. * @var \Magento\Framework\Validator\UniversalFactory
  26. */
  27. protected $_oneValidatorFactory;
  28. /**
  29. * @param \Magento\Framework\Validator\ConstraintFactory $constraintFactory
  30. * @param \Magento\Framework\ValidatorFactory $validatorFactory
  31. * @param \Magento\Framework\Validator\UniversalFactory $oneValidatorFactory
  32. * @param array $constraints
  33. */
  34. public function __construct(
  35. \Magento\Framework\Validator\ConstraintFactory $constraintFactory,
  36. \Magento\Framework\ValidatorFactory $validatorFactory,
  37. \Magento\Framework\Validator\UniversalFactory $oneValidatorFactory,
  38. array $constraints
  39. ) {
  40. foreach ($constraints as $constraint) {
  41. if (isset($constraint['options']) && is_array($constraint['options'])) {
  42. $this->_checkConfigurationArguments($constraint['options'], true);
  43. $this->_checkConfigurationCallback($constraint['options'], true);
  44. }
  45. }
  46. $this->_constraints = $constraints;
  47. $this->_constraintFactory = $constraintFactory;
  48. $this->_validatorFactory = $validatorFactory;
  49. $this->_oneValidatorFactory = $oneValidatorFactory;
  50. }
  51. /**
  52. * Check configuration arguments
  53. *
  54. * @param array $configuration
  55. * @param bool $argumentsIsArray
  56. * @return void
  57. * @throws \InvalidArgumentException
  58. */
  59. protected function _checkConfigurationArguments(array $configuration, $argumentsIsArray)
  60. {
  61. // https://jira.corp.x.com/browse/MAGETWO-10439
  62. $allowedKeys = ['arguments', 'callback', 'method', 'methods', 'breakChainOnFailure'];
  63. if (!array_intersect($allowedKeys, array_keys($configuration))) {
  64. throw new \InvalidArgumentException('Configuration has incorrect format');
  65. }
  66. // Check method arguments
  67. if ($argumentsIsArray) {
  68. if (array_key_exists('methods', $configuration)) {
  69. foreach ($configuration['methods'] as $method) {
  70. $this->_checkMethodArguments($method);
  71. }
  72. }
  73. } elseif (array_key_exists('method', $configuration)) {
  74. $this->_checkMethodArguments($configuration);
  75. }
  76. // Check constructor arguments
  77. if (array_key_exists('arguments', $configuration) && !is_array($configuration['arguments'])) {
  78. throw new \InvalidArgumentException('Arguments must be an array');
  79. }
  80. }
  81. /**
  82. * Check configuration method arguments
  83. *
  84. * @param array $configuration
  85. * @return void
  86. * @throws \InvalidArgumentException
  87. */
  88. protected function _checkMethodArguments(array $configuration)
  89. {
  90. if (!is_string($configuration['method'])) {
  91. throw new \InvalidArgumentException('Method has to be passed as string');
  92. }
  93. if (array_key_exists('arguments', $configuration) && !is_array($configuration['arguments'])) {
  94. throw new \InvalidArgumentException('Method arguments must be an array');
  95. }
  96. }
  97. /**
  98. * Check configuration callbacks
  99. *
  100. * @param array $configuration
  101. * @param bool $callbackIsArray
  102. * @return void
  103. * @throws \InvalidArgumentException
  104. */
  105. protected function _checkConfigurationCallback(array $configuration, $callbackIsArray)
  106. {
  107. if (array_key_exists('callback', $configuration)) {
  108. if ($callbackIsArray) {
  109. $callbacks = $configuration['callback'];
  110. } else {
  111. $callbacks = [$configuration['callback']];
  112. }
  113. foreach ($callbacks as $callback) {
  114. if (!$callback instanceof \Magento\Framework\Validator\Constraint\Option\Callback) {
  115. throw new \InvalidArgumentException(
  116. 'Callback must be instance of \Magento\Framework\Validator\Constraint\Option\Callback'
  117. );
  118. }
  119. }
  120. }
  121. }
  122. /**
  123. * Create validator instance and configure it
  124. *
  125. * @return \Magento\Framework\Validator
  126. */
  127. public function createValidator()
  128. {
  129. return $this->_createValidatorInstance();
  130. }
  131. /**
  132. * Get validator instance
  133. *
  134. * @return \Magento\Framework\Validator
  135. */
  136. protected function _createValidatorInstance()
  137. {
  138. $validator = $this->_validatorFactory->create();
  139. foreach ($this->_constraints as $constraintData) {
  140. // https://jira.corp.x.com/browse/MAGETWO-10439
  141. $breakChainOnFailure = !empty($constraintData['options']['breakChainOnFailure']);
  142. $validator->addValidator($this->_createConstraint($constraintData), $breakChainOnFailure);
  143. }
  144. return $validator;
  145. }
  146. /**
  147. * Add constraint configuration
  148. *
  149. * @param string $alias
  150. * @param array $configuration
  151. * @return $this
  152. * @throws \InvalidArgumentException
  153. */
  154. public function addConfiguration($alias, array $configuration)
  155. {
  156. $this->_checkConfigurationArguments($configuration, false);
  157. $this->_checkConfigurationCallback($configuration, false);
  158. foreach ($this->_constraints as &$constraint) {
  159. if ($constraint['alias'] != $alias) {
  160. continue;
  161. }
  162. if (!array_key_exists('options', $constraint) || !is_array($constraint['options'])) {
  163. $constraint['options'] = [];
  164. }
  165. if (!array_key_exists('method', $configuration)) {
  166. if (array_key_exists('arguments', $configuration)) {
  167. $constraint['options']['arguments'] = $configuration['arguments'];
  168. } elseif (array_key_exists('callback', $configuration)) {
  169. $constraint = $this->_addConstraintCallback($constraint, $configuration['callback']);
  170. }
  171. } else {
  172. $constraint = $this->_addConstraintMethod($constraint, $configuration);
  173. }
  174. }
  175. return $this;
  176. }
  177. /**
  178. * Add callback to constraint configuration
  179. *
  180. * @param array $constraint
  181. * @param \Magento\Framework\Validator\Constraint\Option\Callback $callback
  182. * @return array
  183. */
  184. protected function _addConstraintCallback(
  185. array $constraint,
  186. \Magento\Framework\Validator\Constraint\Option\Callback $callback
  187. ) {
  188. if (!array_key_exists('callback', $constraint['options'])) {
  189. $constraint['options']['callback'] = [];
  190. }
  191. $constraint['options']['callback'][] = $callback;
  192. return $constraint;
  193. }
  194. /**
  195. * Add method to constraint configuration
  196. *
  197. * @param array $constraint
  198. * @param array $configuration
  199. * @return array
  200. */
  201. protected function _addConstraintMethod(array $constraint, array $configuration)
  202. {
  203. if (!array_key_exists('methods', $constraint['options'])) {
  204. $constraint['options']['methods'] = [];
  205. }
  206. $constraint['options']['methods'][] = $configuration;
  207. return $constraint;
  208. }
  209. /**
  210. * Add constraints configuration
  211. *
  212. * @param array $configurations
  213. * @return $this
  214. */
  215. public function addConfigurations(array $configurations)
  216. {
  217. foreach ($configurations as $alias => $concreteConfigs) {
  218. foreach ($concreteConfigs as $configuration) {
  219. $this->addConfiguration($alias, $configuration);
  220. }
  221. }
  222. return $this;
  223. }
  224. /**
  225. * Create constraint from data
  226. *
  227. * @param array $data
  228. * @return \Magento\Framework\Validator\Constraint
  229. */
  230. protected function _createConstraint(array $data)
  231. {
  232. // Create validator instance
  233. $validator = $this->_createConstraintValidator($data);
  234. if (isset($data['options']) && is_array($data['options'])) {
  235. $this->_configureConstraintValidator($validator, $data['options']);
  236. }
  237. if (\Magento\Framework\Validator\Config::CONSTRAINT_TYPE_PROPERTY == $data['type']) {
  238. $result = new \Magento\Framework\Validator\Constraint\Property(
  239. $validator,
  240. $data['property'],
  241. $data['alias']
  242. );
  243. } else {
  244. $result = $this->_constraintFactory->create(['validator' => $validator, 'alias' => $data['alias']]);
  245. }
  246. return $result;
  247. }
  248. /**
  249. * Create constraint validator instance
  250. *
  251. * @param array $data
  252. * @return \Magento\Framework\Validator\ValidatorInterface
  253. * @throws \InvalidArgumentException
  254. */
  255. protected function _createConstraintValidator(array $data)
  256. {
  257. $validator = $this->_oneValidatorFactory->create(
  258. $data['class'],
  259. isset(
  260. $data['options']['arguments']
  261. ) ? $this->_applyArgumentsCallback(
  262. $data['options']['arguments']
  263. ) : []
  264. );
  265. // Check validator type
  266. if (!$validator instanceof \Magento\Framework\Validator\ValidatorInterface) {
  267. throw new \InvalidArgumentException(
  268. sprintf(
  269. 'Constraint class "%s" must implement \Magento\Framework\Validator\ValidatorInterface',
  270. $data['class']
  271. )
  272. );
  273. }
  274. return $validator;
  275. }
  276. /**
  277. * Configure validator
  278. *
  279. * @param \Magento\Framework\Validator\ValidatorInterface $validator
  280. * @param array $options
  281. * @return void
  282. */
  283. protected function _configureConstraintValidator(
  284. \Magento\Framework\Validator\ValidatorInterface $validator,
  285. array $options
  286. ) {
  287. // Call all validator methods according to configuration
  288. if (isset($options['methods'])) {
  289. foreach ($options['methods'] as $methodData) {
  290. $methodName = $methodData['method'];
  291. if (method_exists($validator, $methodName)) {
  292. if (array_key_exists('arguments', $methodData)) {
  293. $arguments = $this->_applyArgumentsCallback($methodData['arguments']);
  294. call_user_func_array([$validator, $methodName], $arguments);
  295. } else {
  296. call_user_func([$validator, $methodName]);
  297. }
  298. }
  299. }
  300. }
  301. // Call validator configurators if any
  302. if (isset($options['callback'])) {
  303. /** @var $callback \Magento\Framework\Validator\Constraint\Option\Callback */
  304. foreach ($options['callback'] as $callback) {
  305. $callback->setArguments($validator);
  306. $callback->getValue();
  307. }
  308. }
  309. }
  310. /**
  311. * Apply all argument callback
  312. *
  313. * @param OptionInterface[] $arguments
  314. * @return OptionInterface[]
  315. */
  316. protected function _applyArgumentsCallback(array $arguments)
  317. {
  318. foreach ($arguments as &$argument) {
  319. if (is_array($argument)) {
  320. $argument = $this->_applyArgumentsCallback($argument);
  321. } elseif ($argument instanceof OptionInterface) {
  322. $argument = $argument->getValue();
  323. }
  324. }
  325. return $arguments;
  326. }
  327. }