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

/src/Validation/Validator.php

https://github.com/ceeram/cakephp
PHP | 580 lines | 238 code | 51 blank | 291 comment | 43 complexity | 6c9af4d77bc12994dc56ceb871743c24 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 2.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Validation;
  16. use Cake\Validation\RulesProvider;
  17. use Cake\Validation\ValidationSet;
  18. /**
  19. * Validator object encapsulates all methods related to data validations for a model
  20. * It also provides an API to dynamically change validation rules for each model field.
  21. *
  22. * Implements ArrayAccess to easily modify rules in the set
  23. *
  24. * @link http://book.cakephp.org/3.0/en/core-libraries/validation.html
  25. */
  26. class Validator implements \ArrayAccess, \IteratorAggregate, \Countable
  27. {
  28. /**
  29. * Holds the ValidationSet objects array
  30. *
  31. * @var array
  32. */
  33. protected $_fields = [];
  34. /**
  35. * An associative array of objects or classes containing methods
  36. * used for validation
  37. *
  38. * @var array
  39. */
  40. protected $_providers = [];
  41. /**
  42. * Contains the validation messages associated with checking the presence
  43. * for each corresponding field.
  44. *
  45. * @var array
  46. */
  47. protected $_presenceMessages = [];
  48. /**
  49. * Whether or not to use I18n functions for translating default error messages
  50. *
  51. * @var bool
  52. */
  53. protected $_useI18n = false;
  54. /**
  55. * Contains the validation messages associated with checking the emptiness
  56. * for each corresponding field.
  57. *
  58. * @var array
  59. */
  60. protected $_allowEmptyMessages = [];
  61. /**
  62. * Constructor
  63. *
  64. */
  65. public function __construct()
  66. {
  67. $this->_useI18n = function_exists('__d');
  68. }
  69. /**
  70. * Returns an array of fields that have failed validation. On the current model. This method will
  71. * actually run validation rules over data, not just return the messages.
  72. *
  73. * @param array $data The data to be checked for errors
  74. * @param bool $newRecord whether the data to be validated is new or to be updated.
  75. * @return array Array of invalid fields
  76. */
  77. public function errors(array $data, $newRecord = true)
  78. {
  79. $errors = [];
  80. $requiredMessage = 'This field is required';
  81. $emptyMessage = 'This field cannot be left empty';
  82. if ($this->_useI18n) {
  83. $requiredMessage = __d('cake', 'This field is required');
  84. $emptyMessage = __d('cake', 'This field cannot be left empty');
  85. }
  86. foreach ($this->_fields as $name => $field) {
  87. $keyPresent = array_key_exists($name, $data);
  88. if (!$keyPresent && !$this->_checkPresence($field, $newRecord)) {
  89. $errors[$name]['_required'] = isset($this->_presenceMessages[$name])
  90. ? $this->_presenceMessages[$name]
  91. : $requiredMessage;
  92. continue;
  93. }
  94. if (!$keyPresent) {
  95. continue;
  96. }
  97. $providers = $this->_providers;
  98. $context = compact('data', 'newRecord', 'field', 'providers');
  99. $canBeEmpty = $this->_canBeEmpty($field, $context);
  100. $isEmpty = $this->_fieldIsEmpty($data[$name]);
  101. if (!$canBeEmpty && $isEmpty) {
  102. $errors[$name]['_empty'] = isset($this->_allowEmptyMessages[$name])
  103. ? $this->_allowEmptyMessages[$name]
  104. : $emptyMessage;
  105. continue;
  106. }
  107. if ($isEmpty) {
  108. continue;
  109. }
  110. $result = $this->_processRules($name, $field, $data, $newRecord);
  111. if ($result) {
  112. $errors[$name] = $result;
  113. }
  114. }
  115. return $errors;
  116. }
  117. /**
  118. * Returns a ValidationSet object containing all validation rules for a field, if
  119. * passed a ValidationSet as second argument, it will replace any other rule set defined
  120. * before
  121. *
  122. * @param string $name [optional] The fieldname to fetch.
  123. * @param \Cake\Validation\ValidationSet|null $set The set of rules for field
  124. * @return \Cake\Validation\ValidationSet
  125. */
  126. public function field($name, ValidationSet $set = null)
  127. {
  128. if (empty($this->_fields[$name])) {
  129. $set = $set ?: new ValidationSet;
  130. $this->_fields[$name] = $set;
  131. }
  132. return $this->_fields[$name];
  133. }
  134. /**
  135. * Check whether or not a validator contains any rules for the given field.
  136. *
  137. * @param string $name The field name to check.
  138. * @return bool
  139. */
  140. public function hasField($name)
  141. {
  142. return isset($this->_fields[$name]);
  143. }
  144. /**
  145. * Associates an object to a name so it can be used as a provider. Providers are
  146. * objects or class names that can contain methods used during validation of for
  147. * deciding whether a validation rule can be applied. All validation methods,
  148. * when called will receive the full list of providers stored in this validator.
  149. *
  150. * If called with no arguments, it will return the provider stored under that name if
  151. * it exists, otherwise it returns this instance of chaining.
  152. *
  153. * @param string $name The name under which the provider should be set.
  154. * @param null|object|string $object Provider object or class name.
  155. * @return $this|object|string|null
  156. */
  157. public function provider($name, $object = null)
  158. {
  159. if ($object === null) {
  160. if (isset($this->_providers[$name])) {
  161. return $this->_providers[$name];
  162. }
  163. if ($name === 'default') {
  164. return $this->_providers[$name] = new RulesProvider;
  165. }
  166. return null;
  167. }
  168. $this->_providers[$name] = $object;
  169. return $this;
  170. }
  171. /**
  172. * Returns whether a rule set is defined for a field or not
  173. *
  174. * @param string $field name of the field to check
  175. * @return bool
  176. */
  177. public function offsetExists($field)
  178. {
  179. return isset($this->_fields[$field]);
  180. }
  181. /**
  182. * Returns the rule set for a field
  183. *
  184. * @param string $field name of the field to check
  185. * @return \Cake\Validation\ValidationSet
  186. */
  187. public function offsetGet($field)
  188. {
  189. return $this->field($field);
  190. }
  191. /**
  192. * Sets the rule set for a field
  193. *
  194. * @param string $field name of the field to set
  195. * @param array|\Cake\Validation\ValidationSet $rules set of rules to apply to field
  196. * @return void
  197. */
  198. public function offsetSet($field, $rules)
  199. {
  200. if (!$rules instanceof ValidationSet) {
  201. $set = new ValidationSet;
  202. foreach ((array)$rules as $name => $rule) {
  203. $set->add($name, $rule);
  204. }
  205. }
  206. $this->_fields[$field] = $rules;
  207. }
  208. /**
  209. * Unsets the rule set for a field
  210. *
  211. * @param string $field name of the field to unset
  212. * @return void
  213. */
  214. public function offsetUnset($field)
  215. {
  216. unset($this->_fields[$field]);
  217. }
  218. /**
  219. * Returns an iterator for each of the fields to be validated
  220. *
  221. * @return \ArrayIterator
  222. */
  223. public function getIterator()
  224. {
  225. return new \ArrayIterator($this->_fields);
  226. }
  227. /**
  228. * Returns the number of fields having validation rules
  229. *
  230. * @return int
  231. */
  232. public function count()
  233. {
  234. return count($this->_fields);
  235. }
  236. /**
  237. * Adds a new rule to a field's rule set. If second argument is an array
  238. * then rules list for the field will be replaced with second argument and
  239. * third argument will be ignored.
  240. *
  241. * ### Example:
  242. *
  243. * ```
  244. * $validator
  245. * ->add('title', 'required', ['rule' => 'notEmpty'])
  246. * ->add('user_id', 'valid', ['rule' => 'numeric', 'message' => 'Invalid User'])
  247. *
  248. * $validator->add('password', [
  249. * 'size' => ['rule' => ['between', 8, 20]],
  250. * 'hasSpecialCharacter' => ['rule' => 'validateSpecialchar', 'message' => 'not valid']
  251. * ]);
  252. * ```
  253. *
  254. * @param string $field The name of the field from which the rule will be removed
  255. * @param array|string $name The alias for a single rule or multiple rules array
  256. * @param array|\Cake\Validation\ValidationRule $rule the rule to add
  257. * @return $this
  258. */
  259. public function add($field, $name, $rule = [])
  260. {
  261. $field = $this->field($field);
  262. if (!is_array($name)) {
  263. $rules = [$name => $rule];
  264. } else {
  265. $rules = $name;
  266. }
  267. foreach ($rules as $name => $rule) {
  268. $field->add($name, $rule);
  269. }
  270. return $this;
  271. }
  272. /**
  273. * Removes a rule from the set by its name
  274. *
  275. * ### Example:
  276. *
  277. * ```
  278. * $validator
  279. * ->remove('title', 'required')
  280. * ->remove('user_id')
  281. * ```
  282. *
  283. * @param string $field The name of the field from which the rule will be removed
  284. * @param string|null $rule the name of the rule to be removed
  285. * @return $this
  286. */
  287. public function remove($field, $rule = null)
  288. {
  289. if ($rule === null) {
  290. unset($this->_fields[$field]);
  291. } else {
  292. $this->field($field)->remove($rule);
  293. }
  294. return $this;
  295. }
  296. /**
  297. * Sets whether a field is required to be present in data array.
  298. *
  299. * @param string $field the name of the field
  300. * @param bool|string $mode Valid values are true, false, 'create', 'update'
  301. * @param string|null $message The message to show if the field presence validation fails.
  302. * @return $this
  303. */
  304. public function requirePresence($field, $mode = true, $message = null)
  305. {
  306. $this->field($field)->isPresenceRequired($mode);
  307. if ($message) {
  308. $this->_presenceMessages[$field] = $message;
  309. }
  310. return $this;
  311. }
  312. /**
  313. * Allows a field to be empty.
  314. *
  315. * This is the opposite of notEmpty() which requires a field to not be empty.
  316. * By using $mode equal to 'create' or 'update', you can allow fields to be empty
  317. * when records are first created, or when they are updated.
  318. *
  319. * ### Example:
  320. *
  321. * ```
  322. * $validator->allowEmpty('email'); // Email can be empty
  323. * $validator->allowEmpty('email', 'create'); // Email can be empty on create
  324. * $validator->allowEmpty('email', 'update'); // Email can be empty on update
  325. * ```
  326. *
  327. * It is possible to conditionally allow emptiness on a field by passing a callback
  328. * as a second argument. The callback will receive the validation context array as
  329. * argument:
  330. *
  331. * ```
  332. * $validator->allowEmpty('email', function ($context) {
  333. * return !$context['newRecord'] || $context['data']['role'] === 'admin';
  334. * });
  335. * ```
  336. *
  337. * This method will correctly detect empty file uploads and date/time/datetime fields.
  338. *
  339. * Because this and `notEmpty()` modify the same internal state, the last
  340. * method called will take precedence.
  341. *
  342. * @param string $field the name of the field
  343. * @param bool|string|callable $when Indicates when the field is allowed to be empty
  344. * Valid values are true (always), 'create', 'update'. If a callable is passed then
  345. * the field will allowed to be empty only when the callback returns true.
  346. * @return $this
  347. */
  348. public function allowEmpty($field, $when = true)
  349. {
  350. $this->field($field)->isEmptyAllowed($when);
  351. return $this;
  352. }
  353. /**
  354. * Sets a field to require a non-empty value.
  355. *
  356. * This is the opposite of allowEmpty() which allows a field to be empty.
  357. * By using $mode equal to 'create' or 'update', you can make fields required
  358. * when records are first created, or when they are updated.
  359. *
  360. * ### Example:
  361. *
  362. * ```
  363. * $message = 'This field cannot be empty';
  364. * $validator->notEmpty('email'); // Email cannot be empty
  365. * $validator->notEmpty('email', $message, 'create'); // Email can be empty on update
  366. * $validator->notEmpty('email', $message, 'update'); // Email can be empty on create
  367. * ```
  368. *
  369. * It is possible to conditionally disallow emptiness on a field by passing a callback
  370. * as the third argument. The callback will receive the validation context array as
  371. * argument:
  372. *
  373. * ```
  374. * $validator->notEmpty('email', 'Email is required', function ($context) {
  375. * return $context['newRecord'] && $context['data']['role'] !== 'admin';
  376. * });
  377. * ```
  378. *
  379. * Because this and `allowEmpty()` modify the same internal state, the last
  380. * method called will take precedence.
  381. *
  382. * @param string $field the name of the field
  383. * @param string $message The validation message to show if the field is not
  384. * @param bool|string|callable $when Indicates when the field is not allowed
  385. * to be empty. Valid values are true (always), 'create', 'update'. If a
  386. * callable is passed then the field will allowed be empty only when
  387. * the callback returns false.
  388. * @return $this
  389. */
  390. public function notEmpty($field, $message = null, $when = false)
  391. {
  392. if ($when === 'create' || $when === 'update') {
  393. $when = $when === 'create' ? 'update' : 'create';
  394. } elseif (is_callable($when)) {
  395. $when = function ($context) use ($when) {
  396. return !$when($context);
  397. };
  398. }
  399. $this->field($field)->isEmptyAllowed($when);
  400. if ($message) {
  401. $this->_allowEmptyMessages[$field] = $message;
  402. }
  403. return $this;
  404. }
  405. /**
  406. * Returns whether or not a field can be left empty for a new or already existing
  407. * record.
  408. *
  409. * @param string $field Field name.
  410. * @param bool $newRecord whether the data to be validated is new or to be updated.
  411. * @return bool
  412. */
  413. public function isEmptyAllowed($field, $newRecord)
  414. {
  415. $providers = $this->_providers;
  416. $data = [];
  417. $context = compact('data', 'newRecord', 'field', 'providers');
  418. return $this->_canBeEmpty($this->field($field), $context);
  419. }
  420. /**
  421. * Returns whether or not a field can be left out for a new or already existing
  422. * record.
  423. *
  424. * @param string $field Field name.
  425. * @param bool $newRecord whether the data to be validated is new or to be updated.
  426. * @return bool
  427. */
  428. public function isPresenceRequired($field, $newRecord)
  429. {
  430. return !$this->_checkPresence($this->field($field), $newRecord);
  431. }
  432. /**
  433. * Returns false if any validation for the passed rule set should be stopped
  434. * due to the field missing in the data array
  435. *
  436. * @param ValidationSet $field the set of rules for a field
  437. * @param bool $newRecord whether the data to be validated is new or to be updated.
  438. * @return bool
  439. */
  440. protected function _checkPresence($field, $newRecord)
  441. {
  442. $required = $field->isPresenceRequired();
  443. if (in_array($required, ['create', 'update'], true)) {
  444. return (
  445. ($required === 'create' && !$newRecord) ||
  446. ($required === 'update' && $newRecord)
  447. );
  448. }
  449. return !$required;
  450. }
  451. /**
  452. * Returns whether the field can be left blank according to `allowEmpty`
  453. *
  454. * @param ValidationSet $field the set of rules for a field
  455. * @param array $context a key value list of data containing the validation context.
  456. * @return bool
  457. */
  458. protected function _canBeEmpty($field, $context)
  459. {
  460. $allowed = $field->isEmptyAllowed();
  461. if (!is_string($allowed) && is_callable($allowed)) {
  462. return $allowed($context);
  463. }
  464. $newRecord = $context['newRecord'];
  465. if (in_array($allowed, ['create', 'update'], true)) {
  466. $allowed = (
  467. ($allowed === 'create' && $newRecord) ||
  468. ($allowed === 'update' && !$newRecord)
  469. );
  470. }
  471. return $allowed;
  472. }
  473. /**
  474. * Returns true if the field is empty in the passed data array
  475. *
  476. * @param mixed $data value to check against
  477. * @return bool
  478. */
  479. protected function _fieldIsEmpty($data)
  480. {
  481. if (empty($data) && $data !== '0' && $data !== false && $data !== 0 && $data !== 0.0) {
  482. return true;
  483. }
  484. $isArray = is_array($data);
  485. if ($isArray && (isset($data['year']) || isset($data['hour']))) {
  486. $value = implode('', $data);
  487. return strlen($value) === 0;
  488. }
  489. if ($isArray && isset($data['name'], $data['type'], $data['tmp_name'], $data['error'])) {
  490. return (int)$data['error'] === UPLOAD_ERR_NO_FILE;
  491. }
  492. return false;
  493. }
  494. /**
  495. * Iterates over each rule in the validation set and collects the errors resulting
  496. * from executing them
  497. *
  498. * @param string $field The name of the field that is being processed
  499. * @param ValidationSet $rules the list of rules for a field
  500. * @param array $data the full data passed to the validator
  501. * @param bool $newRecord whether is it a new record or an existing one
  502. * @return array
  503. */
  504. protected function _processRules($field, ValidationSet $rules, $data, $newRecord)
  505. {
  506. $value = $data[$field];
  507. $errors = [];
  508. // Loading default provider in case there is none
  509. $this->provider('default');
  510. $message = 'The provided value is invalid';
  511. if ($this->_useI18n) {
  512. $message = __d('cake', 'The provided value is invalid');
  513. }
  514. foreach ($rules as $name => $rule) {
  515. $result = $rule->process($value, $this->_providers, compact('newRecord', 'data', 'field'));
  516. if ($result === true) {
  517. continue;
  518. }
  519. $errors[$name] = $message;
  520. if (is_string($result)) {
  521. $errors[$name] = $result;
  522. }
  523. if ($rule->isLast()) {
  524. break;
  525. }
  526. }
  527. return $errors;
  528. }
  529. }