Merge branch '2.2-validator' into 2.2

Conflicts:
	lib/Cake/Model/Model.php
	lib/Cake/Test/Case/Model/ModelValidationTest.php
This commit is contained in:
Jose Lorenzo Rodriguez 2012-05-20 14:27:35 -04:30
commit 2ad406ab64
10 changed files with 2676 additions and 383 deletions

View file

@ -25,6 +25,7 @@ App::uses('String', 'Utility');
App::uses('Hash', 'Utility');
App::uses('BehaviorCollection', 'Model');
App::uses('ModelBehavior', 'Model');
App::uses('ModelValidator', 'Model');
App::uses('ConnectionManager', 'Model');
App::uses('Xml', 'Utility');
App::uses('CakeEvent', 'Event');
@ -618,6 +619,13 @@ class Model extends Object implements CakeEventListener {
*/
protected $_eventManager = null;
/**
* Instance of the ModelValidator
*
* @var ModelValidator
*/
protected $_validator = null;
/**
* Constructor. Binds the model's database table to the object.
*
@ -736,6 +744,7 @@ class Model extends Object implements CakeEventListener {
'Model.beforeFind' => array('callable' => 'beforeFind', 'passParams' => true),
'Model.afterFind' => array('callable' => 'afterFind', 'passParams' => true),
'Model.beforeValidate' => array('callable' => 'beforeValidate', 'passParams' => true),
'Model.afterValidate' => array('callable' => 'afterValidate'),
'Model.beforeSave' => array('callable' => 'beforeSave', 'passParams' => true),
'Model.afterSave' => array('callable' => 'afterSave', 'passParams' => true),
'Model.beforeDelete' => array('callable' => 'beforeDelete', 'passParams' => true),
@ -969,8 +978,8 @@ class Model extends Object implements CakeEventListener {
$value = array();
if (strpos($assoc, '.') !== false) {
list($plugin, $assoc) = pluginSplit($assoc);
$this->{$type}[$assoc] = array('className' => $plugin . '.' . $assoc);
list($plugin, $assoc) = pluginSplit($assoc, true);
$this->{$type}[$assoc] = array('className' => $plugin . $assoc);
} else {
$this->{$type}[$assoc] = $value;
}
@ -2128,32 +2137,7 @@ class Model extends Object implements CakeEventListener {
* depending on whether each record validated successfully.
*/
public function validateMany(&$data, $options = array()) {
$options = array_merge(array('atomic' => true, 'deep' => false), $options);
$this->validationErrors = $validationErrors = $return = array();
foreach ($data as $key => &$record) {
if ($options['deep']) {
$validates = $this->validateAssociated($record, $options);
} else {
$this->create(null);
$validates = $this->set($record) && $this->validates($options);
$data[$key] = $this->data;
}
if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) {
$validationErrors[$key] = $this->validationErrors;
$validates = false;
} else {
$validates = true;
}
$return[$key] = $validates;
}
$this->validationErrors = $validationErrors;
if (!$options['atomic']) {
return $return;
}
if (empty($this->validationErrors)) {
return true;
}
return false;
return $this->validator()->validateMany($data, $options);
}
/**
@ -2333,61 +2317,7 @@ class Model extends Object implements CakeEventListener {
* depending on whether each record validated successfully.
*/
public function validateAssociated(&$data, $options = array()) {
$options = array_merge(array('atomic' => true, 'deep' => false), $options);
$this->validationErrors = $validationErrors = $return = array();
$this->create(null);
if (!($this->set($data) && $this->validates($options))) {
$validationErrors[$this->alias] = $this->validationErrors;
$return[$this->alias] = false;
} else {
$return[$this->alias] = true;
}
$data = $this->data;
if (!empty($options['deep']) && isset($data[$this->alias])) {
$recordData = $data[$this->alias];
unset($data[$this->alias]);
$data = array_merge($data, $recordData);
}
$associations = $this->getAssociated();
foreach ($data as $association => &$values) {
$validates = true;
if (isset($associations[$association])) {
if (in_array($associations[$association], array('belongsTo', 'hasOne'))) {
if ($options['deep']) {
$validates = $this->{$association}->validateAssociated($values, $options);
} else {
$validates = $this->{$association}->create($values) !== null && $this->{$association}->validates($options);
}
if (is_array($validates)) {
if (in_array(false, $validates, true)) {
$validates = false;
} else {
$validates = true;
}
}
$return[$association] = $validates;
} elseif ($associations[$association] === 'hasMany') {
$validates = $this->{$association}->validateMany($values, $options);
$return[$association] = $validates;
}
if (!$validates || (is_array($validates) && in_array(false, $validates, true))) {
$validationErrors[$association] = $this->{$association}->validationErrors;
}
}
}
$this->validationErrors = $validationErrors;
if (isset($validationErrors[$this->alias])) {
$this->validationErrors = $validationErrors[$this->alias];
}
if (!$options['atomic']) {
return $return;
}
if ($return[$this->alias] === false || !empty($this->validationErrors)) {
return false;
}
return true;
return $this->validator()->validateAssociated($data, $options);
}
/**
@ -3074,14 +3004,7 @@ class Model extends Object implements CakeEventListener {
* @return boolean True if there are no errors
*/
public function validates($options = array()) {
$errors = $this->invalidFields($options);
if (empty($errors) && $errors !== false) {
$errors = $this->_validateWithModels($options);
}
if (is_array($errors)) {
return count($errors) === 0;
}
return $errors;
return $this->validator()->validates($options);
}
/**
@ -3092,205 +3015,7 @@ class Model extends Object implements CakeEventListener {
* @see Model::validates()
*/
public function invalidFields($options = array()) {
$event = new CakeEvent('Model.beforeValidate', $this, array($options));
list($event->break, $event->breakOn) = array(true, false);
$this->getEventManager()->dispatch($event);
if ($event->isStopped()) {
return false;
}
if (!isset($this->validate) || empty($this->validate)) {
return $this->validationErrors;
}
$data = $this->data;
$methods = array_map('strtolower', get_class_methods($this));
$behaviorMethods = array_keys($this->Behaviors->methods());
if (isset($data[$this->alias])) {
$data = $data[$this->alias];
} elseif (!is_array($data)) {
$data = array();
}
$exists = null;
$_validate = $this->validate;
$whitelist = $this->whitelist;
if (!empty($options['fieldList'])) {
if (!empty($options['fieldList'][$this->alias]) && is_array($options['fieldList'][$this->alias])) {
$whitelist = $options['fieldList'][$this->alias];
} else {
$whitelist = $options['fieldList'];
}
}
if (!empty($whitelist)) {
$validate = array();
foreach ((array)$whitelist as $f) {
if (!empty($this->validate[$f])) {
$validate[$f] = $this->validate[$f];
}
}
$this->validate = $validate;
}
$validationDomain = $this->validationDomain;
if (empty($validationDomain)) {
$validationDomain = 'default';
}
foreach ($this->validate as $fieldName => $ruleSet) {
if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
$ruleSet = array($ruleSet);
}
$default = array(
'allowEmpty' => null,
'required' => null,
'rule' => 'blank',
'last' => true,
'on' => null
);
foreach ($ruleSet as $index => $validator) {
if (!is_array($validator)) {
$validator = array('rule' => $validator);
}
$validator = array_merge($default, $validator);
if (!empty($validator['on']) || in_array($validator['required'], array('create', 'update'), true)) {
if ($exists === null) {
$exists = $this->exists();
}
if ($validator['on'] == 'create' && $exists || $validator['on'] == 'update' && !$exists) {
continue;
}
if ($validator['required'] === 'create' && !$exists || $validator['required'] === 'update' && $exists) {
$validator['required'] = true;
}
}
$valid = true;
$requiredFail = (
(!isset($data[$fieldName]) && $validator['required'] === true) ||
(
isset($data[$fieldName]) && (empty($data[$fieldName]) &&
!is_numeric($data[$fieldName])) && $validator['allowEmpty'] === false
)
);
if (!$requiredFail && array_key_exists($fieldName, $data)) {
if (empty($data[$fieldName]) && $data[$fieldName] != '0' && $validator['allowEmpty'] === true) {
break;
}
if (is_array($validator['rule'])) {
$rule = $validator['rule'][0];
unset($validator['rule'][0]);
$ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule']));
} else {
$rule = $validator['rule'];
$ruleParams = array($data[$fieldName]);
}
if (in_array(strtolower($rule), $methods)) {
$ruleParams[] = $validator;
$ruleParams[0] = array($fieldName => $ruleParams[0]);
$valid = $this->dispatchMethod($rule, $ruleParams);
} elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) {
$ruleParams[] = $validator;
$ruleParams[0] = array($fieldName => $ruleParams[0]);
$valid = $this->Behaviors->dispatchMethod($this, $rule, $ruleParams);
} elseif (method_exists('Validation', $rule)) {
$valid = call_user_func_array(array('Validation', $rule), $ruleParams);
} elseif (!is_array($validator['rule'])) {
$valid = preg_match($rule, $data[$fieldName]);
} elseif (Configure::read('debug') > 0) {
trigger_error(__d('cake_dev', 'Could not find validation handler %s for %s', $rule, $fieldName), E_USER_WARNING);
}
}
if ($requiredFail || !$valid || (is_string($valid) && strlen($valid) > 0)) {
if (is_string($valid)) {
$message = $valid;
} elseif (isset($validator['message'])) {
$args = null;
if (is_array($validator['message'])) {
$message = $validator['message'][0];
$args = array_slice($validator['message'], 1);
} else {
$message = $validator['message'];
}
if (is_array($validator['rule']) && $args === null) {
$args = array_slice($ruleSet[$index]['rule'], 1);
}
if (!empty($args)) {
foreach ($args as $k => $arg) {
if (is_string($arg)) {
$args[$k] = __d($validationDomain, $arg);
}
}
}
$message = __d($validationDomain, $message, $args);
} elseif (is_string($index)) {
if (is_array($validator['rule'])) {
$args = array_slice($ruleSet[$index]['rule'], 1);
$message = __d($validationDomain, $index, $args);
} else {
$message = __d($validationDomain, $index);
}
} elseif (!$requiredFail && is_numeric($index) && count($ruleSet) > 1) {
$message = $index + 1;
} else {
$message = __d('cake_dev', 'This field cannot be left blank');
}
$this->invalidate($fieldName, $message);
if ($validator['last']) {
break;
}
}
}
}
$this->validate = $_validate;
return $this->validationErrors;
}
/**
* Runs validation for hasAndBelongsToMany associations that have 'with' keys
* set. And data in the set() data set.
*
* @param array $options Array of options to use on Validation of with models
* @return boolean Failure of validation on with models.
* @see Model::validates()
*/
protected function _validateWithModels($options) {
$valid = true;
foreach ($this->hasAndBelongsToMany as $assoc => $association) {
if (empty($association['with']) || !isset($this->data[$assoc])) {
continue;
}
list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
$data = $this->data[$assoc];
$newData = array();
foreach ((array)$data as $row) {
if (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
$newData[] = $row;
} elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
$newData[] = $row[$join];
}
}
if (empty($newData)) {
continue;
}
foreach ($newData as $data) {
$data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->id;
$this->{$join}->create($data);
$valid = ($valid && $this->{$join}->validates($options));
}
}
return $valid;
return $this->validator()->errors($options);
}
/**
@ -3303,10 +3028,7 @@ class Model extends Object implements CakeEventListener {
* @return void
*/
public function invalidate($field, $value = true) {
if (!is_array($this->validationErrors)) {
$this->validationErrors = array();
}
$this->validationErrors[$field][] = $value;
$this->validator()->invalidate($field, $value);
}
/**
@ -3615,6 +3337,14 @@ class Model extends Object implements CakeEventListener {
return true;
}
/**
* Called after data has been checked for errors
*
* @return void
*/
public function afterValidate() {
}
/**
* Called when a DataSource-level error occurs.
*
@ -3653,4 +3383,21 @@ class Model extends Object implements CakeEventListener {
}
}
/**
* Retunrs an instance of a model validator for this class
*
* @return ModelValidator
*/
public function validator($instance = null) {
if ($instance instanceof ModelValidator) {
return $this->_validator = $instance;
}
if (is_null($instance)) {
$this->_validator = new ModelValidator($this);
}
return $this->_validator;
}
}

