Browse Source

Moving all validation logic into a new ModelValidator object.

Thomas Ploch 14 years ago
parent
commit
324684c14f

+ 1 - 1
lib/Cake/Controller/Controller.php

@@ -528,7 +528,7 @@ class Controller extends Object implements CakeEventListener {
 	}
 
 /**
- * Merge components, helpers, and uses vars from 
+ * Merge components, helpers, and uses vars from
  * Controller::$_mergeParent and PluginAppController.
  *
  * @return void

+ 93 - 286
lib/Cake/Model/Model.php

@@ -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');
@@ -619,6 +620,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.
  *
  * If `$id` is an array it can be used to pass several options into the model.
@@ -723,6 +731,7 @@ class Model extends Object implements CakeEventListener {
 
 		$this->_createLinks();
 		$this->Behaviors->init($this->alias, $this->actsAs);
+		$this->setValidator();
 	}
 
 /**
@@ -969,9 +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);
-						} else {
+							list($plugin, $assoc) = pluginSplit($assoc, true);
+							$this->{$type}[$assoc] = array('className' => $plugin . $assoc);						} else {
 							$this->{$type}[$assoc] = $value;
 						}
 					}
@@ -1445,7 +1453,7 @@ class Model extends Object implements CakeEventListener {
 		$defaults = array();
 		$this->id = false;
 		$this->data = array();
-		$this->validationErrors = array();
+		$this->validationErrors = $this->getValidator()->validationErrors = array();
 
 		if ($data !== null && $data !== false) {
 			foreach ($this->schema() as $field => $properties) {
@@ -2032,7 +2040,7 @@ class Model extends Object implements CakeEventListener {
 		}
 
 		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
-		$this->validationErrors = $validationErrors = array();
+		$validationErrors = array();
 
 		if (empty($data) && $options['validate'] !== false) {
 			$result = $this->save($data, $options);
@@ -2108,30 +2116,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 {
-				$validates = $this->create($record) && $this->validates($options);
-			}
-			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->getValidator()->validateMany($data, $options);
 	}
 
 /**
@@ -2166,7 +2151,7 @@ class Model extends Object implements CakeEventListener {
 		}
 
 		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
-		$this->validationErrors = $validationErrors = array();
+		$validationErrors = array();
 
 		if (empty($data) && $options['validate'] !== false) {
 			$result = $this->save($data, $options);
@@ -2306,53 +2291,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();
-		if (!($this->create($data) && $this->validates($options))) {
-			$validationErrors[$this->alias] = $this->validationErrors;
-			$return[$this->alias] = false;
-		} else {
-			$return[$this->alias] = true;
-		}
-		$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->getValidator()->validateAssociated($data, $options);
 	}
 
 /**
@@ -3039,14 +2978,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->getValidator()->validates($options);
 	}
 
 /**
@@ -3057,203 +2989,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) {
-								$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->getValidator()->invalidFields($options);
 	}
 
 /**
@@ -3266,10 +3002,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->getValidator()->invalidate($field, $value);
 	}
 
 /**
@@ -3616,4 +3349,78 @@ class Model extends Object implements CakeEventListener {
 		}
 	}
 
+/**
+ * Creates a ModelValidator instance from Model::validatorClass
+ *
+ * @return void
+ * @throws MissingValidatorException
+ * @throws InvalidValidatorException
+ */
+	public function setValidator($validator = null) {
+		if (is_object($validator) && $this->_isValidValidator($validator)) {
+			$this->_validator = $validator;
+			return $this;
+		}
+
+		if (is_null($validator) && is_null($this->validatorClass)) {
+			$this->validatorClass = ModelValidator::DEFAULT_VALIDATOR;
+		} elseif (is_string($validator)) {
+			$this->validatorClass = $validator;
+		}
+
+		if (!$this->_loadValidator($this->validatorClass)) {
+			throw new MissingValidatorException(array($this->validatorClass));
+		}
+
+		if (!$this->_isValidValidator($this->_validator)) {
+			$this->_validator = null;
+			throw new InvalidValidatorException(array($this->validatorClass, ModelValidator::DEFAULT_VALIDATOR));
+		}
+
+		return $this;
+	}
+
+/**
+ * Returns the currently set ModelValidator instance
+ *
+ * @return ModelValidator
+ */
+	public function getValidator() {
+		return $this->_validator;
+	}
+
+/**
+ * Tries to load a validator and returns true if the class could be found, false otherwise.
+ *
+ * @param string $validatorClass The class to be loaded
+ * @return boolean True if the class was found, false otherwise
+ */
+	protected function _loadValidator($validatorClass) {
+		list($plugin, $class) = pluginSplit($validatorClass, true);
+		unset($validatorClass);
+
+		$location = $plugin . 'Model';
+		App::uses($class, $location);
+
+		if (!class_exists($class, true)) {
+			return false;
+		}
+		$this->_validator = new $class($this);
+
+		return true;
+	}
+
+/**
+ * Checks if the passed in validator instance is either an instance or subclass of ModelValidator.
+ *
+ * @param $validator
+ * @return boolean True if the instance is valid, false otherwise
+ */
+	protected function _isValidValidator($validator) {
+		if (!($validator instanceof ModelValidator) && !is_subclass_of($validator, ModelValidator::DEFAULT_VALIDATOR)) {
+			return false;
+		}
+		return true;
+	}
+
 }

