CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.3 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.3
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • AclNode
  • Aco
  • AcoAction
  • Aro
  • BehaviorCollection
  • CakeSchema
  • ConnectionManager
  • I18nModel
  • Model
  • ModelBehavior
  • ModelValidator
  • Permission
  1: <?php
  2: /**
  3:  * ModelValidator.
  4:  *
  5:  * Provides the Model validation logic.
  6:  *
  7:  * PHP 5
  8:  *
  9:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 10:  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 11:  *
 12:  * Licensed under The MIT License
 13:  * For full copyright and license information, please see the LICENSE.txt
 14:  * Redistributions of files must retain the above copyright notice.
 15:  *
 16:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 17:  * @link          http://cakephp.org CakePHP(tm) Project
 18:  * @package       Cake.Model
 19:  * @since         CakePHP(tm) v 2.2.0
 20:  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 21:  */
 22: 
 23: App::uses('CakeValidationSet', 'Model/Validator');
 24: App::uses('Hash', 'Utility');
 25: 
 26: /**
 27:  * ModelValidator object encapsulates all methods related to data validations for a model
 28:  * It also provides an API to dynamically change validation rules for each model field.
 29:  *
 30:  * Implements ArrayAccess to easily modify rules as usually done with `Model::$validate`
 31:  * definition array
 32:  *
 33:  * @package       Cake.Model
 34:  * @link          http://book.cakephp.org/2.0/en/data-validation.html
 35:  */
 36: class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
 37: 
 38: /**
 39:  * Holds the CakeValidationSet objects array
 40:  *
 41:  * @var array
 42:  */
 43:     protected $_fields = array();
 44: 
 45: /**
 46:  * Holds the reference to the model this Validator is attached to
 47:  *
 48:  * @var Model
 49:  */
 50:     protected $_model = array();
 51: 
 52: /**
 53:  * The validators $validate property, used for checking whether validation
 54:  * rules definition changed in the model and should be refreshed in this class
 55:  *
 56:  * @var array
 57:  */
 58:     protected $_validate = array();
 59: 
 60: /**
 61:  * Holds the available custom callback methods, usually taken from model methods
 62:  * and behavior methods
 63:  *
 64:  * @var array
 65:  */
 66:     protected $_methods = array();
 67: 
 68: /**
 69:  * Holds the available custom callback methods from the model
 70:  *
 71:  * @var array
 72:  */
 73:     protected $_modelMethods = array();
 74: 
 75: /**
 76:  * Holds the list of behavior names that were attached when this object was created
 77:  *
 78:  * @var array
 79:  */
 80:     protected $_behaviors = array();
 81: 
 82: /**
 83:  * Constructor
 84:  *
 85:  * @param Model $Model A reference to the Model the Validator is attached to
 86:  */
 87:     public function __construct(Model $Model) {
 88:         $this->_model = $Model;
 89:     }
 90: 
 91: /**
 92:  * Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations
 93:  * that use the 'with' key as well. Since `Model::_saveMulti` is incapable of exiting a save operation.
 94:  *
 95:  * Will validate the currently set data. Use `Model::set()` or `Model::create()` to set the active data.
 96:  *
 97:  * @param array $options An optional array of custom options to be made available in the beforeValidate callback
 98:  * @return boolean True if there are no errors
 99:  */