View file

@ -0,0 +1,570 @@
<?php
/**
* ModelValidator.
*
* Provides the Model validation logic.
*
* PHP versions 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Model
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('CakeValidationSet', 'Model/Validator');
/**
* ModelValidator 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 as usually done with `Model::$validate`
* definition array
*
* @package Cake.Model
* @link http://book.cakephp.org/2.0/en/data-validation.html
*/
class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
/**
* Holds the CakeValidationSet objects array
*
* @var array
*/
protected $_fields = array();
/**
* Holds the reference to the model this Validator is attached to
*
* @var Model
*/
protected $_model = array();
/**
* The validators $validate property, used for checking wheter validation
* rules definition changed in the model and should be refreshed in this class
*
* @var array
*/
protected $_validate = array();
/**
* Holds the available custom callback methods, usually taken from model methods
* and behavior methods
*
* @var array
*/
protected $_methods = array();
/**
* Constructor
*
* @param Model $Model A reference to the Model the Validator is attached to
*/
public function __construct(Model $Model) {
$this->_model = $Model;
}
/**
* Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations
* that use the 'with' key as well. Since `Model::_saveMulti` is incapable of exiting a save operation.
*
* Will validate the currently set data. Use `Model::set()` or `Model::create()` to set the active data.
*
* @param array $options An optional array of custom options to be made available in the beforeValidate callback
* @return boolean True if there are no errors
*/
public function validates($options = array()) {
$errors = $this->errors($options);
if (empty($errors) && $errors !== false) {
$errors = $this->_validateWithModels($options);
}
if (is_array($errors)) {
return count($errors) === 0;
}
return $errors;
}
/**
* Validates a single record, as well as all its directly associated records.
*
* #### Options
*
* - atomic: If true (default), returns boolean. If false returns array.
* - fieldList: Equivalent to the $fieldList parameter in Model::save()
* - deep: If set to true, not only directly associated data , but deeper nested associated data is validated as well.
*
* Warning: This method could potentially change the passed argument `$data`,
* If you do not want this to happen, make a copy of `$data` before passing it
* to this method
*
* @param array $data Record data to validate. This should be an array indexed by association name.
* @param array $options Options to use when validating record data (see above), See also $options of validates().
* @return array|boolean If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record validated successfully.
*/
public function validateAssociated(&$data, $options = array()) {
$model = $this->getModel();
$options = array_merge(array('atomic' => true, 'deep' => false), $options);
$model->validationErrors = $validationErrors = $return = array();
$model->create(null);
if (!($model->set($data) && $model->validates($options))) {
$validationErrors[$model->alias] = $model->validationErrors;
$return[$model->alias] = false;
} else {
$return[$model->alias] = true;
}
$data = $model->data;
if (!empty($options['deep']) && isset($data[$model->alias])) {
$recordData = $data[$model->alias];
unset($data[$model->alias]);
$data = array_merge($data, $recordData);
}
$associations = $model->getAssociated();
foreach ($data as $association => &$values) {
$validates = true;
if (isset($associations[$association])) {
if (in_array($associations[$association], array('belongsTo', 'hasOne'))) {
if ($options['deep']) {
$validates = $model->{$association}->validateAssociated($values, $options);
} else {
$validates = $model->{$association}->create($values) !== null && $model->{$association}->validates($options);
}
if (is_array($validates)) {
if (in_array(false, $validates, true)) {
$validates = false;
} else {
$validates = true;
}
}
$return[$association] = $validates;
} elseif ($associations[$association] === 'hasMany') {
$validates = $model->{$association}->validateMany($values, $options);
$return[$association] = $validates;
}
if (!$validates || (is_array($validates) && in_array(false, $validates, true))) {
$validationErrors[$association] = $model->{$association}->validationErrors;
}
}
}
$model->validationErrors = $validationErrors;
if (isset($validationErrors[$model->alias])) {
$model->validationErrors = $validationErrors[$model->alias];
}
if (!$options['atomic']) {
return $return;
}
if ($return[$model->alias] === false || !empty($model->validationErrors)) {
return false;
}
return true;
}
/**
* Validates multiple individual records for a single model
*
* #### Options
*
* - atomic: If true (default), returns boolean. If false returns array.
* - fieldList: Equivalent to the $fieldList parameter in Model::save()
* - deep: If set to true, all associated data will be validated as well.
*
* Warning: This method could potentially change the passed argument `$data`,
* If you do not want this to happen, make a copy of `$data` before passing it
* to this method
*
* @param array $data Record data to validate. This should be a numerically-indexed array
* @param array $options Options to use when validating record data (see above), See also $options of validates().
* @return boolean True on success, or false on failure.
* @return mixed If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record validated successfully.
*/
public function validateMany(&$data, $options = array()) {
$model = $this->getModel();
$options = array_merge(array('atomic' => true, 'deep' => false), $options);
$model->validationErrors = $validationErrors = $return = array();
foreach ($data as $key => &$record) {
if ($options['deep']) {
$validates = $model->validateAssociated($record, $options);
} else {
$model->create(null);
$validates = $model->set($record) && $model->validates($options);
$data[$key] = $model->data;
}
if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) {
$validationErrors[$key] = $model->validationErrors;
$validates = false;
} else {
$validates = true;
}
$return[$key] = $validates;
}
$model->validationErrors = $validationErrors;
if (!$options['atomic']) {
return $return;
}
if (empty($model->validationErrors)) {
return true;
}
return false;
}
/**
* 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 string $options An optional array of custom options to be made available in the beforeValidate callback
* @return array Array of invalid fields
* @see ModelValidator::validates()
*/
public function errors($options = array()) {
if (!$this->_triggerBeforeValidate($options)) {
return false;
}
$model = $this->getModel();
if (!$this->_parseRules()) {
return $model->validationErrors;
}
$fieldList = isset($options['fieldList']) ? $options['fieldList'] : array();
$exists = $model->exists();
$methods = $this->getMethods();
$fields = $this->_validationList($fieldList);
foreach ($fields as $field) {
$field->setMethods($methods);
$field->setValidationDomain($model->validationDomain);
$data = isset($model->data[$model->alias]) ? $model->data[$model->alias] : array();
$errors = $field->validate($data, $exists);
foreach ($errors as $error) {
$this->invalidate($field->field, $error);
}
}
$model->getEventManager()->dispatch(new CakeEvent('Model.afterValidate', $model));
return $model->validationErrors;
}
/**
* Marks a field as invalid, optionally setting a message explaining
* why the rule failed
*
* @param string $field The name of the field to invalidate
* @param string $message Validation message explaining why the rule failed, defaults to true.
* @return void
*/
public function invalidate($field, $message = true) {
$this->getModel()->validationErrors[$field][] = $message;
}
/**
* Gets all possible custom methods from the Model and attached Behaviors
* to be used as validators
*
* @return array List of callables to be used as validation methods
*/
public function getMethods() {
if (!empty($this->_methods)) {
return $this->_methods;
}
$methods = array();
foreach (get_class_methods($this->_model) as $method) {
$methods[strtolower($method)] = array($this->_model, $method);
}
foreach (array_keys($this->_model->Behaviors->methods()) as $method) {
$methods += array(strtolower($method) => array($this->_model, $method));
}
return $this->_methods = $methods;
}
/**
* Returns a CakeValidationSet object containing all validation rules for a field, if no
* params are passed then it returns an array with all CakeValidationSet objects for each field
*
* @param string $name [optional] The fieldname to fetch. Defaults to null.
* @return CakeValidationSet|array
*/
public function getField($name = null) {
if ($name !== null && !empty($this->_fields[$name])) {
return $this->_fields[$name];
} elseif ($name !==null) {
return null;
}
return $this->_fields;
}
/**
* Sets the CakeValidationSet objects from the `Model::$validate` property
* If `Model::$validate` is not set or empty, this method returns false. True otherwise.
*
* @return boolean true if `Model::$validate` was processed, false otherwise
*/
protected function _parseRules() {
if ($this->_validate === $this->_model->validate) {
return true;
}
if (empty($this->_model->validate)) {
$this->_validate = array();
$this->_fields = array();
return false;
}
$this->_validate = $this->_model->validate;
$this->_fields = array();
$methods = $this->getMethods();
foreach ($this->_validate as $fieldName => $ruleSet) {
$this->_fields[$fieldName] = new CakeValidationSet($fieldName, $ruleSet, $methods);
}
return true;
}
/**
* Sets the I18n domain for validation messages. This method is chainable.
*
* @param string $validationDomain [optional] The validation domain to be used.
* @return ModelValidator
*/
public function setValidationDomain($validationDomain = null) {
if (empty($validationDomain)) {
$validationDomain = 'default';
}
$this->getModel()->validationDomain = $validationDomain;
return $this;
}
/**
* Gets the model related to this validator
*
* @return Model
*/
public function getModel() {
return $this->_model;
}
/**
* Processes the Model's whitelist or passed fieldList and returns the list of fields
* to be validated
*
* @param array $fieldList list of fields to be used for validation
* @return array List of validation rules to be applied
*/
protected function _validationList($fieldList = array()) {
$model = $this->getModel();
$whitelist = $model->whitelist;
if (!empty($fieldList)) {
if (!empty($fieldList[$model->alias]) && is_array($fieldList[$model->alias])) {
$whitelist = $fieldList[$model->alias];
} else {
$whitelist = $fieldList;
}
}
unset($fieldList);
$validateList = array();
if (!empty($whitelist)) {
$this->validationErrors = array();
foreach ((array)$whitelist as $f) {
if (!empty($this->_fields[$f])) {
$validateList[$f] = $this->_fields[$f];
}
}
} else {
return $this->_fields;
}
return $validateList;
}
/**
* Runs validation for hasAndBelongsToMany associations that have 'with' keys
* set and data in the data set.
*
* @param array $options Array of options to use on Validation of with models
* @return boolean Failure of validation on with models.
* @see Model::validates()
*/
protected function _validateWithModels($options) {
$valid = true;
$model = $this->getModel();
foreach ($model->hasAndBelongsToMany as $assoc => $association) {
if (empty($association['with']) || !isset($model->data[$assoc])) {
continue;
}
list($join) = $model->joinModel($model->hasAndBelongsToMany[$assoc]['with']);
$data = $model->data[$assoc];
$newData = array();
foreach ((array)$data as $row) {
if (isset($row[$model->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
$newData[] = $row;
} elseif (isset($row[$join]) && isset($row[$join][$model->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
$newData[] = $row[$join];
}
}
if (empty($newData)) {
continue;
}
foreach ($newData as $data) {
$data[$model->hasAndBelongsToMany[$assoc]['foreignKey']] = $model->id;
$model->{$join}->create($data);
$valid = ($valid && $model->{$join}->validator()->validates($options));
}
}
return $valid;
}
/**
* Propagates beforeValidate event
*
* @param array $options
* @return boolean
*/
protected function _triggerBeforeValidate($options = array()) {
$model = $this->getModel();
$event = new CakeEvent('Model.beforeValidate', $model, array($options));
list($event->break, $event->breakOn) = array(true, false);
$model->getEventManager()->dispatch($event);
if ($event->isStopped()) {
return false;
}
return true;
}
/**
* Returns wheter 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) {
$this->_parseRules();
return isset($this->_fields[$field]);
}
/**
* Returns the rule set for a field
*
* @param string $field name of the field to check
* @return CakeValidationSet
**/
public function offsetGet($field) {
$this->_parseRules();
return $this->_fields[$field];
}
/**
* Sets the rule set for a field
*
* @param string $field name of the field to set
* @param array|CakeValidationSet $rules set of rules to apply to field
* @return void
**/
public function offsetSet($field, $rules) {
$this->_parseRules();
if (!$rules instanceof CakeValidationSet) {
$rules = new CakeValidationSet($field, $rules, $this->getMethods());
}
$this->_fields[$field] = $rules;
}
/**
* Unsets the rulset for a field
*
* @param string $field name of the field to unset
* @return void
**/
public function offsetUnset($field) {
$this->_parseRules();
unset($this->_fields[$field]);
}
/**
* Returns an iterator for each of the fields to be validated
*
* @return ArrayIterator
**/
public function getIterator() {
$this->_parseRules();
return new ArrayIterator($this->_fields);
}
/**
* Returns the number of fields having validation rules
*
* @return int
**/
public function count() {
$this->_parseRules();
return count($this->_fields);
}
/**
* Adds a new rule to a field's rule set
*
* ## Example:
*
* {{{
* $validator
* ->add('title', 'required', array('rule' => 'notEmpty', 'required' => true))
* ->add('user_id', 'valid', array('rule' => 'numeric', 'message' => 'Invalid User'))
* }}}
*
* @param string $field The name of the field from wich the rule will be removed
* @param array|CakeValidationRule $rule the rule to be added to the field's rule set
* @return ModelValidator this instance
**/
public function add($field, $name, $rule) {
$this->_parseRules();
if (!isset($this->_fields[$field])) {
$rule = array($name => $rule);
$this->_fields[$field] = new CakeValidationSet($field, $rule, $this->getMethods());
} else {
$this->_fields[$field]->setRule($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 wich the rule will be removed
* @param string $rule the name of the rule to be removed
* @return ModelValidator this instance
**/
public function remove($field, $rule = null) {
$this->_parseRules();
if ($rule === null) {
unset($this->_fields[$field]);
} else {
$this->_fields[$field]->removeRule($rule);
}
return $this;
}
}

View file

@ -0,0 +1,333 @@
<?php
/**
* CakeValidationRule.
*
* Provides the Model validation logic.
*
* PHP versions 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Model.Validator
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('ModelValidator', 'Model');
App::uses('CakeValidationSet', 'Model/Validator');
App::uses('Validation', 'Utility');
/**
* CakeValidationRule object. Represents a validation method, error message and
* rules for applying such method to a field.
*
* @package Cake.Model.Validator
* @link http://book.cakephp.org/2.0/en/data-validation.html
*/
class CakeValidationRule {
/**
* Whether the field passed this validation rule
*
* @var mixed
*/
protected $_valid = true;
/**
* Holds whether the record being validated exists in datasource or not
*
* @var boolean
*/
protected $_recordExists = false;
/**
* Validation method
*
* @var mixed
*/
protected $_rule = null;
/**
* Validation method arguments
*
* @var array
*/
protected $_ruleParams = array();
/**
* Holds passed in options
*
* @var array
*/
protected $_passedOptions = array();
/**
* The 'rule' key
*
* @var mixed
*/
public $rule = 'blank';
/**
* The 'required' key
*
* @var mixed
*/
public $required = null;
/**
* The 'allowEmpty' key
*
* @var boolean
*/
public $allowEmpty = false;
/**
* The 'on' key
*
* @var string
*/
public $on = null;
/**
* The 'last' key
*
* @var boolean
*/
public $last = true;
/**
* The 'message' key
*
* @var string
*/
public $message = null;
/**
* Constructor
*
* @param array $validator [optional] The validator properties
*/
public function __construct($validator = array()) {
$this->_addValidatorProps($validator);
}
/**
* Checks if the rule is valid
*
* @return boolean
*/
public function isValid() {
if (!$this->_valid || (is_string($this->_valid) && !empty($this->_valid))) {
return false;
}
return true;
}
/**
* Returns whether the field can be left blank according to this rule
*
* @return boolean
*/
public function isEmptyAllowed() {
return $this->skip() || $this->allowEmpty === true;
}
/**
* Checks if the field is required according to the `required` property
*
* @return boolean
*/
public function isRequired() {
if (in_array($this->required, array('create', 'update'), true)) {
if ($this->required === 'create' && !$this->isUpdate() || $this->required === 'update' && $this->isUpdate()) {
return true;
} else {
return false;
}
}
return $this->required;
}
/**
* Checks whether the field failed the `field should be present` validation
*
* @param array $data data to check rule against
* @return boolean
*/
public function checkRequired($field, &$data) {
return (
(!isset($data[$field]) && $this->isRequired() === true) ||
(
isset($data[$field]) && (empty($data[$field]) &&
!is_numeric($data[$field])) && $this->allowEmpty === false
)
);
}
/**
* Checks if the allowEmpty key applies
*
* @param array $data data to check rule against
* @return boolean
*/
public function checkEmpty($field, &$data) {
if (empty($data[$field]) && $data[$field] != '0' && $this->allowEmpty === true) {
return true;
}
return false;
}
/**
* Checks if the validation rule should be skipped
*
* @return boolean True if the ValidationRule can be skipped
*/
public function skip() {
if (!empty($this->on)) {
if ($this->on == 'create' && $this->isUpdate() || $this->on == 'update' && !$this->isUpdate()) {
return true;
}
}
return false;
}
/**
* Returns whethere this rule should break validation process for associated field
* after it fails
*
* @return boolean
*/
public function isLast() {
return (bool) $this->last;
}
/**
* Gets the validation error message
*
* @return string
*/
public function getValidationResult() {
return $this->_valid;
}
/**
* Gets an array with the rule properties
*
* @return array
*/
protected function _getPropertiesArray() {
$rule = $this->rule;
if (!is_string($rule)) {
unset($rule[0]);
}
return array(
'rule' => $rule,
'required' => $this->required,
'allowEmpty' => $this->allowEmpty,
'on' => $this->on,
'last' => $this->last,
'message' => $this->message
);
}
/**
* Sets the recordExists configuration value for this rule,
* ir refers to wheter the model record it is validating exists
* exists in the collection or not (create or update operation)
*
* If called with no parameters it will return whether this rule
* is configured for update operations or not.
*
* @return boolean
**/
public function isUpdate($exists = null) {
if ($exists === null) {
return $this->_recordExists;
}
return $this->_recordExists = $exists;
}
/**
* Dispatches the validation rule to the given validator method
*
* @return boolean True if the rule could be dispatched, false otherwise
*/
public function process($field, &$data, &$methods) {
$this->_valid = true;
$this->_parseRule($field, $data);
$validator = $this->_getPropertiesArray();
$rule = strtolower($this->_rule);
if (isset($methods[$rule])) {
$this->_ruleParams[] = array_merge($validator, $this->_passedOptions);
$this->_ruleParams[0] = array($field => $this->_ruleParams[0]);
$this->_valid = call_user_func_array($methods[$rule], $this->_ruleParams);
} elseif (class_exists('Validation') && method_exists('Validation', $this->_rule)) {
$this->_valid = call_user_func_array(array('Validation', $this->_rule), $this->_ruleParams);
} elseif (is_string($validator['rule'])) {
$this->_valid = preg_match($this->_rule, $data[$field]);
} elseif (Configure::read('debug') > 0) {
trigger_error(__d('cake_dev', 'Could not find validation handler %s for %s', $this->_rule, $field), E_USER_WARNING);
return false;
}
return true;
}
/**
* Returns passed options for this rule
*
* @return array
**/
public function getOptions($key) {
if (!isset($this->_passedOptions[$key])) {
return null;
}
return $this->_passedOptions[$key];
}
/**
* Sets the rule properties from the rule entry in validate
*
* @param array $validator [optional]
* @return void
*/
protected function _addValidatorProps($validator = array()) {
if (!is_array($validator)) {
$validator = array('rule' => $validator);
}
foreach ($validator as $key => $value) {
if (isset($value) || !empty($value)) {
if (in_array($key, array('rule', 'required', 'allowEmpty', 'on', 'message', 'last'))) {
$this->{$key} = $validator[$key];
} else {
$this->_passedOptions[$key] = $value;
}
}
}
}
/**
* Parses the rule and sets the rule and ruleParams
*
* @return void
*/
protected function _parseRule($field, &$data) {
if (is_array($this->rule)) {
$this->_rule = $this->rule[0];
$this->_ruleParams = array_merge(array($data[$field]), array_values(array_slice($this->rule, 1)));
} else {
$this->_rule = $this->rule;
$this->_ruleParams = array($data[$field]);
}
}
}

View file

@ -0,0 +1,343 @@
<?php
/**
* CakeValidationSet.
*
* Provides the Model validation logic.
*
* PHP versions 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Model.Validator
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('ModelValidator', 'Model');
App::uses('CakeValidationRule', 'Model/Validator');
/**
* CakeValidationSet object. Holds all validation rules for a field and exposes
* methods to dynamically add or remove validation rules
*
* @package Cake.Model.Validator
* @link http://book.cakephp.org/2.0/en/data-validation.html
*/
class CakeValidationSet implements ArrayAccess, IteratorAggregate, Countable {
/**
* Holds the CakeValidationRule objects
*
* @var array
*/
protected $_rules = array();
/**
* List of methods available for validation
*
* @var array
**/
protected $_methods = array();
/**
* I18n domain for validation messages.
*
* @var string
**/
protected $_validationDomain = null;
/**
* Whether the validation is stopped
*
* @var boolean
*/
public $isStopped = false;
/**
* Holds the fieldname
*
* @var string
*/
public $field = null;
/**
* Holds the original ruleSet
*
* @var array
*/
public $ruleSet = array();
/**
* Constructor
*
* @param string $fieldName The fieldname
* @param array $ruleset
*/
public function __construct($fieldName, $ruleSet) {
$this->field = $fieldName;
if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
$ruleSet = array($ruleSet);
}
foreach ($ruleSet as $index => $validateProp) {
$this->_rules[$index] = new CakeValidationRule($validateProp);
}
$this->ruleSet = $ruleSet;
}
/**
* Sets the list of methods to use for validation
*
* @return void
**/
public function setMethods(&$methods) {
$this->_methods =& $methods;
}
/**
* Sets the I18n domain for validation messages.
*
* @param string $validationDomain The validation domain to be used.
* @return void
*/
public function setValidationDomain($validationDomain) {
$this->_validationDomain = $validationDomain;
}
/**
* Runs all validation rules in this set and returns a list of
* validation errors
*
* @return array list of validation errors for this field
*/
public function validate($data, $isUpdate = false) {
$errors = array();
foreach ($this->getRules() as $name => $rule) {
$rule->isUpdate($isUpdate);
if ($rule->skip()) {
continue;
}
$checkRequired = $rule->checkRequired($this->field, $data);
if (!$checkRequired && array_key_exists($this->field, $data)) {
if ($rule->checkEmpty($this->field, $data)) {
break;
}
$rule->process($this->field, $data, $this->_methods);
}
if ($checkRequired || !$rule->isValid()) {
$errors[] = $this->_processValidationResponse($name, $rule);
if ($rule->isLast()) {
break;
}
}
}
return $errors;
}
/**
* Gets a rule for a given name if exists
*
* @param string $name
* @return CakeValidationRule
*/
public function getRule($name) {
if (!empty($this->_rules[$name])) {
return $this->_rules[$name];
}
}
/**
* Returns all rules for this validation set
*
* @return array
*/
public function getRules() {
return $this->_rules;
}
/**
* Sets a CakeValidationRule $rule with a $name
*
* ## Example:
*
* {{{
* $set
* ->setRule('required', array('rule' => 'notEmpty', 'required' => true))
* ->setRule('inRange', array('rule' => array('between', 4, 10))
* }}}
*
* @param mixed $name The name under which the rule should be set
* @param CakeValidationRule|array $rule The validation rule to be set
* @return CakeValidationSet this instance
*/
public function setRule($name, $rule) {
if (!$rule instanceof CakeValidationRule) {
$rule = new CakeValidationRule($rule);
}
$this->_rules[$name] = $rule;
return $this;
}
/**
* Removes a validation rule from the set
*
* ## Example:
*
* {{{
* $set
* ->removeRule('required')
* ->removeRule('inRange')
* }}}
*
* @param mixed $name The name under which the rule should be unset
* @return CakeValidationSet this instance
*/
public function removeRule($name) {
unset($this->_rules[$name]);
return $this;
}
/**
* Sets the rules for a given field
*
* ## Example:
*
* {{{
* $set->setRules(array(
* 'required' => array('rule' => 'notEmpty', 'required' => true),
* 'inRange' => array('rule' => array('between', 4, 10)
* ));
* }}}
*
* @param array $rules The rules to be set
* @param bolean $mergeVars [optional] If true, merges vars instead of replace. Defaults to true.
* @return ModelField
*/
public function setRules($rules = array(), $mergeVars = true) {
if ($mergeVars === false) {
$this->_rules = $rules;
} else {
$this->_rules = array_merge($this->_rules, $rules);
}
return $this;
}
/**
* Fetches the correct error message for a failed validation
*
* @param string $name the name of the rule as it was configured
* @param CakeValidationRule $rule the object containing validation information
* @return string
*/
protected function _processValidationResponse($name, $rule) {
$message = $rule->getValidationResult();
if (is_string($message)) {
return $message;
}
$message = $rule->message;
if ($message !== null) {
$args = null;
if (is_array($message)) {
$result = $message[0];
$args = array_slice($message, 1);
} else {
$result = $message;
}
if (is_array($rule->rule) && $args === null) {
$args = array_slice($rule->rule, 1);
}
if (!empty($args)) {
foreach ($args as $k => $arg) {
$args[$k] = __d($this->_validationDomain, $arg);
}
}
$message = __d($this->_validationDomain, $result, $args);
} elseif (is_string($name)) {
if (is_array($rule->rule)) {
$args = array_slice($rule->rule, 1);
if (!empty($args)) {
foreach ($args as $k => $arg) {
$args[$k] = __d($this->_validationDomain, $arg);
}
}
$message = __d($this->_validationDomain, $name, $args);
} else {
$message = __d($this->_validationDomain, $name);
}
} else {
$message = __d('cake_dev', 'This field cannot be left blank');
}
return $message;
}
/**
* Returns wheter an index exists in the rule set
*
* @param string $index name of the rule
* @return boolean
**/
public function offsetExists($index) {
return isset($this->_rules[$index]);
}
/**
* Returns a rule object by its index
*
* @param string $index name of the rule
* @return CakeValidationRule
**/
public function offsetGet($index) {
return $this->_rules[$index];
}
/**
* Sets or replace a validation rule
*
* @param string $index name of the rule
* @param CakeValidationRule|array rule to add to $index
**/
public function offsetSet($index, $rule) {
$this->setRule($index, $rule);
}
/**
* Unsets a validation rule
*
* @param string $index name of the rule
* @return void
**/
public function offsetUnset($index) {
unset($this->_rules[$index]);
}
/**
* Returns an iterator for each of the rules to be applied
*
* @return ArrayIterator
**/
public function getIterator() {
return new ArrayIterator($this->_rules);
}
/**
* Returns the number of rules in this set
*
* @return int
**/
public function count() {
return count($this->_rules);
}
}

View file

@ -34,6 +34,8 @@ class ModelTest extends PHPUnit_Framework_TestSuite {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite('All Model related class tests');
$suite->addTestFile(CORE_TEST_CASES . DS . 'Model' . DS . 'Validator' . DS .'CakeValidationSetTest.php');
$suite->addTestFile(CORE_TEST_CASES . DS . 'Model' . DS . 'Validator' . DS .'CakeValidationRuleTest.php');
$suite->addTestFile(CORE_TEST_CASES . DS . 'Model' . DS . 'ModelReadTest.php');
$suite->addTestFile(CORE_TEST_CASES . DS . 'Model' . DS . 'ModelWriteTest.php');
$suite->addTestFile(CORE_TEST_CASES . DS . 'Model' . DS . 'ModelDeleteTest.php');

View file

@ -48,7 +48,8 @@ class ModelValidationTest extends BaseModelTest {
'on' => null,
'last' => true,
'allowEmpty' => false,
'required' => true
'required' => true,
'message' => null
),
'or' => true,
'ignoreOnSame' => 'id'
@ -84,7 +85,8 @@ class ModelValidationTest extends BaseModelTest {
'on' => null,
'last' => true,
'allowEmpty' => false,
'required' => true
'required' => true,
'message' => null
),
'six' => 6
);
@ -110,7 +112,8 @@ class ModelValidationTest extends BaseModelTest {
'on' => null,
'last' => true,
'allowEmpty' => false,
'required' => true
'required' => true,
'message' => null
)
);
$this->assertEquals($expected, $TestModel->validatorParams);
@ -537,6 +540,8 @@ class ModelValidationTest extends BaseModelTest {
'title' => array('tooShort', 'onlyLetters')
);
$this->assertEquals($expected, $result);
$result = $TestModel->validationErrors;
$this->assertEquals($expected, $result);
}
/**
@ -601,6 +606,7 @@ class ModelValidationTest extends BaseModelTest {
* @return void
*/
public function testValidatesWithModelsAndSaveAll() {
$this->loadFixtures('Something', 'SomethingElse', 'JoinThing');
$data = array(
'Something' => array(
'id' => 5,
@ -621,7 +627,11 @@ class ModelValidationTest extends BaseModelTest {
$Something->create();
$result = $Something->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$result = $Something->validateAssociated($data);
$this->assertFalse($result);
$this->assertEquals($expectedError, $JoinThing->validationErrors);
$result = $Something->validator()->validateAssociated($data);
$this->assertFalse($result);
$Something->create();
$result = $Something->saveAll($data, array('validate' => 'first'));
@ -663,6 +673,9 @@ class ModelValidationTest extends BaseModelTest {
$Author->create();
$result = $Author->saveAll($data, array('validate' => 'only'));
$this->assertTrue($result);
$result = $Author->validateAssociated($data);
$this->assertTrue($result);
$this->assertTrue($result);
$Author->create();
$result = $Author->saveAll($data, array('validate' => 'first'));
@ -742,22 +755,22 @@ class ModelValidationTest extends BaseModelTest {
);
$TestModel->create();
$TestModel->invalidFields();
$expected = array(
'title' => array(
'Minimum length allowed is 6 chars',
)
);
$TestModel->invalidFields();
$this->assertEquals($expected, $TestModel->validationErrors);
$TestModel->create(array('title' => 'foo'));
$TestModel->invalidFields();
$expected = array(
'title' => array(
'Minimum length allowed is 6 chars',
'You may enter up to 14 chars (minimum is 6 chars)'
)
);
$TestModel->invalidFields();
$this->assertEquals($expected, $TestModel->validationErrors);
}
@ -786,12 +799,12 @@ class ModelValidationTest extends BaseModelTest {
);
$TestModel->create();
$TestModel->invalidFields();
$expected = array(
'title' => array(
'Translated validation failed: Translated arg1',
)
);
$TestModel->invalidFields();
$this->assertEquals($expected, $TestModel->validationErrors);
$TestModel->validationDomain = 'default';
@ -1029,6 +1042,864 @@ class ModelValidationTest extends BaseModelTest {
$this->assertFalse($Article->validates());
}
/**
* testSaveAllDeepValidateOnly
* tests the validate methods with deeper recursive data
*
* @return void
*/
public function testSaveAllDeepValidateOnly() {
$this->loadFixtures('Article', 'Comment', 'User', 'Attachment');
$TestModel = new Article();
$TestModel->hasMany['Comment']['order'] = array('Comment.created' => 'ASC');
$TestModel->hasAndBelongsToMany = array();
$TestModel->Comment->Attachment->validate['attachment'] = 'notEmpty';
$TestModel->Comment->validate['comment'] = 'notEmpty';
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => 'newuser', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertTrue($result);
$result = $TestModel->validateAssociated($data, array('deep' => true));
$this->assertTrue($result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => '', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => 'newuser', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$expected = array(
'Article' => true,
'Comment' => array(
true,
true
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => '', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$expected = array(
'Article' => true,
'Comment' => array(
false,
true
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => 'deepsaved'))
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertTrue($result);
$result = $TestModel->validateAssociated($data, array('deep' => true));
$this->assertTrue($result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => ''))
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => 'deepsave'))
)
);
$expected = array(
'Article' => true,
'Comment' => array(
true,
true
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => ''))
)
);
$expected = array(
'Article' => true,
'Comment' => array(
true,
false
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$expected = array(
'Comment' => array(
1 => array(
'Attachment' => array(
'attachment' => array('This field cannot be left blank')
)
)
)
);
$result = $TestModel->validationErrors;
$this->assertSame($expected, $result);
$data = array(
'Attachment' => array(
'attachment' => 'deepsave insert',
),
'Comment' => array(
'comment' => 'First comment deepsave insert',
'published' => 'Y',
'user_id' => 5,
'Article' => array(
'title' => 'First Article deepsave insert',
'body' => 'First Article Body deepsave insert',
'User' => array(
'user' => 'deepsave',
'password' => 'magic'
),
),
)
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertTrue($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => true));
$this->assertTrue($result);
$expected = array(
'Attachment' => true,
'Comment' => true
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertSame($expected, $result);
$data = array(
'Attachment' => array(
'attachment' => 'deepsave insert',
),
'Comment' => array(
'comment' => 'First comment deepsave insert',
'published' => 'Y',
'user_id' => 5,
'Article' => array(
'title' => 'First Article deepsave insert',
'body' => 'First Article Body deepsave insert',
'User' => array(
'user' => '',
'password' => 'magic'
),
),
)
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array(
'Comment' => array(
'Article' => array(
'User' => array(
'user' => array('This field cannot be left blank')
)
)
)
);
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => true,
'Comment' => false
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$data['Comment']['Article']['body'] = '';
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array(
'Comment' => array(
'Article' => array(
'body' => array('This field cannot be left blank')
)
)
);
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => true,
'Comment' => false
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$data['Comment']['comment'] = '';
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array(
'Comment' => array(
'comment' => array('This field cannot be left blank')
)
);
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => true,
'Comment' => false
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$data['Attachment']['attachment'] = '';
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => true));
$this->assertFalse($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array('attachment' => array('This field cannot be left blank'));
$this->assertSame($expected, $result);
$result = $TestModel->Comment->validationErrors;
$expected = array('comment' => array('This field cannot be left blank'));
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => false,
'Comment' => false
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => true));
$this->assertEquals($expected, $result);
}
/**
* testSaveAllNotDeepValidateOnly
* tests the validate methods to not validate deeper recursive data
*
* @return void
*/
public function testSaveAllNotDeepValidateOnly() {
$this->loadFixtures('Article', 'Comment', 'User', 'Attachment');
$TestModel = new Article();
$TestModel->hasMany['Comment']['order'] = array('Comment.created' => 'ASC');
$TestModel->hasAndBelongsToMany = array();
$TestModel->Comment->Attachment->validate['attachment'] = 'notEmpty';
$TestModel->Comment->validate['comment'] = 'notEmpty';
$data = array(
'Article' => array('id' => 2, 'body' => ''),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => '', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => false));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($data, array('deep' => false));
$this->assertFalse($result);
$expected = array('body' => array('This field cannot be left blank'));
$result = $TestModel->validationErrors;
$this->assertSame($expected, $result);
$data = array(
'Article' => array('id' => 2, 'body' => 'Ignore invalid user data'),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => '', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => false));
$this->assertTrue($result);
$result = $TestModel->validateAssociated($data, array('deep' => false));
$this->assertTrue($result);
$data = array(
'Article' => array('id' => 2, 'body' => 'Ignore invalid user data'),
'Comment' => array(
array('comment' => 'First new comment', 'published' => 'Y', 'User' => array('user' => '', 'password' => 'newuserpass')),
array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2)
)
);
$expected = array(
'Article' => true,
'Comment' => array(
true,
true
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => false));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => false));
$this->assertSame($expected, $result);
$data = array(
'Article' => array('id' => 2, 'body' => 'Ignore invalid attachment data'),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => ''))
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'deep' => false));
$this->assertTrue($result);
$result = $TestModel->validateAssociated($data, array('deep' => false));
$this->assertTrue($result);
$data = array(
'Article' => array('id' => 2, 'body' => 'Ignore invalid attachment data'),
'Comment' => array(
array('comment' => 'Third new comment', 'published' => 'Y', 'user_id' => 5),
array('comment' => 'Fourth new comment', 'published' => 'Y', 'user_id' => 2, 'Attachment' => array('attachment' => ''))
)
);
$expected = array(
'Article' => true,
'Comment' => array(
true,
true
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => false));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false, 'deep' => false));
$this->assertSame($expected, $result);
$expected = array();
$result = $TestModel->validationErrors;
$this->assertSame($expected, $result);
$data = array(
'Attachment' => array(
'attachment' => 'deepsave insert',
),
'Comment' => array(
'comment' => 'First comment deepsave insert',
'published' => 'Y',
'user_id' => 5,
'Article' => array(
'title' => 'First Article deepsave insert ignored',
'body' => 'First Article Body deepsave insert',
'User' => array(
'user' => '',
'password' => 'magic'
),
),
)
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => false));
$this->assertTrue($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => false));
$this->assertTrue($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array();
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => true,
'Comment' => true
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => false));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => false));
$this->assertEquals($expected, $result);
$data['Comment']['Article']['body'] = '';
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'deep' => false));
$this->assertTrue($result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('deep' => false));
$this->assertTrue($result);
$result = $TestModel->Comment->Attachment->validationErrors;
$expected = array();
$this->assertSame($expected, $result);
$expected = array(
'Attachment' => true,
'Comment' => true
);
$result = $TestModel->Comment->Attachment->saveAll($data, array('validate' => 'only', 'atomic' => false, 'deep' => false));
$this->assertEquals($expected, $result);
$result = $TestModel->Comment->Attachment->validateAssociated($data, array('atomic' => false, 'deep' => false));
$this->assertEquals($expected, $result);
}
/**
* testValidateAssociated method
*
* @return void
*/
public function testValidateAssociated() {
$this->loadFixtures('Comment', 'Attachment');
$TestModel = new Comment();
$TestModel->Attachment->validate = array('attachment' => 'notEmpty');
$data = array(
'Comment' => array(
'comment' => 'This is the comment'
),
'Attachment' => array(
'attachment' => ''
)
);
$result = $TestModel->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($data);
$this->assertFalse($result);
$TestModel->validate = array('comment' => 'notEmpty');
$record = array(
'Comment' => array(
'user_id' => 1,
'article_id' => 1,
'comment' => '',
),
'Attachment' => array(
'attachment' => ''
)
);
$result = $TestModel->saveAll($record, array('validate' => 'only'));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($record);
$this->assertFalse($result);
$fieldList = array(
'Comment' => array('id', 'article_id', 'user_id'),
'Attachment' => array('comment_id')
);
$result = $TestModel->saveAll($record, array(
'fieldList' => $fieldList, 'validate' => 'only'
));
$this->assertTrue($result);
$this->assertEmpty($TestModel->validationErrors);
$result = $TestModel->validateAssociated($record, array('fieldList' => $fieldList));
$this->assertTrue($result);
$this->assertEmpty($TestModel->validationErrors);
$TestModel = new Article();
$TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array();
$TestModel->Comment->validate = array('comment' => 'notEmpty');
$data = array(
'Article' => array('id' => 2),
'Comment' => array(
array(
'id' => 1,
'comment' => '',
'published' => 'Y',
'user_id' => 1,
),
array(
'id' => 2,
'comment' =>
'comment',
'published' => 'Y',
'user_id' => 1
),
array(
'id' => 3,
'comment' => '',
'published' => 'Y',
'user_id' => 1
)));
$result = $TestModel->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$result = $TestModel->validateAssociated($data);
$this->assertFalse($result);
$expected = array(
'Article' => true,
'Comment' => array(false, true, false)
);
$result = $TestModel->saveAll($data, array('atomic' => false, 'validate' => 'only'));
$this->assertSame($expected, $result);
$result = $TestModel->validateAssociated($data, array('atomic' => false));
$this->assertSame($expected, $result);
$expected = array('Comment' => array(
0 => array('comment' => array('This field cannot be left blank')),
2 => array('comment' => array('This field cannot be left blank'))
));
$this->assertEquals($expected['Comment'], $TestModel->Comment->validationErrors);
$model = new Comment();
$model->deleteAll(true);
$model->validate = array('comment' => 'notEmpty');
$model->Attachment->validate = array('attachment' => 'notEmpty');
$model->Attachment->bindModel(array('belongsTo' => array('Comment')));
$expected = array(
'Comment' => array('comment' => array('This field cannot be left blank')),
'Attachment' => array('attachment' => array('This field cannot be left blank'))
);
$data = array(
'Comment' => array('comment' => '', 'article_id' => 1, 'user_id' => 1),
'Attachment' => array('attachment' => '')
);
$result = $model->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$result = $model->validateAssociated($data);
$this->assertFalse($result);
$this->assertEquals($expected['Comment'], $model->validationErrors);
$this->assertEquals($expected['Attachment'], $model->Attachment->validationErrors);
}
/**
* testValidateMany method
*
* @return void
*/
public function testValidateMany() {
$TestModel = new Article();
$TestModel->validate = array('title' => 'notEmpty');
$data = array(
0 => array('title' => ''),
1 => array('title' => 'title 1'),
2 => array('title' => 'title 2'),
);
$expected = array(
0 => array('title' => array('This field cannot be left blank')),
);
$result = $TestModel->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$this->assertEquals($expected, $TestModel->validationErrors);
$result = $TestModel->validateMany($data);
$this->assertFalse($result);
$this->assertEquals($expected, $TestModel->validationErrors);
$data = array(
0 => array('title' => 'title 0'),
1 => array('title' => ''),
2 => array('title' => 'title 2'),
);
$expected = array(
1 => array('title' => array('This field cannot be left blank')),
);
$result = $TestModel->saveAll($data, array('validate' => 'only'));
$this->assertFalse($result);
$this->assertEquals($expected, $TestModel->validationErrors);
$result = $TestModel->validateMany($data);
$this->assertFalse($result);
$this->assertEquals($expected, $TestModel->validationErrors);
}
/**
* testGetMethods method
*
* @return void
*/
public function testGetMethods() {
$this->loadFixtures('Article', 'Comment');
$TestModel = new Article();
$Validator = $TestModel->validator();
$result = $Validator->getMethods();
$expected = array_map('strtolower', get_class_methods('Article'));
$this->assertEquals($expected, array_keys($result));
}
/**
* testSetValidationDomain method
*
* @return void
*/
public function testSetValidationDomain() {
$this->loadFixtures('Article', 'Comment');
$TestModel = new Article();
$Validator = $TestModel->validator();
$result = $Validator->setValidationDomain('default');
$this->assertEquals('default', $TestModel->validationDomain);
$result = $Validator->setValidationDomain('other');
$this->assertEquals('other', $TestModel->validationDomain);
}
/**
* testGetModel method
*
* @return void
*/
public function testGetModel() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$result = $Validator->getModel();
$this->assertInstanceOf('Article', $result);
}
/**
* Tests it is possible to get validation sets for a field using an array inteface
*
* @return void
*/
public function testArrayAccessGet() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$titleValidator = $Validator['title'];
$this->assertEquals('title', $titleValidator->field);
$this->assertCount(1, $titleValidator->getRules());
$rule = current($titleValidator->getRules());
$this->assertEquals('notEmpty', $rule->rule);
$titleValidator = $Validator['body'];
$this->assertEquals('body', $titleValidator->field);
$this->assertCount(1, $titleValidator->getRules());
$rule = current($titleValidator->getRules());
$this->assertEquals('notEmpty', $rule->rule);
$titleValidator = $Validator['user_id'];
$this->assertEquals('user_id', $titleValidator->field);
$this->assertCount(1, $titleValidator->getRules());
$rule = current($titleValidator->getRules());
$this->assertEquals('numeric', $rule->rule);
}
/**
* Tests it is possible to check for validation sets for a field using an array inteface
*
* @return void
*/
public function testArrayAccessExists() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$this->assertTrue(isset($Validator['title']));
$this->assertTrue(isset($Validator['body']));
$this->assertTrue(isset($Validator['user_id']));
$this->assertFalse(isset($Validator['other']));
}
/**
* Tests it is possible to set validation rules for a field using an array inteface
*
* @return void
*/
public function testArrayAccessSet() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$set = array(
'numeric' => array('rule' => 'numeric', 'allowEmpty' => false),
'range' => array('rule' => array('between', 1, 5), 'allowEmpty' => false),
);
$Validator['other'] = $set;
$rules = $Validator['other'];
$this->assertEquals('other', $rules->field);
$validators = $rules->getRules();
$this->assertCount(2, $validators);
$this->assertEquals('numeric', $validators['numeric']->rule);
$this->assertEquals(array('between', 1, 5), $validators['range']->rule);
$Validator['new'] = new CakeValidationSet('new', $set, array());
$rules = $Validator['new'];
$this->assertEquals('new', $rules->field);
$validators = $rules->getRules();
$this->assertCount(2, $validators);
$this->assertEquals('numeric', $validators['numeric']->rule);
$this->assertEquals(array('between', 1, 5), $validators['range']->rule);
}
/**
* Tests it is possible to unset validation rules
*
* @return void
*/
public function testArrayAccessUset() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$this->assertTrue(isset($Validator['title']));
unset($Validator['title']);
$this->assertFalse(isset($Validator['title']));
}
/**
* Tests it is possible to iterate a validation object
*
* @return void
*/
public function testIterator() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$i = 0;
foreach ($Validator as $field => $rules) {
if ($i === 0) {
$this->assertEquals('user_id', $field);
}
if ($i === 1) {
$this->assertEquals('title', $field);
}
if ($i === 2) {
$this->assertEquals('body', $field);
}
$this->assertInstanceOf('CakeValidationSet', $rules);
$i++;
}
$this->assertEquals(3, $i);
}
/**
* Tests countable interface in ModelValidator
*
* @return void
*/
public function testCount() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$this->assertCount(3, $Validator);
$set = array(
'numeric' => array('rule' => 'numeric', 'allowEmpty' => false),
'range' => array('rule' => array('between', 1, 5), 'allowEmpty' => false),
);
$Validator['other'] = $set;
$this->assertCount(4, $Validator);
unset($Validator['title']);
$this->assertCount(3, $Validator);
unset($Validator['body']);
$this->assertCount(2, $Validator);
}
/**
* Tests it is possible to add validation rules
*
* @return void
*/
public function testAddRule() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$set = array(
'numeric' => array('rule' => 'numeric', 'allowEmpty' => false),
'range' => array('rule' => array('between', 1, 5), 'allowEmpty' => false),
);
$Validator->add('other', 'numeric', array('rule' => 'numeric', 'allowEmpty' => false));
$Validator->add('other', 'range', array('rule' => array('between', 1, 5), 'allowEmpty' => false));
$rules = $Validator['other'];
$this->assertEquals('other', $rules->field);
$validators = $rules->getRules();
$this->assertCount(2, $validators);
$this->assertEquals('numeric', $validators['numeric']->rule);
$this->assertEquals(array('between', 1, 5), $validators['range']->rule);
}
/**
* Tests it is possible to remove validation rules
*
* @return void
*/
public function testRemoveRule() {
$TestModel = new Article();
$Validator = $TestModel->validator();
$this->assertTrue(isset($Validator['title']));
$Validator->remove('title');
$this->assertFalse(isset($Validator['title']));
$Validator->add('other', 'numeric', array('rule' => 'numeric', 'allowEmpty' => false));
$Validator->add('other', 'range', array('rule' => array('between', 1, 5), 'allowEmpty' => false));
$this->assertTrue(isset($Validator['other']));
$Validator->remove('other', 'numeric');
$this->assertTrue(isset($Validator['other']));
$this->assertFalse(isset($Validator['other']['numeric']));
$this->assertTrue(isset($Validator['other']['range']));
}
/**
* Tests validation callbacks are triggered
*
* @return void
*/
public function testValidateCallbacks() {
$TestModel = $this->getMock('Article', array('beforeValidate', 'afterValidate'));
$TestModel->expects($this->once())->method('beforeValidate');
$TestModel->expects($this->once())->method('afterValidate');
$TestModel->set(array('title' => '', 'body' => 'body'));
$TestModel->validates();
}
/**
* Tests that altering data in a beforeValidate callback will lead to saving those
* values in database
@ -1054,6 +1925,7 @@ class ModelValidationTest extends BaseModelTest {
);
$result = $model->saveAll($data, array('validate' => 'first'));
$this->assertTrue($result);
$this->assertFalse($model->findMethods['unPublished'], 'beforeValidate was run twice');
$model->findMethods['unPublished'] = true;
@ -1074,8 +1946,6 @@ class ModelValidationTest extends BaseModelTest {
array('body' => 'foo4')
);
$result = $model->saveAll($data, array('validate' => 'first'));
$this->assertTrue($result);
$result = $model->saveAll($data, array('validate' => 'first', 'deep' => true));
$this->assertTrue($result);
@ -1165,4 +2035,4 @@ class ModelValidationTest extends BaseModelTest {
$this->assertEquals($expected['Article'], $result['Article']);
}
}
}

View file

@ -107,7 +107,7 @@ class ModelWriteTest extends BaseModelTest {
$testResult = $Article->find('first', array('conditions' => array('Article.title' => 'Test Title')));
$this->assertEquals($testResult['Article']['title'], $data['Article']['title']);
$this->assertEquals($data['Article']['title'], $testResult['Article']['title']);
$this->assertEquals('2008-01-01 00:00:00', $testResult['Article']['created']);
}
@ -151,8 +151,8 @@ class ModelWriteTest extends BaseModelTest {
$TestModel->save(array('title' => 'Test record'));
$result = $TestModel->findByTitle('Test record');
$this->assertEquals(
array_keys($result['Uuid']),
array('id', 'title', 'count', 'created', 'updated')
array('id', 'title', 'count', 'created', 'updated'),
array_keys($result['Uuid'])
);
$this->assertEquals(36, strlen($result['Uuid']['id']));
}
@ -173,8 +173,8 @@ class ModelWriteTest extends BaseModelTest {
$TestModel->save(array('title' => 'Test record', 'id' => null));
$result = $TestModel->findByTitle('Test record');
$this->assertEquals(
array_keys($result['Uuid']),
array('id', 'title', 'count', 'created', 'updated')
array('id', 'title', 'count', 'created', 'updated'),
array_keys($result['Uuid'])
);
$this->assertEquals(36, strlen($result['Uuid']['id']));
}
@ -2345,7 +2345,7 @@ class ModelWriteTest extends BaseModelTest {
'User' => array(
'user' => 'updated user'
)));
$this->assertEquals($TestModel->id, $id);
$this->assertEquals($id, $TestModel->id);
$result = $TestModel->findById($id);
$this->assertEquals('updated user', $result['User']['user']);
@ -2940,7 +2940,7 @@ class ModelWriteTest extends BaseModelTest {
$model->Attachment->validate = array('attachment' => 'notEmpty');
$model->Attachment->bindModel(array('belongsTo' => array('Comment')));
$this->assertEquals($model->saveAll(
$result = $model->saveAll(
array(
'Comment' => array(
'comment' => '',
@ -2950,23 +2950,14 @@ class ModelWriteTest extends BaseModelTest {
'Attachment' => array('attachment' => '')
),
array('validate' => 'first')
), false);
);
$this->assertEquals(false, $result);
$expected = array(
'Comment' => array('comment' => array('This field cannot be left blank')),
'Attachment' => array('attachment' => array('This field cannot be left blank'))
);
$this->assertEquals($expected['Comment'], $model->validationErrors);
$this->assertEquals($expected['Attachment'], $model->Attachment->validationErrors);
$this->assertFalse($model->saveAll(
array(
'Comment' => array('comment' => '', 'article_id' => 1, 'user_id' => 1),
'Attachment' => array('attachment' => '')
),
array('validate' => 'only')
));
$this->assertEquals($expected['Comment'], $model->validationErrors);
$this->assertEquals($expected['Attachment'], $model->Attachment->validationErrors);
}
/**
@ -4329,7 +4320,7 @@ class ModelWriteTest extends BaseModelTest {
$this->assertTrue(Set::matches('/Post[2][title=Just update the title]', $result));
}
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
$TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric');
$data = array(
@ -4400,7 +4391,7 @@ class ModelWriteTest extends BaseModelTest {
$result[3]['Post']['updated'], $result[3]['Post']['created']
);
$this->assertEquals($expected, $result);
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
$data = array(
array(
@ -4422,7 +4413,7 @@ class ModelWriteTest extends BaseModelTest {
$result[3]['Post']['updated'], $result[3]['Post']['created']
);
$this->assertEquals($expected, $result);
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
}
/**
@ -5037,7 +5028,7 @@ class ModelWriteTest extends BaseModelTest {
$model->Attachment->validate = array('attachment' => 'notEmpty');
$model->Attachment->bindModel(array('belongsTo' => array('Comment')));
$this->assertEquals($model->saveAssociated(
$result = $model->saveAssociated(
array(
'Comment' => array(
'comment' => '',
@ -5046,7 +5037,8 @@ class ModelWriteTest extends BaseModelTest {
),
'Attachment' => array('attachment' => '')
)
), false);
);
$this->assertFalse($result);
$expected = array(
'Comment' => array('comment' => array('This field cannot be left blank')),
'Attachment' => array('attachment' => array('This field cannot be left blank'))
@ -5688,7 +5680,7 @@ class ModelWriteTest extends BaseModelTest {
$this->assertTrue(Set::matches('/Post[2][title=Just update the title]', $result));
}
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
$TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric');
$data = array(
@ -5746,7 +5738,7 @@ class ModelWriteTest extends BaseModelTest {
'published' => 'N',
)));
$this->assertEquals($expected, $result);
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
$data = array(
array(
@ -5768,7 +5760,7 @@ class ModelWriteTest extends BaseModelTest {
'order' => 'Post.id ASC'
));
$this->assertEquals($expected, $result);
$this->assertEquals($TestModel->validationErrors, $errors);
$this->assertEquals($errors, $TestModel->validationErrors);
}
/**
@ -5876,8 +5868,8 @@ class ModelWriteTest extends BaseModelTest {
$result = $model->find('all');
$this->assertEquals(
$result[0]['Article']['title'],
'Post with Author saveAlled from comment'
'Post with Author saveAlled from comment',
$result[0]['Article']['title']
);
$this->assertEquals('Only new comment', $result[0]['Comment'][0]['comment']);
}

View file

@ -0,0 +1,164 @@
<?php
/**
* CakeValidationRuleTest file
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
* @package Cake.Test.Case.Model.Validator
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('CakeValidationRule', 'Model/Validator');
/**
* CakeValidationRuleTest
*
* @package Cake.Test.Case.Model.Validator
*/
class CakeValidationRuleTest extends CakeTestCase {
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
}
/**
* Auxiliary method to test custom validators
*
* @return boolean
**/
public function myTestRule() {
return false;
}
/**
* Auxiliary method to test custom validators
*
* @return boolean
**/
public function myTestRule2() {
return true;
}
/**
* Auxiliary method to test custom validators
*
* @return string
**/
public function myTestRule3() {
return 'string';
}
/**
* Test isValid method
*
* @return void
*/
public function testIsValid() {
$def = array('rule' => 'notEmpty', 'message' => 'Can not be empty');
$data = array(
'fieldName' => ''
);
$methods = array();
$Rule = new CakeValidationRule($def);
$Rule->process('fieldName', $data, $methods);
$this->assertFalse($Rule->isValid());
$data = array('fieldName' => 'not empty');
$Rule->process('fieldName', $data, $methods);
$this->assertTrue($Rule->isValid());
}
/**
* tests that passing custom validation methods work
*
* @return void
*/
public function testCustomMethods() {
$def = array('rule' => 'myTestRule');
$data = array(
'fieldName' => 'some data'
);
$methods = array('mytestrule' => array($this, 'myTestRule'));
$Rule = new CakeValidationRule($def);
$Rule->process('fieldName', $data, $methods);
$this->assertFalse($Rule->isValid());
$methods = array('mytestrule' => array($this, 'myTestRule2'));
$Rule->process('fieldName', $data, $methods);
$this->assertTrue($Rule->isValid());
$methods = array('mytestrule' => array($this, 'myTestRule3'));
$Rule->process('fieldName', $data, $methods);
$this->assertFalse($Rule->isValid());
}
/**
* Test isRequired method
*
* @return void
*/
public function testIsRequired() {
$def = array('rule' => 'notEmpty', 'required' => true);
$Rule = new CakeValidationRule($def);
$this->assertTrue($Rule->isRequired());
$def = array('rule' => 'notEmpty', 'required' => false);
$Rule = new CakeValidationRule($def);
$this->assertFalse($Rule->isRequired());
$def = array('rule' => 'notEmpty', 'required' => 'create');
$Rule = new CakeValidationRule($def);
$this->assertTrue($Rule->isRequired());
$def = array('rule' => 'notEmpty', 'required' => 'update');
$Rule = new CakeValidationRule($def);
$this->assertFalse($Rule->isRequired());
$Rule->isUpdate(true);
$this->assertTrue($Rule->isRequired());
}
/**
* Test isEmptyAllowed method
*
* @return void
*/
public function testIsEmplyAllowed() {
$def = array('rule' => 'aRule', 'allowEmpty' => true);
$Rule = new CakeValidationRule($def);
$this->assertTrue($Rule->isEmptyAllowed());
$def = array('rule' => 'aRule', 'allowEmpty' => false);
$Rule = new CakeValidationRule($def);
$this->assertFalse($Rule->isEmptyAllowed());
$def = array('rule' => 'notEmpty', 'allowEmpty' => false, 'on' => 'update');
$Rule = new CakeValidationRule($def);
$this->assertTrue($Rule->isEmptyAllowed());
$Rule->isUpdate(true);
$this->assertFalse($Rule->isEmptyAllowed());
$def = array('rule' => 'notEmpty', 'allowEmpty' => false, 'on' => 'create');
$Rule = new CakeValidationRule($def);
$this->assertFalse($Rule->isEmptyAllowed());
$Rule->isUpdate(true);
$this->assertTrue($Rule->isEmptyAllowed());
}
}

View file

@ -0,0 +1,316 @@
<?php
/**
* CakeValidationSetTest file
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
* @package Cake.Test.Case.Model.Validator
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('CakeValidationSet', 'Model/Validator');
/**
* CakeValidationSetTest
*
* @package Cake.Test.Case.Model.Validator
*/
class CakeValidationSetTest extends CakeTestCase {
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
}
/**
* testValidate method
*
* @return void
*/
public function testValidate() {
$Field = new CakeValidationSet('title', 'notEmpty');
$data = array(
'title' => '',
'body' => 'a body'
);
$result = $Field->validate($data);
$expected = array('This field cannot be left blank');
$this->assertEquals($expected, $result);
$Field = new CakeValidationSet('body', 'notEmpty');
$result = $Field->validate($data);
$this->assertEmpty($result);
$Field = new CakeValidationSet('nothere', array('notEmpty' => array('rule' => 'notEmpty', 'required' => true)));
$result = $Field->validate($data);
$expected = array('notEmpty');
$this->assertEquals($expected, $result);
}
/**
* testGetRule method
*
* @return void
*/
public function testGetRule() {
$rules = array('notEmpty' => array('rule' => 'notEmpty', 'message' => 'Can not be empty'));
$Field = new CakeValidationSet('title', $rules);
$data = array(
'title' => '',
'body' => 'a body'
);
$result = $Field->getRule('notEmpty');
$this->assertInstanceOf('CakeValidationRule', $result);
$this->assertEquals('notEmpty', $result->rule);
$this->assertEquals(null, $result->required);
$this->assertEquals(false, $result->allowEmpty);
$this->assertEquals(null, $result->on);
$this->assertEquals(true, $result->last);
$this->assertEquals('Can not be empty', $result->message);
}
/**
* testGetRules method
*
* @return void
*/
public function testGetRules() {
$rules = array('notEmpty' => array('rule' => 'notEmpty', 'message' => 'Can not be empty'));
$Field = new CakeValidationSet('title', $rules);
$result = $Field->getRules();
$this->assertEquals(array('notEmpty'), array_keys($result));
$this->assertInstanceOf('CakeValidationRule', $result['notEmpty']);
}
/**
* testSetRule method
*
* @return void
*/
public function testSetRule() {
$rules = array('notEmpty' => array('rule' => 'notEmpty', 'message' => 'Can not be empty'));
$Field = new CakeValidationSet('title', $rules);
$Rule = new CakeValidationRule($rules['notEmpty']);
$this->assertEquals($Rule, $Field->getRule('notEmpty'));
$rules = array('validEmail' => array('rule' => 'email', 'message' => 'Invalid email'));
$Rule = new CakeValidationRule($rules['validEmail']);
$Field->setRule('validEmail', $Rule);
$result = $Field->getRules();
$this->assertEquals(array('notEmpty', 'validEmail'), array_keys($result));
$rules = array('validEmail' => array('rule' => 'email', 'message' => 'Other message'));
$Rule = new CakeValidationRule($rules['validEmail']);
$Field->setRule('validEmail', $Rule);
$result = $Field->getRules();
$this->assertEquals(array('notEmpty', 'validEmail'), array_keys($result));
$result = $Field->getRule('validEmail');
$this->assertInstanceOf('CakeValidationRule', $result);
$this->assertEquals('email', $result->rule);
$this->assertEquals(null, $result->required);
$this->assertEquals(false, $result->allowEmpty);
$this->assertEquals(null, $result->on);
$this->assertEquals(true, $result->last);
$this->assertEquals('Other message', $result->message);
}
/**
* testSetRules method
*
* @return void
*/
public function testSetRules() {
$rule = array('notEmpty' => array('rule' => 'notEmpty', 'message' => 'Can not be empty'));
$Field = new CakeValidationSet('title', $rule);
$RuleEmpty = new CakeValidationRule($rule['notEmpty']);
$rule = array('validEmail' => array('rule' => 'email', 'message' => 'Invalid email'));
$RuleEmail = new CakeValidationRule($rule['validEmail']);
$rules = array('validEmail' => $RuleEmail);
$Field->setRules($rules, false);
$result = $Field->getRules();
$this->assertEquals(array('validEmail'), array_keys($result));
$rules = array('notEmpty' => $RuleEmpty);
$Field->setRules($rules, true);
$result = $Field->getRules();
$this->assertEquals(array('validEmail', 'notEmpty'), array_keys($result));
}
/**
* Tests getting a rule from the set using array access
*
* @return void
*/
public function testArrayAccessGet() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
$rule = $Set['notEmpty'];
$this->assertInstanceOf('CakeValidationRule', $rule);
$this->assertEquals('notEmpty', $rule->rule);
$rule = $Set['numeric'];
$this->assertInstanceOf('CakeValidationRule', $rule);
$this->assertEquals('numeric', $rule->rule);
$rule = $Set['other'];
$this->assertInstanceOf('CakeValidationRule', $rule);
$this->assertEquals(array('other', 1), $rule->rule);
}
/**
* Tests checking a rule from the set using array access
*
* @return void
*/
public function testArrayAccessExists() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
$this->assertTrue(isset($Set['notEmpty']));
$this->assertTrue(isset($Set['numeric']));
$this->assertTrue(isset($Set['other']));
$this->assertFalse(isset($Set['fail']));
}
/**
* Tests setting a rule in the set using array access
*
* @return void
*/
public function testArrayAccessSet() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
));
$this->assertFalse(isset($Set['other']));
$Set['other'] = array('rule' => array('other', 1));
$rule = $Set['other'];
$this->assertInstanceOf('CakeValidationRule', $rule);
$this->assertEquals(array('other', 1), $rule->rule);
$this->assertFalse(isset($Set['numeric']));
$Set['numeric'] = new CakeValidationRule(array('rule' => 'numeric'));
$rule = $Set['numeric'];
$this->assertInstanceOf('CakeValidationRule', $rule);
$this->assertEquals('numeric', $rule->rule);
}
/**
* Tests unseting a rule from the set using array access
*
* @return void
*/
public function testArrayAccessUnset() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
unset($Set['notEmpty']);
$this->assertFalse(isset($Set['notEmpty']));
unset($Set['numeric']);
$this->assertFalse(isset($Set['notEmpty']));
unset($Set['other']);
$this->assertFalse(isset($Set['notEmpty']));
}
/**
* Tests it is possible to iterate a validation set object
*
* @return void
*/
public function testIterator() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
$i = 0;
foreach ($Set as $name => $rule) {
if ($i === 0) {
$this->assertEquals('notEmpty', $name);
}
if ($i === 1) {
$this->assertEquals('numeric', $name);
}
if ($i === 2) {
$this->assertEquals('other', $name);
}
$this->assertInstanceOf('CakeValidationRule', $rule);
$i++;
}
$this->assertEquals(3, $i);
}
/**
* Tests countable interface
*
* @return void
*/
public function testCount() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
$this->assertCount(3, $Set);
unset($Set['other']);
$this->assertCount(2, $Set);
}
/**
* Test removeRule method
*
* @return void
*/
public function testRemoveRule() {
$Set = new CakeValidationSet('title', array(
'notEmpty' => array('rule' => 'notEmpty', 'required' => true),
'numeric' => array('rule' => 'numeric'),
'other' => array('rule' => array('other', 1)),
));
$Set->removeRule('notEmpty');
$this->assertFalse(isset($Set['notEmpty']));
$Set->removeRule('numeric');
$this->assertFalse(isset($Set['numeric']));
$Set->removeRule('other');
$this->assertFalse(isset($Set['other']));
}
}

