1: <?php
2: /**
3: * ValidationRule.
4: *
5: * Provides the Model validation logic.
6: *
7: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
8: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * For full copyright and license information, please see the LICENSE.txt
12: * Redistributions of files must retain the above copyright notice.
13: *
14: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
15: * @link https://cakephp.org CakePHP(tm) Project
16: * @since 2.2.0
17: * @license https://opensource.org/licenses/mit-license.php MIT License
18: */
19: namespace Cake\Validation;
20:
21: use InvalidArgumentException;
22:
23: /**
24: * ValidationRule object. Represents a validation method, error message and
25: * rules for applying such method to a field.
26: */
27: class ValidationRule
28: {
29:
30: /**
31: * The method to be called for a given scope
32: *
33: * @var string|callable
34: */
35: protected $_rule;
36:
37: /**
38: * The 'on' key
39: *
40: * @var string
41: */
42: protected $_on;
43:
44: /**
45: * The 'last' key
46: *
47: * @var bool
48: */
49: protected $_last = false;
50:
51: /**
52: * The 'message' key
53: *
54: * @var string
55: */
56: protected $_message;
57:
58: /**
59: * Key under which the object or class where the method to be used for
60: * validation will be found
61: *
62: * @var string
63: */
64: protected $_provider = 'default';
65:
66: /**
67: * Extra arguments to be passed to the validation method
68: *
69: * @var array
70: */
71: protected $_pass = [];
72:
73: /**
74: * Constructor
75: *
76: * @param array $validator [optional] The validator properties
77: */
78: public function __construct(array $validator = [])
79: {
80: $this->_addValidatorProps($validator);
81: }
82:
83: /**
84: * Returns whether this rule should break validation process for associated field
85: * after it fails
86: *
87: * @return bool
88: */
89: public function isLast()
90: {
91: return (bool)$this->_last;
92: }
93:
94: /**
95: * Dispatches the validation rule to the given validator method and returns
96: * a boolean indicating whether the rule passed or not. If a string is returned
97: * it is assumed that the rule failed and the error message was given as a result.
98: *
99: * @param mixed $value The data to validate
100: * @param array $providers associative array with objects or class names that will
101: * be passed as the last argument for the validation method
102: * @param array $context A key value list of data that could be used as context
103: * during validation. Recognized keys are:
104: * - newRecord: (boolean) whether or not the data to be validated belongs to a
105: * new record
106: * - data: The full data that was passed to the validation process
107: * - field: The name of the field that is being processed
108: * @return bool|string
109: * @throws \InvalidArgumentException when the supplied rule is not a valid
110: * callable for the configured scope
111: */
112: public function process($value, array $providers, array $context = [])
113: {
114: $context += ['data' => [], 'newRecord' => true, 'providers' => $providers];
115:
116: if ($this->_skip($context)) {
117: return true;
118: }
119:
120: if (!is_string($this->_rule) && is_callable($this->_rule)) {
121: $callable = $this->_rule;
122: $isCallable = true;
123: } else {
124: $provider = $providers[$this->_provider];
125: $callable = [$provider, $this->_rule];
126: $isCallable = is_callable($callable);
127: }
128:
129: if (!$isCallable) {
130: $message = 'Unable to call method "%s" in "%s" provider for field "%s"';
131: throw new InvalidArgumentException(
132: sprintf($message, $this->_rule, $this->_provider, $context['field'])
133: );
134: }
135:
136: if ($this->_pass) {
137: $args = array_values(array_merge([$value], $this->_pass, [$context]));
138: $result = $callable(...$args);
139: } else {
140: $result = $callable($value, $context);
141: }
142:
143: if ($result === false) {
144: return $this->_message ?: false;
145: }
146:
147: return $result;
148: }
149:
150: /**
151: * Checks if the validation rule should be skipped
152: *
153: * @param array $context A key value list of data that could be used as context
154: * during validation. Recognized keys are:
155: * - newRecord: (boolean) whether or not the data to be validated belongs to a
156: * new record
157: * - data: The full data that was passed to the validation process
158: * - providers associative array with objects or class names that will
159: * be passed as the last argument for the validation method
160: * @return bool True if the ValidationRule should be skipped
161: */
162: protected function _skip($context)
163: {
164: if (!is_string($this->_on) && is_callable($this->_on)) {
165: $function = $this->_on;
166:
167: return !$function($context);
168: }
169:
170: $newRecord = $context['newRecord'];
171: if (!empty($this->_on)) {
172: if (($this->_on === 'create' && !$newRecord) || ($this->_on === 'update' && $newRecord)) {
173: return true;
174: }
175: }
176:
177: return false;
178: }
179:
180: /**
181: * Sets the rule properties from the rule entry in validate
182: *
183: * @param array $validator [optional]
184: * @return void
185: */
186: protected function _addValidatorProps($validator = [])
187: {
188: foreach ($validator as $key => $value) {
189: if (!isset($value) || empty($value)) {
190: continue;
191: }
192: if ($key === 'rule' && is_array($value) && !is_callable($value)) {
193: $this->_pass = array_slice($value, 1);
194: $value = array_shift($value);
195: }
196: if (in_array($key, ['rule', 'on', 'message', 'last', 'provider', 'pass'])) {
197: $this->{"_$key"} = $value;
198: }
199: }
200: }
201:
202: /**
203: * Returns the value of a property by name
204: *
205: * @param string $property The name of the property to retrieve.
206: * @return mixed
207: */
208: public function get($property)
209: {
210: $property = '_' . $property;
211: if (isset($this->{$property})) {
212: return $this->{$property};
213: }
214: }
215: }
216: