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