Validator.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. /**
  3. * PHP Version 5.4
  4. *
  5. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @since CakePHP(tm) v 2.2.0
  15. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  16. */
  17. namespace Cake\Validation;
  18. use Cake\Validation\RulesProvider;
  19. use Cake\Validation\ValidationSet;
  20. /**
  21. * Validator object encapsulates all methods related to data validations for a model
  22. * It also provides an API to dynamically change validation rules for each model field.
  23. *
  24. * Implements ArrayAccess to easily modify rules in the set
  25. *
  26. * @link http://book.cakephp.org/2.0/en/data-validation.html
  27. */
  28. class Validator implements \ArrayAccess, \IteratorAggregate, \Countable {
  29. /**
  30. * Holds the ValidationSet objects array
  31. *
  32. * @var array
  33. */
  34. protected $_fields = [];
  35. /**
  36. * An associative array of objects or classes containing methods
  37. * used for validation
  38. *
  39. * @var array
  40. */
  41. protected $_providers = [];
  42. /**
  43. * The translation domain to use when setting the error messages
  44. *
  45. * @var string
  46. */
  47. protected $_validationDomain = 'default';
  48. /**
  49. * Returns an array of fields that have failed validation. On the current model. This method will
  50. * actually run validation rules over data, not just return the messages.
  51. *
  52. * @param array $data The data to be checked for errors
  53. * @param boolean $newRecord whether the data to be validated is new or to be updated.
  54. * @return array Array of invalid fields
  55. * @see Validator::validates()
  56. */
  57. public function errors(array $data, $newRecord = true) {
  58. $errors = [];
  59. foreach ($this->_fields as $name => $field) {
  60. $keyPresent = array_key_exists($name, $data);
  61. if (!$keyPresent && !$this->_checkPresence($field, $newRecord)) {
  62. $errors[$name][] = __d('cake', 'This field is required');
  63. continue;
  64. }
  65. if (!$keyPresent) {
  66. continue;
  67. }
  68. $canBeEmpty = $this->_canBeEmpty($field, $newRecord);
  69. $isEmpty = $this->_fieldIsEmpty($data[$name]);
  70. if (!$canBeEmpty && $isEmpty) {
  71. $errors[$name][] = __d('cake', 'This field cannot be left empty');
  72. continue;
  73. }
  74. if ($isEmpty) {
  75. continue;
  76. }
  77. $result = $this->_processRules($field, $data[$name], $data, $newRecord);
  78. if ($result) {
  79. $errors[$name] = $result;
  80. }
  81. }
  82. return $errors;
  83. }
  84. /**
  85. * Returns a ValidationSet object containing all validation rules for a field, if
  86. * passed a ValidationSet as second argument, it will replace any other rule set defined
  87. * before
  88. *
  89. * @param string $name [optional] The fieldname to fetch.
  90. * @param \Cake\Validation\ValidationSet $set The set of rules for field
  91. * @return Cake\Validation\ValidationSet
  92. */
  93. public function field($name, ValidationSet $set = null) {
  94. if (empty($this->_fields[$name])) {
  95. $set = $set ?: new ValidationSet;
  96. $this->_fields[$name] = $set;
  97. }
  98. return $this->_fields[$name];
  99. }
  100. /**
  101. * Check whether or not a validator contains any rules for the given field.
  102. *
  103. * @param string $name The field name to check.
  104. * @return boolean
  105. */
  106. public function hasField($name) {
  107. return isset($this->_fields[$name]);
  108. }
  109. /**
  110. * Associates an object to a name so it can be used as a provider. Providers are
  111. * objects or class names that can contain methods used during validation of for
  112. * deciding whether a validation rule can be applied. All validation methods,
  113. * when called will receive the full list of providers stored in this validator.
  114. *
  115. * If called with no arguments, it will return the provider stored under that name if
  116. * it exists, otherwise it returns this instance of chaining.
  117. *
  118. * @param string $name
  119. * @param null|object|string $object
  120. * @return Validator|object|string
  121. */
  122. public function provider($name, $object = null) {
  123. if ($object === null) {
  124. if (isset($this->_providers[$name])) {
  125. return $this->_providers[$name];
  126. }
  127. if ($name === 'default') {
  128. return $this->_providers[$name] = new RulesProvider;
  129. }
  130. return null;
  131. }
  132. $this->_providers[$name] = $object;
  133. return $this;
  134. }
  135. /**
  136. * Sets the I18n domain for validation messages. This method is chainable.
  137. *
  138. * @param string $validationDomain The validation domain to be used.
  139. * @return Cake\Validation
  140. */
  141. public function setValidationDomain($validationDomain) {
  142. $this->_validationDomain = $validationDomain;
  143. return $this;
  144. }
  145. /**
  146. * Returns whether a rule set is defined for a field or not
  147. *
  148. * @param string $field name of the field to check
  149. * @return boolean
  150. */
  151. public function offsetExists($field) {
  152. return isset($this->_fields[$field]);
  153. }
  154. /**
  155. * Returns the rule set for a field
  156. *
  157. * @param string $field name of the field to check
  158. * @return Cake\Validation\ValidationSet
  159. */
  160. public function offsetGet($field) {
  161. return $this->field($field);
  162. }
  163. /**
  164. * Sets the rule set for a field
  165. *
  166. * @param string $field name of the field to set
  167. * @param array|Cake\Validation\ValidationSet $rules set of rules to apply to field
  168. * @return void
  169. */
  170. public function offsetSet($field, $rules) {
  171. if (!$rules instanceof ValidationSet) {
  172. $set = new ValidationSet;
  173. foreach ((array)$rules as $name => $rule) {
  174. $set->add($name, $rule);
  175. }
  176. }
  177. $this->_fields[$field] = $rules;
  178. }
  179. /**
  180. * Unsets the rule set for a field
  181. *
  182. * @param string $field name of the field to unset
  183. * @return void
  184. */
  185. public function offsetUnset($field) {
  186. unset($this->_fields[$field]);
  187. }
  188. /**
  189. * Returns an iterator for each of the fields to be validated
  190. *
  191. * @return ArrayIterator
  192. */
  193. public function getIterator() {
  194. return new \ArrayIterator($this->_fields);
  195. }
  196. /**
  197. * Returns the number of fields having validation rules
  198. *
  199. * @return integer
  200. */
  201. public function count() {
  202. return count($this->_fields);
  203. }
  204. /**
  205. * Adds a new rule to a field's rule set. If second argument is an array
  206. * then rules list for the field will be replaced with second argument and
  207. * third argument will be ignored.
  208. *
  209. * ## Example:
  210. *
  211. * {{{
  212. * $validator
  213. * ->add('title', 'required', array('rule' => 'notEmpty'))
  214. * ->add('user_id', 'valid', array('rule' => 'numeric', 'message' => 'Invalid User'))
  215. *
  216. * $validator->add('password', array(
  217. * 'size' => array('rule' => array('between', 8, 20)),
  218. * 'hasSpecialCharacter' => array('rule' => 'validateSpecialchar', 'message' => 'not valid')
  219. * ));
  220. * }}}
  221. *
  222. * @param string $field The name of the field from wich the rule will be removed
  223. * @param array|string $name The alias for a single rule or multiple rules array
  224. * @param array|Cake\Validation\ValidationRule $rule the rule to add
  225. * @return Validator this instance
  226. */
  227. public function add($field, $name, $rule = []) {
  228. $rules = $rule;
  229. $field = $this->field($field);
  230. if (!is_array($name)) {
  231. $rules = [$name => $rule];
  232. }
  233. foreach ($rules as $name => $rule) {
  234. $field->add($name, $rule);
  235. }
  236. return $this;
  237. }
  238. /**
  239. * Removes a rule from the set by its name
  240. *
  241. * ## Example:
  242. *
  243. * {{{
  244. * $validator
  245. * ->remove('title', 'required')
  246. * ->remove('user_id')
  247. * }}}
  248. *
  249. * @param string $field The name of the field from which the rule will be removed
  250. * @param string $rule the name of the rule to be removed
  251. * @return Validator this instance
  252. */
  253. public function remove($field, $rule = null) {
  254. if ($rule === null) {
  255. unset($this->_fields[$field]);
  256. } else {
  257. $this->field($field)->remove($rule);
  258. }
  259. return $this;
  260. }
  261. /**
  262. * Sets whether a field is required to be present in data array.
  263. *
  264. * @param string $field the name of the field
  265. * @param boolean|string $mode Valid values are true, false, 'create', 'update'
  266. * @return Validator this instance
  267. */
  268. public function validatePresence($field, $mode = true) {
  269. $this->field($field)->isPresenceRequired($mode);
  270. return $this;
  271. }
  272. /**
  273. * Sets whether a field is allowed to be empty. If it is, all other validation
  274. * rules will be ignored
  275. *
  276. * @param string $field the name of the field
  277. * @param boolean|string $mode Valid values are true, false, 'create', 'update'
  278. * @return Validator this instance
  279. */
  280. public function allowEmpty($field, $mode = true) {
  281. $this->field($field)->isEmptyAllowed($mode);
  282. return $this;
  283. }
  284. /**
  285. * Returns whether or not a field can be left empty for a new or already existing
  286. * record.
  287. *
  288. * @param string field
  289. * @param boolean $newRecord whether the data to be validated is new or to be updated.
  290. * @return boolean
  291. */
  292. public function isEmptyAllowed($field, $newRecord) {
  293. return $this->_canBeEmpty($this->field($field), $newRecord);
  294. }
  295. /**
  296. * Returns whether or not a field can be left out for a new or already existing
  297. * record.
  298. *
  299. * @param string field
  300. * @param boolean $newRecord whether the data to be validated is new or to be updated.
  301. * @return boolean
  302. */
  303. public function isPresenceRequired($field, $newRecord) {
  304. return !$this->_checkPresence($this->field($field), $newRecord);
  305. }
  306. /**
  307. * Returns false if any validation for the passed rule set should be stopped
  308. * due to the field missing in the data array
  309. *
  310. * @param ValidationSet $field the set of rules for a field
  311. * @param boolean $newRecord whether the data to be validated is new or to be updated.
  312. * @return boolean
  313. */
  314. protected function _checkPresence($field, $newRecord) {
  315. $required = $field->isPresenceRequired();
  316. if (in_array($required, ['create', 'update'], true)) {
  317. return (
  318. ($required === 'create' && !$newRecord) ||
  319. ($required === 'update' && $newRecord)
  320. );
  321. }
  322. return !$required;
  323. }
  324. /**
  325. * Returns whether the field can be left blank according to `allowEmpty`
  326. *
  327. * @param ValidationSet $field the set of rules for a field
  328. * @param boolean $newRecord whether the data to be validated is new or to be updated.
  329. * @return boolean
  330. */
  331. protected function _canBeEmpty($field, $newRecord) {
  332. $allowed = $field->isEmptyAllowed();
  333. if (in_array($allowed, array('create', 'update'), true)) {
  334. $allowed = (
  335. ($allowed === 'create' && $newRecord) ||
  336. ($allowed === 'update' && !$newRecord)
  337. );
  338. }
  339. return $allowed;
  340. }
  341. /**
  342. * Returns true if the field is empty in the passed data array
  343. *
  344. * @param mixed $data value to check against
  345. * @return boolean
  346. */
  347. protected function _fieldIsEmpty($data) {
  348. if (empty($data) && $data !== '0' && $data !== false && $data !== 0) {
  349. return true;
  350. }
  351. return false;
  352. }
  353. /**
  354. * Iterates over each rule in the validation set and collects the errors resulting
  355. * from executing them
  356. *
  357. * @param ValidationSet $rules the list of rules for a field
  358. * @param mixed $value the value to be checked
  359. * @param array $data the full data passed to the validator
  360. * @param boolean $newRecord whether is it a new record or an existing one
  361. * @return array
  362. */
  363. protected function _processRules(ValidationSet $rules, $value, $data, $newRecord) {
  364. $errors = [];
  365. // Loading default provider in case there is none
  366. $this->provider('default');
  367. foreach ($rules as $name => $rule) {
  368. $result = $rule->process($value, $this->_providers, compact('newRecord', 'data'));
  369. if ($result === true) {
  370. continue;
  371. }
  372. $errors[$name] = __d('cake', 'The provided value is invalid');
  373. if (is_string($result)) {
  374. $errors[$name] = __d($this->_validationDomain, $result);
  375. }
  376. if ($rule->isLast()) {
  377. break;
  378. }
  379. }
  380. return $errors;
  381. }
  382. }