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