+ 543 - 0
lib/Cake/Model/ModelValidator.php

@@ -0,0 +1,543 @@
+<?php
+/**
+ * ModelValidator.
+ *
+ * Provides the Model validation logic.
+ *
+ * PHP versions 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @package       Cake.Model
+ * @since         CakePHP(tm) v 0.10.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+App::uses('CakeField', 'Model/Validator');
+App::uses('CakeRule', 'Model/Validator');
+
+/**
+ * ModelValidator object.
+ *
+ * @package       Cake.Model
+ * @link          http://book.cakephp.org/2.0/en/data-validation.html
+ */
+class ModelValidator {
+
+/**
+ * The default ModelValidator class name
+ *
+ * @var string
+ */
+	const DEFAULT_VALIDATOR = 'ModelValidator';
+
+/**
+ * The default validation domain
+ *
+ * @var string
+ */
+	const DEFAULT_DOMAIN = 'default';
+
+/**
+ * Holds the data array from the Model
+ *
+ * @var array
+ */
+	public $data = array();
+
+/**
+ * The default ValidationDomain
+ *
+ * @var string
+ */
+	public $validationDomain = 'default';
+
+/**
+ * Holds the validationErrors
+ *
+ * @var array
+ */
+	public $validationErrors = array();
+
+/**
+ * Holds the options
+ *
+ * @var array
+ */
+	public $options = array();
+
+/**
+ * Holds the ModelFields
+ *
+ * @var array
+ */
+	protected $_fields = array();
+
+/**
+ * Holds the reference to the model the Validator is attached to
+ *
+ * @var Model
+ */
+	protected $_model = array();
+
+/**
+ * The validators $validate property
+ *
+ * @var array
+ */
+	protected $_validate = array();
+
+/**
+ * Holds the available custom callback 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 _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()) {
+		$this->validationErrors = array();
+		$errors = $this->invalidFields($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.
+ *
+ * @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()) {
+		$options = array_merge(array('atomic' => true, 'deep' => false), $options);
+		$this->validationErrors = $this->getModel()->validationErrors = $return = array();
+		if (!($this->getModel()->create($data) && $this->validates($options))) {
+			$this->validationErrors = array($this->getModel()->alias => $this->validationErrors);
+			$return[$this->getModel()->alias] = false;
+		} else {
+			$return[$this->getModel()->alias] = true;
+		}
+		$associations = $this->getModel()->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->getModel()->{$association}->getValidator()->validateAssociated($values, $options);
+					} else {
+						$validates = $this->getModel()->{$association}->create($values) !== null && $this->getModel()->{$association}->getValidator()->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->getModel()->{$association}->getValidator()->validateMany($values, $options);
+					$return[$association] = $validates;
+				}
+				if (!$validates || (is_array($validates) && in_array(false, $validates, true))) {
+					$this->validationErrors[$association] = $this->getModel()->{$association}->getValidator()->validationErrors;
+				}
+			}
+		}
+
+		if (isset($this->validationErrors[$this->getModel()->alias])) {
+			$this->validationErrors = $this->validationErrors[$this->getModel()->alias];
+		}
+		$this->getModel()->validationErrors = $this->validationErrors;
+		if (!$options['atomic']) {
+			return $return;
+		}
+		if ($return[$this->getModel()->alias] === false || !empty($this->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.
+ *
+ * @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()) {
+		$options = array_merge(array('atomic' => true, 'deep' => false), $options);
+		$this->validationErrors = $validationErrors = $this->getModel()->validationErrors = $return = array();
+		foreach ($data as $key => $record) {
+			if ($options['deep']) {
+				$validates = $this->validateAssociated($record, $options);
+			} else {
+				$validates = $this->getModel()->create($record) && $this->validates($options);
+			}
+			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 = $this->getModel()->validationErrors = $validationErrors;
+		if (!$options['atomic']) {
+			return $return;
+		}
+		if (empty($this->validationErrors)) {
+			return true;
+		}
+		return false;
+	}
+
+/**
+ * Returns an array of fields that have failed validation. On the current model.
+ *
+ * @param string $options An optional array of custom options to be made available in the beforeValidate callback
+ * @return array Array of invalid fields
+ * @see Model::validates()
+ */
+	public function invalidFields($options = array()) {
+		if (!$this->propagateBeforeValidate($options)) {
+			return false;
+		}
+		$this->data = array();
+
+		$this->setOptions($options);
+
+		if (!$this->setFields()) {
+			return $this->getModel()->validationErrors = $this->validationErrors;
+		}
+
+		$this->getData();
+		$this->getMethods();
+		$this->setValidationDomain();
+
+		foreach ($this->_fields as $field) {
+			$field->validate();
+		}
+
+		$this->setFields(true);
+
+		return $this->getModel()->validationErrors = $this->validationErrors;
+	}
+
+/**
+ * Marks a field as invalid, optionally setting the name of validation
+ * rule (in case of multiple validation for field) that was broken.
+ *
+ * @param string $field The name of the field to invalidate
+ * @param mixed $value Name of validation rule that was not failed, or validation message to
+ *    be returned. If no validation key is provided, defaults to true.
+ * @return void
+ */
+	public function invalidate($field, $value = true) {
+		if (!is_array($this->validationErrors)) {
+			$this->validationErrors = array();
+		}
+		$this->validationErrors[$field][] = $this->getModel()->validationErrors[$field][] = $value;
+	}
+
+/**
+ * Gets the current data from the model and sets it to $this->data
+ *
+ * @param string $field [optional]
+ * @return array The data
+ */
+	public function getData($field = null, $all = false) {
+		if (!empty($this->data)) {
+			if ($field !== null && isset($this->data[$field])) {
+				return $this->data[$field];
+			}
+			return $this->data;
+		}
+
+		$this->data = $this->_model->data;
+		if (FALSE === $all && isset($this->data[$this->_model->alias])) {
+			$this->data = $this->data[$this->_model->alias];
+		} elseif (!is_array($this->data)) {
+			$this->data = array();
+		}
+
+		if ($field !== null && isset($this->data[$field])) {
+			return $this->data[$field];
+		}
+
+		return $this->data;
+	}
+
+/**
+ * Gets all possible custom methods from the Model, Behaviors and the Validator.
+ * If $type is null (default) gets all methods. If $type is one of 'model', 'behaviors' or 'validator',
+ * gets the corresponding methods.
+ *
+ * @param string $type [optional] The methods type to get. Defaults to null
+ * @return array The requested methods
+ */
+	public function getMethods($type = null) {
+		if (!empty($this->_methods)) {
+			if ($type !== null && !empty($this->_methods[$type])) {
+				return $this->_methods[$type];
+			}
+			return $this->_methods;
+		}
+
+		$this->_methods['model'] = array_map('strtolower', get_class_methods($this->_model));
+		$this->_methods['behaviors'] = array_keys($this->_model->Behaviors->methods());
+		$this->_methods['validator'] = get_class_methods($this);
+
+		if ($type !== null && !empty($this->_methods[$type])) {
+			return $this->_methods[$type];
+		}
+		unset($type);
+
+		return $this->_methods;
+	}
+
+/**
+ * Gets all fields if $name is null (default), or the field for fieldname $name if it's found.
+ *
+ * @param string $name [optional] The fieldname to fetch. Defaults to null.
+ * @return array|ModelField Either the fields array or the ModelField for fieldname $name
+ */
+	public function getFields($name = null) {
+		if ($name !== null && !empty($this->_fields[$name])) {
+			return $this->_fields[$name];
+		}
+		return $this->_fields;
+	}
+
+/**
+ * Sets the ModelField isntances from the Model::$validate property after processing the fieldList and whiteList.
+ * If Model::$validate is not set or empty, this method returns false. True otherwise.
+ *
+ * @param boolean $reset If true will reset the Validator $validate array to the Model's default
+ * @return boolean True if Model::$validate was processed, false otherwise
+ */
+	public function setFields($reset = false) {
+		if (!isset($this->_model->validate) || empty($this->_model->validate)) {
+			$this->_validate = array();
+			return false;
+		}
+
+		$this->_validate = $this->_model->validate;
+
+		if ($reset === true) {
+			return true;
+		}
+
+		$this->_processWhitelist();
+
+		$this->_fields = array();
+		foreach ($this->_validate as $fieldName => $ruleSet) {
+			$this->_fields[$fieldName] = new CakeField($this, $fieldName, $ruleSet);
+		}
+		unset($fieldName, $ruleSet);
+		return true;
+	}
+
+/**
+ * Sets an options array. If $mergeVars is true, the options will be merged with the existing ones.
+ * Otherwise they will get replaced. The default is merging the vars.
+ *
+ * @param array $options [optional] The options to be set
+ * @param boolean $mergeVars [optional] If true, the options will be merged, otherwise they get replaced
+ * @return ModelValidator
+ */
+	public function setOptions($options = array(), $mergeVars = false) {
+		if ($mergeVars === false) {
+			$this->options = $options;
+		} else {
+			$this->options = array_merge($this->options, $options);
+		}
+		return $this;
+	}
+
+/**
+ * Sets an option $name with $value. This method is chainable
+ *
+ * @param string $name The options name to be set
+ * @param mixed $value [optional] The value to be set. Defaults to null.
+ * @return ModelValidator
+ */
+	public function setOption($name, $value = null) {
+		$this->options[$name] = $value;
+		return $this;
+	}
+
+/**
+ * Gets an options value by $name. If $name is not set or no option has been found, returns null.
+ *
+ * @param string $name The options name to look up
+ * @return mixed Either null or the option value
+ */
+	public function getOptions($name = NULL) {
+		if (NULL !== $name) {
+			if (!isset($this->options[$name])) {
+				return NULL;
+			}
+			return $this->options[$name];
+		}
+		return $this->options;
+	}
+
+/**
+ * Sets the I18n domain for validation messages. This method is chainable.
+ *
+ * @param string $validationDomain [optional] The validation domain to be used. If none is given, uses Model::$validationDomain
+ * @return ModelValidator
+ */
+	public function setValidationDomain($validationDomain = null) {
+		if ($validationDomain !== null) {
+			$this->validationDomain = $validationDomain;
+		} elseif ($this->_model->validationDomain !== null) {
+			$this->validationDomain = $this->_model->validationDomain;
+		} else {
+			$this->validationDomain = ModelValidator::DEFAULT_DOMAIN;
+		}
+
+		return $this;
+	}
+
+/**
+ * Gets the parent Model
+ *
+ * @return Model
+ */
+	public function getModel() {
+		return $this->_model;
+	}
+
+/**
+ * Processes the Model's whitelist and adjusts the validate array accordingly
+ *
+ * @return void
+ */
+	protected function _processWhitelist() {
+		$whitelist = $this->getModel()->whitelist;
+		$fieldList = $this->getOptions('fieldList');
+
+		if (!empty($fieldList)) {
+			if (!empty($fieldList[$this->getModel()->alias]) && is_array($fieldList[$this->getModel()->alias])) {
+				$whitelist = $fieldList[$this->getModel()->alias];
+			} else {
+				$whitelist = $fieldList;
+			}
+		}
+		unset($fieldList);
+
+		if (!empty($whitelist)) {
+			$this->validationErrors = array();
+			$validate = array();
+			foreach ((array) $whitelist as $f) {
+				if (!empty($this->_validate[$f])) {
+					$validate[$f] = $this->_validate[$f];
+				}
+			}
+			$this->_validate = $validate;
+		}
+	}
+
+/**
+ * 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;
+		$this->getData(null, true);
+
+		foreach ($this->getModel()->hasAndBelongsToMany as $assoc => $association) {
+			if (empty($association['with']) || !isset($this->data[$assoc])) {
+				continue;
+			}
+			list($join) = $this->getModel()->joinModel($this->getModel()->hasAndBelongsToMany[$assoc]['with']);
+			$data = $this->data[$assoc];
+
+			$newData = array();
+			foreach ((array)$data as $row) {
+				if (isset($row[$this->getModel()->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
+					$newData[] = $row;
+				} elseif (isset($row[$join]) && isset($row[$join][$this->getModel()->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
+					$newData[] = $row[$join];
+				}
+			}
+			if (empty($newData)) {
+				continue;
+			}
+			foreach ($newData as $data) {
+				$data[$this->getModel()->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->getModel()->id;
+				$this->getModel()->{$join}->create($data);
+				$valid = ($valid && $this->getModel()->{$join}->getValidator()->validates($options));
+			}
+		}
+		return $valid;
+	}
+
+/**
+ * Propagates the beforeValidate event
+ *
+ * @param array $options
+ * @return boolean
+ */
+	public function propagateBeforeValidate($options = array()) {
+		$event = new CakeEvent('Model.beforeValidate', $this->getModel(), array($options));
+		list($event->break, $event->breakOn) = array(true, false);
+		$this->getModel()->getEventManager()->dispatch($event);
+		if ($event->isStopped()) {
+			return false;
+		}
+		return true;
+	}
+
+}

