ValidationSet.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <?php
  2. /**
  3. * ValidationSet.
  4. *
  5. * Provides the Model validation logic.
  6. *
  7. * PHP 5
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * For full copyright and license information, please see the LICENSE.txt
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  17. * @link http://cakephp.org CakePHP(tm) Project
  18. * @since CakePHP(tm) v 2.2.0
  19. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  20. */
  21. namespace Cake\Model\Validator;
  22. /**
  23. * ValidationSet object. Holds all validation rules for a field and exposes
  24. * methods to dynamically add or remove validation rules
  25. *
  26. * @link http://book.cakephp.org/2.0/en/data-validation.html
  27. */
  28. class ValidationSet implements \ArrayAccess, \IteratorAggregate, \Countable {
  29. /**
  30. * Holds the ValidationRule objects
  31. *
  32. * @var array
  33. */
  34. protected $_rules = array();
  35. /**
  36. * List of methods available for validation
  37. *
  38. * @var array
  39. */
  40. protected $_methods = array();
  41. /**
  42. * I18n domain for validation messages.
  43. *
  44. * @var string
  45. */
  46. protected $_validationDomain = null;
  47. /**
  48. * Whether the validation is stopped
  49. *
  50. * @var boolean
  51. */
  52. public $isStopped = false;
  53. /**
  54. * Holds the fieldname
  55. *
  56. * @var string
  57. */
  58. public $field = null;
  59. /**
  60. * Holds the original ruleSet
  61. *
  62. * @var array
  63. */
  64. public $ruleSet = array();
  65. /**
  66. * Denotes whether the fieldname key must be present in data array
  67. *
  68. * @var boolean|string
  69. */
  70. protected $_validatePresent = false;
  71. /**
  72. * Denotes if a field is allowed to be empty
  73. *
  74. * @var boolean|string
  75. */
  76. protected $_allowEmpty = false;
  77. /**
  78. * Holds whether the record being validated is to be created or updated
  79. *
  80. * @var boolean
  81. */
  82. protected $_isUpdate = false;
  83. /**
  84. * Constructor
  85. *
  86. * @param string $fieldName The fieldname
  87. * @param array $ruleset
  88. */
  89. public function __construct($fieldName, $ruleSet) {
  90. $this->field = $fieldName;
  91. if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
  92. $ruleSet = array($ruleSet);
  93. }
  94. foreach ($ruleSet as $index => $validateProp) {
  95. if (in_array($index, array('_validatePresent', '_allowEmpty'), true)) {
  96. $this->{$index} = $validateProp;
  97. } else {
  98. $this->_rules[$index] = new ValidationRule($validateProp);
  99. }
  100. }
  101. $this->ruleSet = $ruleSet;
  102. }
  103. /**
  104. * Sets the list of methods to use for validation
  105. *
  106. * @param array $methods Methods list
  107. * @return void
  108. */
  109. public function setMethods($methods) {
  110. $this->_methods = $methods;
  111. }
  112. /**
  113. * Sets the I18n domain for validation messages.
  114. *
  115. * If no argument is passed the currently set domain will be returned.
  116. *
  117. * @param string $validationDomain The validation domain to be used.
  118. * @return string
  119. */
  120. public function validationDomain($validationDomain = null) {
  121. if ($validationDomain === null) {
  122. return $this->_validationDomain;
  123. }
  124. return $this->_validationDomain = $validationDomain;
  125. }
  126. /**
  127. * Sets whether a field is required to be present in data array.
  128. *
  129. * If no argument is passed the currently set `validatePresent` value will be returned.
  130. *
  131. * @param boolean|string $validatePresent Valid values are true, false, 'create', 'update'
  132. * @return boolean|string
  133. */
  134. public function validatePresent($validatePresent = null) {
  135. if ($validatePresent === null) {
  136. return $this->_validatePresent;
  137. }
  138. return $this->_validatePresent = $validatePresent;
  139. }
  140. /**
  141. * Sets whether a field value is allowed to be empty
  142. *
  143. * If no argument is passed the currently set `allowEmpty` value will be returned.
  144. *
  145. * @param boolean|string $allowEmpty Valid values are true, false, 'create', 'update'
  146. * @return boolean|string
  147. */
  148. public function allowEmpty($allowEmpty = null) {
  149. if ($allowEmpty === null) {
  150. return $this->_allowEmpty;
  151. }
  152. return $this->_allowEmpty = $allowEmpty;
  153. }
  154. /**
  155. * Sets the isUpdate configuration value for this ruleset,
  156. * it refers to wheter the model record it is validating exists
  157. * in the collection or not (create or update operation)
  158. *
  159. * If called with no parameters it will return whether this ruleset
  160. * is configured for update operations or not.
  161. *
  162. * @return boolean
  163. */
  164. public function isUpdate($isUpdate = null) {
  165. if ($isUpdate === null) {
  166. return $this->_isUpdate;
  167. }
  168. foreach ($this->getRules() as $rule) {
  169. $rule->isUpdate($isUpdate);
  170. }
  171. return $this->_isUpdate = $isUpdate;
  172. }
  173. /**
  174. * Runs all validation rules in this set and returns a list of
  175. * validation errors
  176. *
  177. * @param array $data Data array to validate
  178. * @param boolean $isUpdate Is record being updated or created
  179. * @return array list of validation errors for this field
  180. */
  181. public function validate($data, $isUpdate = false) {
  182. $this->reset();
  183. $this->_isUpdate = $isUpdate;
  184. if ($this->checkValidatePresent($this->field, $data)) {
  185. return array(__d('cake', 'This field must exist in data'));
  186. }
  187. if (!array_key_exists($this->field, $data)) {
  188. return array();
  189. }
  190. $errors = array();
  191. $checkEmpty = $this->checkEmpty($this->field, $data);
  192. foreach ($this->getRules() as $name => $rule) {
  193. if ($rule->skip()) {
  194. continue;
  195. }
  196. if ($checkEmpty) {
  197. break;
  198. }
  199. $rule->process($this->field, $data, $this->_methods);
  200. if (!$rule->isValid()) {
  201. $errors[] = $this->_processValidationResponse($name, $rule);
  202. if ($rule->isLast()) {
  203. break;
  204. }
  205. }
  206. }
  207. return $errors;
  208. }
  209. /**
  210. * Returns whether the field can be left blank according to `allowEmpty`
  211. *
  212. * @return boolean
  213. */
  214. public function isEmptyAllowed() {
  215. if (in_array($this->_allowEmpty, array('create', 'update'), true)) {
  216. return (
  217. ($this->_allowEmpty === 'create' && !$this->_isUpdate) ||
  218. ($this->_allowEmpty === 'update' && $this->_isUpdate)
  219. );
  220. }
  221. return $this->_allowEmpty;
  222. }
  223. /**
  224. * Checks if `validatePresent` property applies
  225. *
  226. * @param string $field Field to check
  227. * @param array $data data to check against
  228. * @return boolean
  229. */
  230. public function checkValidatePresent($field, $data) {
  231. if (array_key_exists($field, $data)) {
  232. return false;
  233. }
  234. if (in_array($this->_validatePresent, array('create', 'update'), true)) {
  235. return (
  236. ($this->_validatePresent === 'create' && !$this->_isUpdate) ||
  237. ($this->_validatePresent === 'update' && $this->_isUpdate)
  238. );
  239. }
  240. return $this->_validatePresent;
  241. }
  242. /**
  243. * Checks if the `allowEmpty` property applies
  244. *
  245. * @param string $field Field to check
  246. * @param array $data data to check against
  247. * @return boolean
  248. */
  249. public function checkEmpty($field, $data) {
  250. if (!array_key_exists($field, $data)) {
  251. return false;
  252. }
  253. if (empty($data[$field]) && $data[$field] != '0' && $this->isEmptyAllowed()) {
  254. return true;
  255. }
  256. return false;
  257. }
  258. /**
  259. * Resets internal state for all validation rules in this set
  260. *
  261. * @return void
  262. */
  263. public function reset() {
  264. foreach ($this->getRules() as $rule) {
  265. $rule->reset();
  266. }
  267. }
  268. /**
  269. * Gets a rule for a given name if exists
  270. *
  271. * @param string $name
  272. * @return Cake\Model\Validator\ValidationRule
  273. */
  274. public function getRule($name) {
  275. if (!empty($this->_rules[$name])) {
  276. return $this->_rules[$name];
  277. }
  278. }
  279. /**
  280. * Returns all rules for this validation set
  281. *
  282. * @return array
  283. */
  284. public function getRules() {
  285. return $this->_rules;
  286. }
  287. /**
  288. * Sets a ValidationRule $rule with a $name
  289. *
  290. * ## Example:
  291. *
  292. * {{{
  293. * $set
  294. * ->setRule('notEmpty', array('rule' => 'notEmpty'))
  295. * ->setRule('inRange', array('rule' => array('between', 4, 10))
  296. * }}}
  297. *
  298. * @param string $name The name under which the rule should be set
  299. * @param Cake\Model\Validator\ValidationRule|array $rule The validation rule to be set
  300. * @return Cake\Model\Validator\ValidationSet this instance
  301. */
  302. public function setRule($name, $rule) {
  303. if (!($rule instanceof ValidationRule)) {
  304. $rule = new ValidationRule($rule);
  305. }
  306. $this->_rules[$name] = $rule;
  307. return $this;
  308. }
  309. /**
  310. * Removes a validation rule from the set
  311. *
  312. * ## Example:
  313. *
  314. * {{{
  315. * $set
  316. * ->removeRule('notEmpty')
  317. * ->removeRule('inRange')
  318. * }}}
  319. *
  320. * @param string $name The name under which the rule should be unset
  321. * @return Cake\Model\Validator\ValidationSet this instance
  322. */
  323. public function removeRule($name) {
  324. unset($this->_rules[$name]);
  325. return $this;
  326. }
  327. /**
  328. * Sets the rules for a given field
  329. *
  330. * ## Example:
  331. *
  332. * {{{
  333. * $set->setRules(array(
  334. * 'notEmpty' => array('rule' => 'notEmpty'),
  335. * 'inRange' => array('rule' => array('between', 4, 10)
  336. * ));
  337. * }}}
  338. *
  339. * @param array $rules The rules to be set
  340. * @param boolean $mergeVars [optional] If true, merges vars instead of replace. Defaults to true.
  341. * @return ModelField
  342. */
  343. public function setRules($rules = array(), $mergeVars = true) {
  344. if ($mergeVars === false) {
  345. $this->_rules = array();
  346. }
  347. foreach ($rules as $name => $rule) {
  348. $this->setRule($name, $rule);
  349. }
  350. return $this;
  351. }
  352. /**
  353. * Fetches the correct error message for a failed validation
  354. *
  355. * @param string $name the name of the rule as it was configured
  356. * @param Cake\Model\Validator\ValidationRule $rule the object containing validation information
  357. * @return string
  358. */
  359. protected function _processValidationResponse($name, $rule) {
  360. $message = $rule->getValidationResult();
  361. if (is_string($message)) {
  362. return $message;
  363. }
  364. $message = $rule->message;
  365. if ($message !== null) {
  366. $args = null;
  367. if (is_array($message)) {
  368. $result = $message[0];
  369. $args = array_slice($message, 1);
  370. } else {
  371. $result = $message;
  372. }
  373. if (is_array($rule->rule) && $args === null) {
  374. $args = array_slice($rule->rule, 1);
  375. }
  376. $args = $this->_translateArgs($args);
  377. $message = __d($this->_validationDomain, $result, $args);
  378. } elseif (is_string($name)) {
  379. if (is_array($rule->rule)) {
  380. $args = array_slice($rule->rule, 1);
  381. $args = $this->_translateArgs($args);
  382. $message = __d($this->_validationDomain, $name, $args);
  383. } else {
  384. $message = __d($this->_validationDomain, $name);
  385. }
  386. } else {
  387. $message = __d('cake', 'The provided value is invalid');
  388. }
  389. return $message;
  390. }
  391. /**
  392. * Applies translations to validator arguments.
  393. *
  394. * @param array $args The args to translate
  395. * @return array Translated args.
  396. */
  397. protected function _translateArgs($args) {
  398. foreach ((array)$args as $k => $arg) {
  399. if (is_string($arg)) {
  400. $args[$k] = __d($this->_validationDomain, $arg);
  401. }
  402. }
  403. return $args;
  404. }
  405. /**
  406. * Returns whether an index exists in the rule set
  407. *
  408. * @param string $index name of the rule
  409. * @return boolean
  410. */
  411. public function offsetExists($index) {
  412. return isset($this->_rules[$index]);
  413. }
  414. /**
  415. * Returns a rule object by its index
  416. *
  417. * @param string $index name of the rule
  418. * @return Cake\Model\Validator\ValidationRule
  419. */
  420. public function offsetGet($index) {
  421. return $this->_rules[$index];
  422. }
  423. /**
  424. * Sets or replace a validation rule
  425. *
  426. * @param string $index name of the rule
  427. * @param Cake\Model\Validator\ValidationRule|array rule to add to $index
  428. * @return void
  429. */
  430. public function offsetSet($index, $rule) {
  431. $this->setRule($index, $rule);
  432. }
  433. /**
  434. * Unsets a validation rule
  435. *
  436. * @param string $index name of the rule
  437. * @return void
  438. */
  439. public function offsetUnset($index) {
  440. unset($this->_rules[$index]);
  441. }
  442. /**
  443. * Returns an iterator for each of the rules to be applied
  444. *
  445. * @return ArrayIterator
  446. */
  447. public function getIterator() {
  448. return new \ArrayIterator($this->_rules);
  449. }
  450. /**
  451. * Returns the number of rules in this set
  452. *
  453. * @return integer
  454. */
  455. public function count() {
  456. return count($this->_rules);
  457. }
  458. }