PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ORM/RulesChecker.php

https://github.com/ceeram/cakephp
PHP | 394 lines | 128 code | 31 blank | 235 comment | 17 complexity | b89c7b2146d42b4c9dab4c77e83360b0 MD5 | raw file
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\ORM;
  16. use Cake\Datasource\EntityInterface;
  17. use Cake\ORM\Rule\ExistsIn;
  18. use Cake\ORM\Rule\IsUnique;
  19. use InvalidArgumentException;
  20. /**
  21. * Contains logic for storing and checking rules on entities
  22. *
  23. * RulesCheckers are used by Table classes to ensure that the
  24. * current entity state satisfies the application logic and business rules.
  25. *
  26. * RulesCheckers afford different rules to be applied in the create and update
  27. * scenario.
  28. *
  29. * ### Adding rules
  30. *
  31. * Rules must be callable objects that return true/false depending on whether or
  32. * not the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(),
  33. * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker.
  34. *
  35. * ### Running checks
  36. *
  37. * Generally a Table object will invoke the rules objects, but you can manually
  38. * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or
  39. * RulesChecker::checkDelete().
  40. */
  41. class RulesChecker
  42. {
  43. /**
  44. * Indicates that the checking rules to apply are those used for creating entities
  45. *
  46. * @var string
  47. */
  48. const CREATE = 'create';
  49. /**
  50. * Indicates that the checking rules to apply are those used for updating entities
  51. *
  52. * @var string
  53. */
  54. const UPDATE = 'update';
  55. /**
  56. * Indicates that the checking rules to apply are those used for deleting entities
  57. *
  58. * @var string
  59. */
  60. const DELETE = 'delete';
  61. /**
  62. * The list of rules to be checked on both create and update operations
  63. *
  64. * @var array
  65. */
  66. protected $_rules = [];
  67. /**
  68. * The list of rules to check during create operations
  69. *
  70. * @var array
  71. */
  72. protected $_createRules = [];
  73. /**
  74. * The list of rules to check during update operations
  75. *
  76. * @var array
  77. */
  78. protected $_updateRules = [];
  79. /**
  80. * The list of rules to check during delete operations
  81. *
  82. * @var array
  83. */
  84. protected $_deleteRules = [];
  85. /**
  86. * List of options to pass to every callable rule
  87. *
  88. * @var array
  89. */
  90. protected $_options = [];
  91. /**
  92. * Whether or not to use I18n functions for translating default error messages
  93. *
  94. * @var bool
  95. */
  96. protected $_useI18n = false;
  97. /**
  98. * Constructor. Takes the options to be passed to all rules.
  99. *
  100. * @param array $options The options to pass to every rule
  101. */
  102. public function __construct(array $options)
  103. {
  104. $this->_options = $options;
  105. $this->_useI18n = function_exists('__d');
  106. }
  107. /**
  108. * Adds a rule that will be applied to the entity both on create and update
  109. * operations.
  110. *
  111. * ### Options
  112. *
  113. * The options array accept the following special keys:
  114. *
  115. * - `errorField`: The name of the entity field that will be marked as invalid
  116. * if the rule does not pass.
  117. * - `message`: The error message to set to `errorField` if the rule does not pass.
  118. *
  119. * @param callable $rule A callable function or object that will return whether
  120. * the entity is valid or not.
  121. * @param string $name The alias for a rule.
  122. * @param array $options List of extra options to pass to the rule callable as
  123. * second argument.
  124. * @return $this
  125. */
  126. public function add(callable $rule, $name = null, array $options = [])
  127. {
  128. $this->_rules[] = $this->_addError($rule, $name, $options);
  129. return $this;
  130. }
  131. /**
  132. * Adds a rule that will be applied to the entity on create operations.
  133. *
  134. * ### Options
  135. *
  136. * The options array accept the following special keys:
  137. *
  138. * - `errorField`: The name of the entity field that will be marked as invalid
  139. * if the rule does not pass.
  140. * - `message`: The error message to set to `errorField` if the rule does not pass.
  141. *
  142. * @param callable $rule A callable function or object that will return whether
  143. * the entity is valid or not.
  144. * @param string $name The alias for a rule.
  145. * @param array $options List of extra options to pass to the rule callable as
  146. * second argument.
  147. * @return $this
  148. */
  149. public function addCreate(callable $rule, $name = null, array $options = [])
  150. {
  151. $this->_createRules[] = $this->_addError($rule, $name, $options);
  152. return $this;
  153. }
  154. /**
  155. * Adds a rule that will be applied to the entity on update operations.
  156. *
  157. * ### Options
  158. *
  159. * The options array accept the following special keys:
  160. *
  161. * - `errorField`: The name of the entity field that will be marked as invalid
  162. * if the rule does not pass.
  163. * - `message`: The error message to set to `errorField` if the rule does not pass.
  164. *
  165. * @param callable $rule A callable function or object that will return whether
  166. * the entity is valid or not.
  167. * @param string $name The alias for a rule.
  168. * @param array $options List of extra options to pass to the rule callable as
  169. * second argument.
  170. * @return $this
  171. */
  172. public function addUpdate(callable $rule, $name = null, array $options = [])
  173. {
  174. $this->_updateRules[] = $this->_addError($rule, $name, $options);
  175. return $this;
  176. }
  177. /**
  178. * Adds a rule that will be applied to the entity on delete operations.
  179. *
  180. * ### Options
  181. *
  182. * The options array accept the following special keys:
  183. *
  184. * - `errorField`: The name of the entity field that will be marked as invalid
  185. * if the rule does not pass.
  186. * - `message`: The error message to set to `errorField` if the rule does not pass.
  187. *
  188. * @param callable $rule A callable function or object that will return whether
  189. * the entity is valid or not.
  190. * @param string $name The alias for a rule.
  191. * @param array $options List of extra options to pass to the rule callable as
  192. * second argument.
  193. * @return $this
  194. */
  195. public function addDelete(callable $rule, $name = null, array $options = [])
  196. {
  197. $this->_deleteRules[] = $this->_addError($rule, $name, $options);
  198. return $this;
  199. }
  200. /**
  201. * Runs each of the rules by passing the provided entity and returns true if all
  202. * of them pass. The rules to be applied are depended on the $mode parameter which
  203. * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE
  204. *
  205. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  206. * @param string $mode Either 'create, 'update' or 'delete'.
  207. * @param array $options Extra options to pass to checker functions.
  208. * @return bool
  209. * @throws \InvalidArgumentException if an invalid mode is passed.
  210. */
  211. public function check(EntityInterface $entity, $mode, array $options = [])
  212. {
  213. if ($mode === self::CREATE) {
  214. return $this->checkCreate($entity, $options);
  215. }
  216. if ($mode === self::UPDATE) {
  217. return $this->checkUpdate($entity, $options);
  218. }
  219. if ($mode === self::DELETE) {
  220. return $this->checkDelete($entity, $options);
  221. }
  222. throw new InvalidArgumentException('Wrong checking mode: ' . $mode);
  223. }
  224. /**
  225. * Runs each of the rules by passing the provided entity and returns true if all
  226. * of them pass. The rules selected will be only those specified to be run on 'create'
  227. *
  228. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  229. * @param array $options Extra options to pass to checker functions.
  230. * @return bool
  231. */
  232. public function checkCreate(EntityInterface $entity, array $options = [])
  233. {
  234. $success = true;
  235. $options = $options + $this->_options;
  236. foreach (array_merge($this->_rules, $this->_createRules) as $rule) {
  237. $success = $rule($entity, $options) && $success;
  238. }
  239. return $success;
  240. }
  241. /**
  242. * Runs each of the rules by passing the provided entity and returns true if all
  243. * of them pass. The rules selected will be only those specified to be run on 'update'
  244. *
  245. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  246. * @param array $options Extra options to pass to checker functions.
  247. * @return bool
  248. */
  249. public function checkUpdate(EntityInterface $entity, array $options = [])
  250. {
  251. $success = true;
  252. $options = $options + $this->_options;
  253. foreach (array_merge($this->_rules, $this->_updateRules) as $rule) {
  254. $success = $rule($entity, $options) && $success;
  255. }
  256. return $success;
  257. }
  258. /**
  259. * Runs each of the rules by passing the provided entity and returns true if all
  260. * of them pass. The rules selected will be only those specified to be run on 'delete'
  261. *
  262. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  263. * @param array $options Extra options to pass to checker functions.
  264. * @return bool
  265. */
  266. public function checkDelete(EntityInterface $entity, array $options = [])
  267. {
  268. $success = true;
  269. $options = $options + $this->_options;
  270. foreach ($this->_deleteRules as $rule) {
  271. $success = $rule($entity, $options) && $success;
  272. }
  273. return $success;
  274. }
  275. /**
  276. * Returns a callable that can be used as a rule for checking the uniqueness of a value
  277. * in the table.
  278. *
  279. * ### Example:
  280. *
  281. * ```
  282. * $rules->add($rules->isUnique(['email'], 'The email should be unique'));
  283. * ```
  284. *
  285. * @param array $fields The list of fields to check for uniqueness.
  286. * @param string $message The error message to show in case the rule does not pass.
  287. * @return callable
  288. */
  289. public function isUnique(array $fields, $message = null)
  290. {
  291. if (!$message) {
  292. if ($this->_useI18n) {
  293. $message = __d('cake', 'This value is already in use');
  294. } else {
  295. $message = 'This value is already in use';
  296. }
  297. }
  298. $errorField = current($fields);
  299. return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message'));
  300. }
  301. /**
  302. * Returns a callable that can be used as a rule for checking that the values
  303. * extracted from the entity to check exist as the primary key in another table.
  304. *
  305. * This is useful for enforcing foreign key integrity checks.
  306. *
  307. * ### Example:
  308. *
  309. * ```
  310. * $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author'));
  311. *
  312. * $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site'));
  313. * ```
  314. *
  315. * @param string|array $field The field or list of fields to check for existence by
  316. * primary key lookup in the other table.
  317. * @param object|string $table The table name where the fields existence will be checked.
  318. * @param string $message The error message to show in case the rule does not pass.
  319. * @return callable
  320. */
  321. public function existsIn($field, $table, $message = null)
  322. {
  323. if (!$message) {
  324. if ($this->_useI18n) {
  325. $message = __d('cake', 'This value does not exist');
  326. } else {
  327. $message = 'This value does not exist';
  328. }
  329. }
  330. $errorField = $field;
  331. return $this->_addError(new ExistsIn($field, $table), '_existsIn', compact('errorField', 'message'));
  332. }
  333. /**
  334. * Utility method for decorating any callable so that if it returns false, the correct
  335. * property in the entity is marked as invalid.
  336. *
  337. * @param callable $rule The rule to decorate
  338. * @param string $name The alias for a rule.
  339. * @param array $options The options containing the error message and field
  340. * @return callable
  341. */
  342. protected function _addError($rule, $name, $options)
  343. {
  344. if (is_array($name)) {
  345. $options = $name;
  346. $name = null;
  347. }
  348. return function ($entity, $scope) use ($rule, $name, $options) {
  349. $pass = $rule($entity, $options + $scope);
  350. if ($pass || empty($options['errorField'])) {
  351. return $pass;
  352. }
  353. $message = isset($options['message']) ? $options['message'] : 'invalid';
  354. if ($name) {
  355. $message = [$name => $message];
  356. } else {
  357. $message = (array)$message;
  358. }
  359. $entity->errors($options['errorField'], $message);
  360. return $pass;
  361. };
  362. }
  363. }