+ 188 - 0
lib/Cake/Model/Validator/CakeField.php

@@ -0,0 +1,188 @@
+<?php
+/**
+ * ModelValidator.
+ *
+ * Provides the Model validation logic.
+ *
+ * PHP versions 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @package       Cake.Model
+ * @since         CakePHP(tm) v 3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+App::uses('ModelValidator', 'Model');
+App::uses('CakeRule', 'Model/Validator');
+
+/**
+ * ModelField object.
+ *
+ * @package       Cake.Model
+ * @link          http://book.cakephp.org/2.0/en/data-validation.html
+ */
+class CakeField {
+
+/**
+ * Holds the parent Validator instance
+ * 
+ * @var ModelValidator
+ */
+	protected $_validator = null;
+
+/**
+ * Holds the ValidationRule objects
+ * 
+ * @var array
+ */
+	protected $_rules = array();
+
+/**
+ * If 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 ModelValidator $validator The parent ModelValidator
+ * @param string $fieldName The fieldname
+ * @param 
+ */
+	public function __construct(ModelValidator $validator, $fieldName, $ruleSet) {
+		$this->_validator = $validator;
+		$this->data = &$this->getValidator()->data;
+		$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 CakeRule($this, $validateProp, $index);
+		}
+		$this->ruleSet = $ruleSet;
+		unset($ruleSet, $validateProp);
+	}
+
+/**
+ * Validates a ModelField
+ * 
+ * @return mixed
+ */
+	public function validate() {
+		foreach ($this->getRules() as $rule) {
+			if ($rule->skip()) {
+				continue;
+			}
+			$rule->isRequired();
+
+			if (!$rule->checkRequired() && array_key_exists($this->field, $this->data)) {
+				if ($rule->checkEmpty()) {
+					break;
+				}
+				$rule->dispatchValidation();
+			}
+
+			if ($rule->checkRequired() || !$rule->isValid()) {
+				$this->getValidator()->invalidate($this->field, $rule->getMessage());
+
+				if ($rule->isLast()) {
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+/**
+ * Gets a rule for a certain index
+ * 
+ * @param mixed index
+ * @return ValidationRule
+ */
+	public function getRule($index) {
+		if (!empty($this->_rules[$index])) {
+			return $this->_rules[$index];
+		}
+	}
+
+/**
+ * Gets all rules for this ModelField
+ * 
+ * @return array
+ */
+	public function getRules() {
+		return $this->_rules;
+	}
+
+/**
+ * Sets a ValidationRule $rule for key $key
+ * 
+ * @param mixed $key The key under which the rule should be set
+ * @param ValidationRule $rule The ValidationRule to be set
+ * @return ModelField
+ */
+	public function setRule($key, CakeRule $rule) {
+		$this->_rules[$key] = $rule;
+		return $this;
+	}
+
+/**
+ * Sets the rules for a given field
+ * 
+ * @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;
+	}
+
+/**
+ * Gets the validator this field is atached to
+ * 
+ * @return ModelValidator The parent ModelValidator instance
+ */
+	public function getValidator() {
+		return $this->_validator;
+	}
+
+/**
+ * Magic isset
+ * 
+ * @return true if the field exists in data, false otherwise
+ */
+	public function __isset($fieldName) {
+		return array_key_exists($fieldName, $this->getValidator()->getData());
+	}
+
+}

+ 404 - 0
lib/Cake/Model/Validator/CakeRule.php

@@ -0,0 +1,404 @@
+<?php
+/**
+ * CakeRule.
+ *
+ * Provides the Model validation logic.
+ *
+ * PHP versions 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @package       Cake.Model
+ * @since         CakePHP(tm) v 3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+App::uses('ModelValidator', 'Model');
+App::uses('CakeField', 'Model/Validator');
+App::uses('Validation', 'Utility');
+
+/**
+ * ValidationRule object.
+ *
+ * @package       Cake.Model
+ * @link          http://book.cakephp.org/2.0/en/data-validation.html
+ */
+class CakeRule {
+
+/**
+ * Holds a reference to the parent field
+ * 
+ * @var ModelField
+ */
+	protected $_field = null;
+
+/**
+ * Has the required check failed?
+ * 
+ * @var boolean
+ */
+	protected $_requiredFail = null;
+
+/**
+ * The 'valid' value
+ * 
+ * @var mixed
+ */
+	protected $_valid = true;
+
+/**
+ * Holds the index under which the Vaildator was attached
+ * 
+ * @var mixed
+ */
+	protected $_index = null;
+
+/**
+ * Create or Update transaction?
+ * 
+ * @var boolean
+ */
+	protected $_modelExists = null;
+
+/**
+ * The parsed rule
+ * 
+ * @var mixed
+ */
+	protected $_rule = null;
+
+/**
+ * The parsed rule parameters
+ * 
+ * @var array
+ */
+	protected $_ruleParams = array();
+
+/**
+ * The errorMessage
+ * 
+ * @var string
+ */
+	protected $_errorMessage = null;
+
+/**
+ * Holds passed in options
+ * 
+ * @var array
+ */
+	protected $_passedOptions = array();
+
+/**
+ * Flag indicating wether the allowEmpty check has failed
+ * 
+ * @var boolean 
+ */
+	protected $_emptyFail = null;
+
+/**
+ * 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 ModelField $field
+ * @param array $validator [optional] The validator properties
+ * @param mixed $index [optional]
+ */
+	public function __construct(CakeField $field, $validator = array(), $index = null) {
+		$this->_field = $field;
+		$this->_index = $index;
+		unset($field, $index);
+
+		$this->data = &$this->getField()
+				->data;
+
+		$this->_modelExists = $this->getField()
+				->getValidator()
+				->getModel()
+				->exists();
+
+		$this->_addValidatorProps($validator);
+		unset($validator);
+	}
+
+/**
+ * Checks if the rule is valid
+ * 
+ * @return boolean
+ */
+	public function isValid() {
+		if (!$this->_valid || (is_string($this->_valid) && strlen($this->_valid) > 0)) {
+			return false;
+		}
+
+		return true;
+	}
+
+/**
+ * Checks if the field is required by the 'required' value
+ * 
+ * @return boolean
+ */
+	public function isRequired() {
+		if ($this->required === true || $this->required === false) {
+			return $this->required;
+		}
+
+		if (in_array($this->required, array('create', 'update'), true)) {
+			if ($this->required === 'create' && !$this->_modelExists || $this->required === 'update' && $this->_modelExists) {
+				$this->required = true;
+			}
+		}
+
+		return $this->required;
+	}
+
+/**
+ * Checks if the field failed the required validation
+ * 
+ * @return boolean
+ */
+	public function checkRequired() {
+		if ($this->_requiredFail !== null) {
+			return $this->_requiredFail;
+		}
+		$this->_requiredFail = (
+			(!isset($this->data[$this->getField()->field]) && $this->required === true) ||
+			(
+				isset($this->data[$this->getField()->field]) && (empty($this->data[$this->getField()->field]) &&
+				!is_numeric($this->data[$this->getField()->field])) && $this->allowEmpty === false
+			)
+		);
+		return $this->_requiredFail;
+	}
+
+/**
+ * Checks if the allowEmpty key applies
+ * 
+ * @return boolean
+ */
+	public function checkEmpty() {
+		if ($this->_emptyFail !== null) {
+			return $this->_emptyFail;
+		}
+		$this->_emptyFail = false;
+
+		if (empty($this->data[$this->getField()->field]) && $this->data[$this->getField()->field] != '0' && $this->allowEmpty === true) {
+			$this->_emptyFail = true;
+		}
+		return $this->_emptyFail;
+	}
+
+/**
+ * Checks if the Validation rule can be skipped
+ * 
+ * @return boolean True if the ValidaitonRule can be skipped
+ */
+	public function skip() {
+		if (!empty($this->on)) {
+			if ($this->on == 'create' && $this->_modelExists || $this->on == 'update' && !$this->_modelExists) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+/**
+ * Checks if the 'last' key is true
+ * 
+ * @return boolean
+ */
+	public function isLast() {
+		return (bool) $this->last;
+	}
+
+/**
+ * Gets the validation error message
+ * 
+ * @return string
+ */
+	public function getMessage() {
+		return $this->_processValidationResponse();
+	}
+
+/**
+ * Gets the parent field
+ * 
+ * @return ModelField
+ */
+	public function getField() {
+		return $this->_field;
+	}
+
+/**
+ * Gets an array with the rule properties
+ * 
+ * @return array
+ */
+	public function getPropertiesArray() {
+		return array(
+			'rule' => $this->rule,
+			'required' => $this->required,
+			'allowEmpty' => $this->allowEmpty,
+			'on' => $this->on,
+			'last' => $this->last,
+			'message' => $this->message
+		);
+	}
+
+/**
+ * Dispatches the validation rule to the given validator method
+ * 
+ * @return boolean True if the rule could be dispatched, false otherwise
+ */
+	public function dispatchValidation() {
+		$this->_parseRule();
+
+		$validator = $this->getPropertiesArray();
+		$methods = $this->getField()->getValidator()->getMethods();
+		$Model = $this->getField()->getValidator()->getModel();
+
+		if (in_array(strtolower($this->_rule), $methods['model'])) {
+			$this->_ruleParams[] = array_merge($validator, $this->_passedOptions);
+			$this->_ruleParams[0] = array($this->getField()->field => $this->_ruleParams[0]);
+			$this->_valid = $Model->dispatchMethod($this->_rule, $this->_ruleParams);
+		} elseif (in_array($this->_rule, $methods['behaviors']) || in_array(strtolower($this->_rule), $methods['behaviors'])) {
+			$this->_ruleParams[] = array_merge($validator, $this->_passedOptions);
+			$this->_ruleParams[0] = array($this->getField()->field => $this->_ruleParams[0]);
+			$this->_valid = $Model->Behaviors->dispatchMethod($Model, $this->_rule, $this->_ruleParams);
+		} elseif (method_exists('Validation', $this->_rule)) {
+			$this->_valid = call_user_func_array(array('Validation', $this->_rule), $this->_ruleParams);
+		} elseif (!is_array($validator['rule'])) {
+			$this->_valid = preg_match($this->_rule, $this->data[$this->getField()->field]);
+		} elseif (Configure::read('debug') > 0) {
+			trigger_error(__d('cake_dev', 'Could not find validation handler %s for %s', $this->_rule, $this->_field->field), E_USER_WARNING);
+			return false;
+		}
+		unset($validator, $methods, $Model);
+
+		return true;
+	}
+
+/**
+ * Fetches the correct error message for a failed validation
+ * 
+ * @return string
+ */
+	protected function _processValidationResponse() {
+		$validationDomain = $this->_field->getValidator()->validationDomain;
+
+		if (is_string($this->_valid)) {
+			$this->_errorMessage = $this->_valid;
+		} elseif ($this->message !== null) {
+			$args = null;
+			if (is_array($this->message)) {
+				$this->_errorMessage = $this->message[0];
+				$args = array_slice($this->message, 1);
+			} else {
+				$this->_errorMessage = $this->message;
+			}
+			if (is_array($this->rule) && $args === null) {
+				$args = array_slice($this->getField()->ruleSet[$this->_index]['rule'], 1);
+			}
+			$this->_errorMessage = __d($validationDomain, $this->_errorMessage, $args);
+		} elseif (is_string($this->_index)) {
+			if (is_array($this->rule)) {
+				$args = array_slice($this->getField()->ruleSet[$this->_index]['rule'], 1);
+				$this->_errorMessage = __d($validationDomain, $this->_index, $args);
+			} else {
+				$this->_errorMessage = __d($validationDomain, $this->_index);
+			}
+		} elseif (!$this->checkRequired() && is_numeric($this->_index) && count($this->getField()->ruleSet) > 1) {
+			$this->_errorMessage = $this->_index + 1;
+		} else {
+			$this->_errorMessage = __d('cake_dev', 'This field cannot be left blank');
+		}
+		unset($validationDomain);
+
+		return $this->_errorMessage;
+	}
+
+/**
+ * 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;
+				}
+			}
+		}
+		unset($validator);
+	}
+
+/**
+ * Parses the rule and sets the rule and ruleParams
+ * 
+ * @return void
+ */
+	protected function _parseRule() {
+		if (is_array($this->rule)) {
+			$this->_rule = $this->rule[0];
+			unset($this->rule[0]);
+			$this->_ruleParams = array_merge(array($this->data[$this->getField()->field]), array_values($this->rule));
+		} else {
+			$this->_rule = $this->rule;
+			$this->_ruleParams = array($this->data[$this->getField()->field]);
+		}
+	}
+
+}

+ 1 - 1
lib/Cake/Test/Case/Controller/ControllerTest.php

@@ -387,7 +387,7 @@ class AnotherTestController extends ControllerTestAppController {
 
 /**
  * merge parent
- * 
+ *
  * @var string
  */
 	protected $_mergeParent = 'ControllerTestAppController';

+ 7 - 3
lib/Cake/Test/Case/Model/ModelValidationTest.php

@@ -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);
@@ -346,6 +349,7 @@ class ModelValidationTest extends BaseModelTest {
 		$result = $TestModel->create($data);
 		$this->assertEquals($data, $result);
 		$result = $TestModel->validates();
+
 		$this->assertTrue($result);
 
 		$data = array('TestValidate' => array(

+ 22 - 20
lib/Cake/Test/Case/Model/ModelWriteTest.php

@@ -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,7 +2950,8 @@ 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'))
@@ -4329,7 +4330,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 +4401,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 +4423,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);
 	}
 
 /**
@@ -4550,8 +4551,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']);
 	}
@@ -5037,7 +5038,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 +5047,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 +5690,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 +5748,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 +5770,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 +5878,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']);
 	}