RulesChecker.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.7
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Datasource;
  16. use InvalidArgumentException;
  17. /**
  18. * Contains logic for storing and checking rules on entities
  19. *
  20. * RulesCheckers are used by Table classes to ensure that the
  21. * current entity state satisfies the application logic and business rules.
  22. *
  23. * RulesCheckers afford different rules to be applied in the create and update
  24. * scenario.
  25. *
  26. * ### Adding rules
  27. *
  28. * Rules must be callable objects that return true/false depending on whether or
  29. * not the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(),
  30. * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker.
  31. *
  32. * ### Running checks
  33. *
  34. * Generally a Table object will invoke the rules objects, but you can manually
  35. * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or
  36. * RulesChecker::checkDelete().
  37. */
  38. class RulesChecker
  39. {
  40. /**
  41. * Indicates that the checking rules to apply are those used for creating entities
  42. *
  43. * @var string
  44. */
  45. const CREATE = 'create';
  46. /**
  47. * Indicates that the checking rules to apply are those used for updating entities
  48. *
  49. * @var string
  50. */
  51. const UPDATE = 'update';
  52. /**
  53. * Indicates that the checking rules to apply are those used for deleting entities
  54. *
  55. * @var string
  56. */
  57. const DELETE = 'delete';
  58. /**
  59. * The list of rules to be checked on both create and update operations
  60. *
  61. * @var callable[]
  62. */
  63. protected $_rules = [];
  64. /**
  65. * The list of rules to check during create operations
  66. *
  67. * @var callable[]
  68. */
  69. protected $_createRules = [];
  70. /**
  71. * The list of rules to check during update operations
  72. *
  73. * @var callable[]
  74. */
  75. protected $_updateRules = [];
  76. /**
  77. * The list of rules to check during delete operations
  78. *
  79. * @var callable[]
  80. */
  81. protected $_deleteRules = [];
  82. /**
  83. * List of options to pass to every callable rule
  84. *
  85. * @var array
  86. */
  87. protected $_options = [];
  88. /**
  89. * Whether or not to use I18n functions for translating default error messages
  90. *
  91. * @var bool
  92. */
  93. protected $_useI18n = false;
  94. /**
  95. * Constructor. Takes the options to be passed to all rules.
  96. *
  97. * @param array $options The options to pass to every rule
  98. */
  99. public function __construct(array $options = [])
  100. {
  101. $this->_options = $options;
  102. $this->_useI18n = function_exists('__d');
  103. }
  104. /**
  105. * Adds a rule that will be applied to the entity both on create and update
  106. * operations.
  107. *
  108. * ### Options
  109. *
  110. * The options array accept the following special keys:
  111. *
  112. * - `errorField`: The name of the entity field that will be marked as invalid
  113. * if the rule does not pass.
  114. * - `message`: The error message to set to `errorField` if the rule does not pass.
  115. *
  116. * @param callable $rule A callable function or object that will return whether
  117. * the entity is valid or not.
  118. * @param string|null $name The alias for a rule.
  119. * @param array $options List of extra options to pass to the rule callable as
  120. * second argument.
  121. * @return $this
  122. */
  123. public function add(callable $rule, $name = null, array $options = [])
  124. {
  125. $this->_rules[] = $this->_addError($rule, $name, $options);
  126. return $this;
  127. }
  128. /**
  129. * Adds a rule that will be applied to the entity on create operations.
  130. *
  131. * ### Options
  132. *
  133. * The options array accept the following special keys:
  134. *
  135. * - `errorField`: The name of the entity field that will be marked as invalid
  136. * if the rule does not pass.
  137. * - `message`: The error message to set to `errorField` if the rule does not pass.
  138. *
  139. * @param callable $rule A callable function or object that will return whether
  140. * the entity is valid or not.
  141. * @param string|null $name The alias for a rule.
  142. * @param array $options List of extra options to pass to the rule callable as
  143. * second argument.
  144. * @return $this
  145. */
  146. public function addCreate(callable $rule, $name = null, array $options = [])
  147. {
  148. $this->_createRules[] = $this->_addError($rule, $name, $options);
  149. return $this;
  150. }
  151. /**
  152. * Adds a rule that will be applied to the entity on update operations.
  153. *
  154. * ### Options
  155. *
  156. * The options array accept the following special keys:
  157. *
  158. * - `errorField`: The name of the entity field that will be marked as invalid
  159. * if the rule does not pass.
  160. * - `message`: The error message to set to `errorField` if the rule does not pass.
  161. *
  162. * @param callable $rule A callable function or object that will return whether
  163. * the entity is valid or not.
  164. * @param string|null $name The alias for a rule.
  165. * @param array $options List of extra options to pass to the rule callable as
  166. * second argument.
  167. * @return $this
  168. */
  169. public function addUpdate(callable $rule, $name = null, array $options = [])
  170. {
  171. $this->_updateRules[] = $this->_addError($rule, $name, $options);
  172. return $this;
  173. }
  174. /**
  175. * Adds a rule that will be applied to the entity on delete operations.
  176. *
  177. * ### Options
  178. *
  179. * The options array accept the following special keys:
  180. *
  181. * - `errorField`: The name of the entity field that will be marked as invalid
  182. * if the rule does not pass.
  183. * - `message`: The error message to set to `errorField` if the rule does not pass.
  184. *
  185. * @param callable $rule A callable function or object that will return whether
  186. * the entity is valid or not.
  187. * @param string|null $name The alias for a rule.
  188. * @param array $options List of extra options to pass to the rule callable as
  189. * second argument.
  190. * @return $this
  191. */
  192. public function addDelete(callable $rule, $name = null, array $options = [])
  193. {
  194. $this->_deleteRules[] = $this->_addError($rule, $name, $options);
  195. return $this;
  196. }
  197. /**
  198. * Runs each of the rules by passing the provided entity and returns true if all
  199. * of them pass. The rules to be applied are depended on the $mode parameter which
  200. * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE
  201. *
  202. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  203. * @param string $mode Either 'create, 'update' or 'delete'.
  204. * @param array $options Extra options to pass to checker functions.
  205. * @return bool
  206. * @throws \InvalidArgumentException if an invalid mode is passed.
  207. */
  208. public function check(EntityInterface $entity, $mode, array $options = [])
  209. {
  210. if ($mode === self::CREATE) {
  211. return $this->checkCreate($entity, $options);
  212. }
  213. if ($mode === self::UPDATE) {
  214. return $this->checkUpdate($entity, $options);
  215. }
  216. if ($mode === self::DELETE) {
  217. return $this->checkDelete($entity, $options);
  218. }
  219. throw new InvalidArgumentException('Wrong checking mode: ' . $mode);
  220. }
  221. /**
  222. * Runs each of the rules by passing the provided entity and returns true if all
  223. * of them pass. The rules selected will be only those specified to be run on 'create'
  224. *
  225. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  226. * @param array $options Extra options to pass to checker functions.
  227. * @return bool
  228. */
  229. public function checkCreate(EntityInterface $entity, array $options = [])
  230. {
  231. return $this->_checkRules($entity, $options, array_merge($this->_rules, $this->_createRules));
  232. }
  233. /**
  234. * Runs each of the rules by passing the provided entity and returns true if all
  235. * of them pass. The rules selected will be only those specified to be run on 'update'
  236. *
  237. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  238. * @param array $options Extra options to pass to checker functions.
  239. * @return bool
  240. */
  241. public function checkUpdate(EntityInterface $entity, array $options = [])
  242. {
  243. return $this->_checkRules($entity, $options, array_merge($this->_rules, $this->_updateRules));
  244. }
  245. /**
  246. * Runs each of the rules by passing the provided entity and returns true if all
  247. * of them pass. The rules selected will be only those specified to be run on 'delete'
  248. *
  249. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  250. * @param array $options Extra options to pass to checker functions.
  251. * @return bool
  252. */
  253. public function checkDelete(EntityInterface $entity, array $options = [])
  254. {
  255. return $this->_checkRules($entity, $options, $this->_deleteRules);
  256. }
  257. /**
  258. * Used by top level functions checkDelete, checkCreate and checkUpdate, this function
  259. * iterates an array containing the rules to be checked and checks them all.
  260. *
  261. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  262. * @param array $options Extra options to pass to checker functions.
  263. * @param array $rules The list of rules that must be checked.
  264. * @return bool
  265. */
  266. protected function _checkRules(EntityInterface $entity, array $options = [], array $rules = [])
  267. {
  268. $success = true;
  269. $options += $this->_options;
  270. foreach ($rules as $rule) {
  271. $success = $rule($entity, $options) && $success;
  272. }
  273. return $success;
  274. }
  275. /**
  276. * Utility method for decorating any callable so that if it returns false, the correct
  277. * property in the entity is marked as invalid.
  278. *
  279. * @param callable $rule The rule to decorate
  280. * @param string $name The alias for a rule.
  281. * @param array $options The options containing the error message and field.
  282. * @return callable
  283. */
  284. protected function _addError($rule, $name, $options)
  285. {
  286. if (is_array($name)) {
  287. $options = $name;
  288. $name = null;
  289. }
  290. if (!($rule instanceof RuleInvoker)) {
  291. $rule = new RuleInvoker($rule, $name, $options);
  292. } else {
  293. $rule->setOptions($options)->setName($name);
  294. }
  295. return $rule;
  296. }
  297. }