View file

@ -218,7 +218,7 @@ class FormHelper extends AppHelper {
if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) {
$validates = array();
if (!empty($object->validate)) {
foreach ($object->validate as $validateField => $validateProperties) {
foreach ($object->validator() as $validateField => $validateProperties) {
if ($this->_isRequiredField($validateProperties)) {
$validates[$validateField] = true;
}
@ -240,61 +240,17 @@ class FormHelper extends AppHelper {
/**
* Returns if a field is required to be filled based on validation properties from the validating object.
*
* @param array $validateProperties
* @param CakeValidationSet $validationRules
* @return boolean true if field is required to be filled, false otherwise
*/
protected function _isRequiredField($validateProperties) {
$required = false;
if (is_string($validateProperties)) {
return true;
} elseif (is_array($validateProperties)) {
$dims = Hash::dimensions($validateProperties);
if ($dims == 1 || ($dims == 2 && isset($validateProperties['rule']))) {
$validateProperties = array($validateProperties);
}
foreach ($validateProperties as $rule => $validateProp) {
$isRequired = $this->_isRequiredRule($validateProp);
if ($isRequired === false) {
continue;
}
$rule = isset($validateProp['rule']) ? $validateProp['rule'] : false;
$required = $rule || empty($validateProp);
if ($required) {
break;
}
protected function _isRequiredField($validationRules) {
foreach ($validationRules as $rule) {
$rule->isUpdate($this->requestType === 'put');
if (!$rule->isEmptyAllowed()) {
return true;
}
}
return $required;
}
/**
* Checks if the field is required by the 'on' key in validation properties.
* If no 'on' key is present in validation props, this method returns true.
*
* @param array $validateProp
* @return mixed. Boolean for required
*/
protected function _isRequiredRule($validateProp) {
if (isset($validateProp['on'])) {
if (
($validateProp['on'] == 'create' && $this->requestType != 'post') ||
($validateProp['on'] == 'update' && $this->requestType != 'put')
) {
return false;
}
}
if (
isset($validateProp['allowEmpty']) &&
$validateProp['allowEmpty'] === true
) {
return false;
}
if (isset($validateProp['required']) && empty($validateProp['required'])) {
return false;
}
return true;
return false;
}
/**