100:     public function validates($options = array()) {
101:         $errors = $this->errors($options);
102:         if (empty($errors) && $errors !== false) {
103:             $errors = $this->_validateWithModels($options);
104:         }
105:         if (is_array($errors)) {
106:             return count($errors) === 0;
107:         }
108:         return $errors;
109:     }
110: 
111: /**
112:  * Validates a single record, as well as all its directly associated records.
113:  *
114:  * #### Options
115:  *
116:  * - atomic: If true (default), returns boolean. If false returns array.
117:  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
118:  * - deep: If set to true, not only directly associated data , but deeper nested associated data is validated as well.
119:  *
120:  * Warning: This method could potentially change the passed argument `$data`,
121:  * If you do not want this to happen, make a copy of `$data` before passing it
122:  * to this method
123:  *
124:  * @param array $data Record data to validate. This should be an array indexed by association name.
125:  * @param array $options Options to use when validating record data (see above), See also $options of validates().
126:  * @return array|boolean If atomic: True on success, or false on failure.
127:  *    Otherwise: array similar to the $data array passed, but values are set to true/false
128:  *    depending on whether each record validated successfully.
129:  */
130:     public function validateAssociated(&$data, $options = array()) {
131:         $model = $this->getModel();
132:         $options = array_merge(array('atomic' => true, 'deep' => false), $options);
133:         $model->validationErrors = $validationErrors = $return = array();
134:         $model->create(null);
135:         $return[$model->alias] = true;
136:         if (!($model->set($data) && $model->validates($options))) {
137:             $validationErrors[$model->alias] = $model->validationErrors;
138:             $return[$model->alias] = false;
139:         }
140:         $data = $model->data;
141:         if (!empty($options['deep']) && isset($data[$model->alias])) {
142:             $recordData = $data[$model->alias];
143:             unset($data[$model->alias]);
144:             $data = array_merge($data, $recordData);
145:         }
146: 
147:         $associations = $model->getAssociated();
148:         foreach ($data as $association => &$values) {
149:             $validates = true;
150:             if (isset($associations[$association])) {
151:                 if (in_array($associations[$association], array('belongsTo', 'hasOne'))) {
152:                     if ($options['deep']) {
153:                         $validates = $model->{$association}->validateAssociated($values, $options);
154:                     } else {
155:                         $model->{$association}->create(null);
156:                         $validates = $model->{$association}->set($values) && $model->{$association}->validates($options);
157:                         $data[$association] = $model->{$association}->data[$model->{$association}->alias];
158:                     }
159:                     if (is_array($validates)) {
160:                         $validates = !in_array(false, Hash::flatten($validates), true);
161:                     }
162:                     $return[$association] = $validates;
163:                 } elseif ($associations[$association] === 'hasMany') {
164:                     $validates = $model->{$association}->validateMany($values, $options);
165:                     $return[$association] = $validates;
166:                 }
167:                 if (!$validates || (is_array($validates) && in_array(false, $validates, true))) {
168:                     $validationErrors[$association] = $model->{$association}->validationErrors;
169:                 }
170:             }
171:         }
172: 
173:         $model->validationErrors = $validationErrors;
174:         if (isset($validationErrors[$model->alias])) {
175:             $model->validationErrors = $validationErrors[$model->alias];
176:             unset($validationErrors[$model->alias]);
177:             $model->validationErrors = array_merge($model->validationErrors, $validationErrors);
178:         }
179:         if (!$options['atomic']) {
180:             return $return;
181:         }
182:         if ($return[$model->alias] === false || !empty($model->validationErrors)) {
183:             return false;
184:         }
185:         return true;
186:     }
187: 
188: /**
189:  * Validates multiple individual records for a single model
190:  *
191:  * #### Options
192:  *
193:  * - atomic: If true (default), returns boolean. If false returns array.
194:  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
195:  * - deep: If set to true, all associated data will be validated as well.
196:  *
197:  * Warning: This method could potentially change the passed argument `$data`,
198:  * If you do not want this to happen, make a copy of `$data` before passing it
199:  * to this method
200:  *
201:  * @param array $data Record data to validate. This should be a numerically-indexed array
202:  * @param array $options Options to use when validating record data (see above), See also $options of validates().
203:  * @return mixed If atomic: True on success, or false on failure.
204:  *    Otherwise: array similar to the $data array passed, but values are set to true/false
205:  *    depending on whether each record validated successfully.
206:  */
207:     public function validateMany(&$data, $options = array()) {
208:         $model = $this->getModel();
209:         $options = array_merge(array('atomic' => true, 'deep' => false), $options);
210:         $model->validationErrors = $validationErrors = $return = array();
211:         foreach ($data as $key => &$record) {
212:             if ($options['deep']) {
213:                 $validates = $model->validateAssociated($record, $options);
214:             } else {
215:                 $model->create(null);
216:                 $validates = $model->set($record) && $model->validates($options);
217:                 $data[$key] = $model->data;
218:             }
219:             if ($validates === false || (is_array($validates) && in_array(false, Hash::flatten($validates), true))) {
220:                 $validationErrors[$key] = $model->validationErrors;
221:                 $validates = false;
222:             } else {
223:                 $validates = true;
224:             }
225:             $return[$key] = $validates;
226:         }
227:         $model->validationErrors = $validationErrors;
228:         if (!$options['atomic']) {
229:             return $return;
230:         }
231:         return empty($model->validationErrors);
232:     }
233: 
234: /**
235:  * Returns an array of fields that have failed validation. On the current model. This method will
236:  * actually run validation rules over data, not just return the messages.
237:  *
238:  * @param string $options An optional array of custom options to be made available in the beforeValidate callback
239:  * @return array Array of invalid fields
240:  * @see ModelValidator::validates()
241:  */
242:     public function errors($options = array()) {
243:         if (!$this->_triggerBeforeValidate($options)) {
244:             return false;
245:         }
246:         $model = $this->getModel();
247: 
248:         if (!$this->_parseRules()) {
249:             return $model->validationErrors;
250:         }
251: 
252:         $fieldList = isset($options['fieldList']) ? $options['fieldList'] : array();
253:         $exists = $model->exists();
254:         $methods = $this->getMethods();
255:         $fields = $this->_validationList($fieldList);
256: 
257:         foreach ($fields as $field) {
258:             $field->setMethods($methods);
259:             $field->setValidationDomain($model->validationDomain);
260:             $data = isset($model->data[$model->alias]) ? $model->data[$model->alias] : array();
261:             $errors = $field->validate($data, $exists);
262:             foreach ($errors as $error) {
263:                 $this->invalidate($field->field, $error);
264:             }
265:         }
266: 
267:         $model->getEventManager()->dispatch(new CakeEvent('Model.afterValidate', $model));
268:         return $model->validationErrors;
269:     }
270: 
271: /**
272:  * Marks a field as invalid, optionally setting a message explaining
273:  * why the rule failed
274:  *
275:  * @param string $field The name of the field to invalidate
276:  * @param string $message Validation message explaining why the rule failed, defaults to true.
277:  * @return void
278:  */
279:     public function invalidate($field, $message = true) {
280:         $this->getModel()->validationErrors[$field][] = $message;
281:     }
282: 
283: /**
284:  * Gets all possible custom methods from the Model and attached Behaviors
285:  * to be used as validators
286:  *
287:  * @return array List of callables to be used as validation methods
288:  */
289:     public function getMethods() {
290:         $behaviors = $this->_model->Behaviors->enabled();
291:         if (!empty($this->_methods) && $behaviors === $this->_behaviors) {
292:             return $this->_methods;
293:         }
294:         $this->_behaviors = $behaviors;
295: 
296:         if (empty($this->_modelMethods)) {
297:             foreach (get_class_methods($this->_model) as $method) {
298:                 $this->_modelMethods[strtolower($method)] = array($this->_model, $method);
299:             }
300:         }
301: 
302:         $methods = $this->_modelMethods;
303:         foreach (array_keys($this->_model->Behaviors->methods()) as $method) {
304:             $methods += array(strtolower($method) => array($this->_model, $method));
305:         }
306: 
307:         return $this->_methods = $methods;
308:     }
309: 
310: /**
311:  * Returns a CakeValidationSet object containing all validation rules for a field, if no
312:  * params are passed then it returns an array with all CakeValidationSet objects for each field
313:  *
314:  * @param string $name [optional] The fieldname to fetch. Defaults to null.
315:  * @return CakeValidationSet|array
316:  */
317:     public function getField($name = null) {
318:         $this->_parseRules();
319:         if ($name !== null) {
320:             if (!empty($this->_fields[$name])) {
321:                 return $this->_fields[$name];
322:             }
323:             return null;
324:         }
325:         return $this->_fields;
326:     }
327: 
328: /**
329:  * Sets the CakeValidationSet objects from the `Model::$validate` property
330:  * If `Model::$validate` is not set or empty, this method returns false. True otherwise.
331:  *
332:  * @return boolean true if `Model::$validate` was processed, false otherwise
333:  */
334:     protected function _parseRules() {
335:         if ($this->_validate === $this->_model->validate) {
336:             return true;
337:         }
338: 
339:         if (empty($this->_model->validate)) {
340:             $this->_validate = array();
341:             $this->_fields = array();
342:             return false;
343:         }
344: 
345:         $this->_validate = $this->_model->validate;
346:         $this->_fields = array();
347:         $methods = $this->getMethods();
348:         foreach ($this->_validate as $fieldName => $ruleSet) {
349:             $this->_fields[$fieldName] = new CakeValidationSet($fieldName, $ruleSet);
350:             $this->_fields[$fieldName]->setMethods($methods);
351:         }
352:         return true;
353:     }
354: 
355: /**
356:  * Sets the I18n domain for validation messages. This method is chainable.
357:  *
358:  * @param string $validationDomain [optional] The validation domain to be used.
359:  * @return ModelValidator
360:  */
361:     public function setValidationDomain($validationDomain = null) {
362:         if (empty($validationDomain)) {
363:             $validationDomain = 'default';
364:         }
365:         $this->getModel()->validationDomain = $validationDomain;
366:         return $this;
367:     }
368: 
369: /**
370:  * Gets the model related to this validator
371:  *
372:  * @return Model
373:  */
374:     public function getModel() {
375:         return $this->_model;
376:     }
377: 
378: /**
379:  * Processes the Model's whitelist or passed fieldList and returns the list of fields
380:  * to be validated
381:  *
382:  * @param array $fieldList list of fields to be used for validation
383:  * @return array List of validation rules to be applied
384:  */
385:     protected function _validationList($fieldList = array()) {
386:         $model = $this->getModel();
387:         $whitelist = $model->whitelist;
388: 
389:         if (!empty($fieldList)) {
390:             if (!empty($fieldList[$model->alias]) && is_array($fieldList[$model->alias])) {
391:                 $whitelist = $fieldList[$model->alias];
392:             } else {
393:                 $whitelist = $fieldList;
394:             }
395:         }
396:         unset($fieldList);
397: 
398:         if (empty($whitelist) || Hash::dimensions($whitelist) > 1) {
399:             return $this->_fields;
400:         }
401: 
402:         $validateList = array();
403:         $this->validationErrors = array();
404:         foreach ((array)$whitelist as $f) {
405:             if (!empty($this->_fields[$f])) {
406:                 $validateList[$f] = $this->_fields[$f];
407:             }
408:         }
409: 
410:         return $validateList;
411:     }
412: 
413: /**
414:  * Runs validation for hasAndBelongsToMany associations that have 'with' keys
415:  * set and data in the data set.
416:  *
417:  * @param array $options Array of options to use on Validation of with models
418:  * @return boolean Failure of validation on with models.
419:  * @see Model::validates()
420:  */
421:     protected function _validateWithModels($options) {
422:         $valid = true;
423:         $model = $this->getModel();
424: 
425:         foreach ($model->hasAndBelongsToMany as $assoc => $association) {
426:             if (empty($association['with']) || !isset($model->data[$assoc])) {
427:                 continue;
428:             }
429:             list($join) = $model->joinModel($model->hasAndBelongsToMany[$assoc]['with']);
430:             $data = $model->data[$assoc];
431: 
432:             $newData = array();
433:             foreach ((array)$data as $row) {
434:                 if (isset($row[$model->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
435:                     $newData[] = $row;
436:                 } elseif (isset($row[$join]) && isset($row[$join][$model->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
437:                     $newData[] = $row[$join];
438:                 }
439:             }
440:             foreach ($newData as $data) {
441:                 $data[$model->hasAndBelongsToMany[$assoc]['foreignKey']] = $model->id;
442:                 $model->{$join}->create($data);
443:                 $valid = ($valid && $model->{$join}->validator()->validates($options));
444:             }
445:         }
446:         return $valid;
447:     }
448: 
449: /**
450:  * Propagates beforeValidate event
451:  *
452:  * @param array $options
453:  * @return boolean
454:  */
455:     protected function _triggerBeforeValidate($options = array()) {
456:         $model = $this->getModel();
457:         $event = new CakeEvent('Model.beforeValidate', $model, array($options));
458:         list($event->break, $event->breakOn) = array(true, false);
459:         $model->getEventManager()->dispatch($event);
460:         if ($event->isStopped()) {
461:             return false;
462:         }
463:         return true;
464:     }
465: 
466: /**
467:  * Returns whether a rule set is defined for a field or not
468:  *
469:  * @param string $field name of the field to check
470:  * @return boolean
471:  */
472:     public function offsetExists($field) {
473:         $this->_parseRules();
474:         return isset($this->_fields[$field]);
475:     }
476: 
477: /**
478:  * Returns the rule set for a field
479:  *
480:  * @param string $field name of the field to check
481:  * @return CakeValidationSet
482:  */
483:     public function offsetGet($field) {
484:         $this->_parseRules();
485:         return $this->_fields[$field];
486:     }
487: 
488: /**
489:  * Sets the rule set for a field
490:  *
491:  * @param string $field name of the field to set
492:  * @param array|CakeValidationSet $rules set of rules to apply to field
493:  * @return void
494:  */
495:     public function offsetSet($field, $rules) {
496:         $this->_parseRules();
497:         if (!$rules instanceof CakeValidationSet) {
498:             $rules = new CakeValidationSet($field, $rules);
499:             $methods = $this->getMethods();
500:             $rules->setMethods($methods);
501:         }
502:         $this->_fields[$field] = $rules;
503:     }
504: 
505: /**
506:  * Unsets the rule set for a field
507:  *
508:  * @param string $field name of the field to unset
509:  * @return void
510:  */
511:     public function offsetUnset($field) {
512:         $this->_parseRules();
513:         unset($this->_fields[$field]);
514:     }
515: 
516: /**
517:  * Returns an iterator for each of the fields to be validated
518:  *
519:  * @return ArrayIterator
520:  */
521:     public function getIterator() {
522:         $this->_parseRules();
523:         return new ArrayIterator($this->_fields);
524:     }
525: 
526: /**
527:  * Returns the number of fields having validation rules
528:  *
529:  * @return int
530:  */
531:     public function count() {
532:         $this->_parseRules();
533:         return count($this->_fields);
534:     }
535: 
536: /**
537:  * Adds a new rule to a field's rule set. If second argument is an array or instance of
538:  * CakeValidationSet then rules list for the field will be replaced with second argument and
539:  * third argument will be ignored.
540:  *
541:  * ## Example:
542:  *
543:  * {{{
544:  *      $validator
545:  *          ->add('title', 'required', array('rule' => 'notEmpty', 'required' => true))
546:  *          ->add('user_id', 'valid', array('rule' => 'numeric', 'message' => 'Invalid User'))
547:  *
548:  *      $validator->add('password', array(
549:  *          'size' => array('rule' => array('between', 8, 20)),
550:  *          'hasSpecialCharacter' => array('rule' => 'validateSpecialchar', 'message' => 'not valid')
551:  *      ));
552:  * }}}
553:  *
554:  * @param string $field The name of the field from which the rule will be removed
555:  * @param string|array|CakeValidationSet $name name of the rule to be added or list of rules for the field
556:  * @param array|CakeValidationRule $rule or list of rules to be added to the field's rule set
557:  * @return ModelValidator this instance
558:  */
559:     public function add($field, $name, $rule = null) {
560:         $this->_parseRules();
561:         if ($name instanceof CakeValidationSet) {
562:             $this->_fields[$field] = $name;
563:             return $this;
564:         }
565: 
566:         if (!isset($this->_fields[$field])) {
567:             $rule = (is_string($name)) ? array($name => $rule) : $name;
568:             $this->_fields[$field] = new CakeValidationSet($field, $rule);
569:         } else {
570:             if (is_string($name)) {
571:                 $this->_fields[$field]->setRule($name, $rule);
572:             } else {
573:                 $this->_fields[$field]->setRules($name);
574:             }
575:         }
576: 
577:         $methods = $this->getMethods();
578:         $this->_fields[$field]->setMethods($methods);
579: 
580:         return $this;
581:     }
582: 
583: /**
584:  * Removes a rule from the set by its name
585:  *
586:  * ## Example:
587:  *
588:  * {{{
589:  *      $validator
590:  *          ->remove('title', 'required')
591:  *          ->remove('user_id')
592:  * }}}
593:  *
594:  * @param string $field The name of the field from which the rule will be removed
595:  * @param string $rule the name of the rule to be removed
596:  * @return ModelValidator this instance
597:  */
598:     public function remove($field, $rule = null) {
599:         $this->_parseRules();
600:         if ($rule === null) {
601:             unset($this->_fields[$field]);
602:         } else {
603:             $this->_fields[$field]->removeRule($rule);
604:         }
605:         return $this;
606:     }
607: }
608: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs