PageRenderTime 78ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Validation/Validator.php

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