diff --git a/Cake/ORM/Validation/ValidationRule.php b/Cake/ORM/Validation/ValidationRule.php index 93f915438ca..67040612e3f 100644 --- a/Cake/ORM/Validation/ValidationRule.php +++ b/Cake/ORM/Validation/ValidationRule.php @@ -26,13 +26,6 @@ */ class ValidationRule { -/** - * Validation method - * - * @var mixed - */ - protected $_rule = null; - /** * Validation method arguments * @@ -41,39 +34,32 @@ class ValidationRule { protected $_ruleParams = array(); /** - * Holds passed in options + * The method to be called for a given scope * - * @var array + * @var string|\Closure */ - protected $_passedOptions = array(); - -/** - * The 'rule' key - * - * @var mixed - */ - public $rule = 'blank'; + protected $_rule; /** * The 'on' key * * @var string */ - public $on = null; + protected $_on = null; /** * The 'last' key * * @var boolean */ - public $last = true; + protected $_last = true; /** * The 'message' key * * @var string */ - public $message = null; + protected $_message = null; /** * Constructor @@ -84,19 +70,6 @@ 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; - } - /** * Checks if the validation rule should be skipped * @@ -122,72 +95,25 @@ public function isLast() { } /** - * Gets the validation error message - * - * @return string - */ - public function getValidationResult() { - return $this->_valid; - } - -/** - * Gets an array with the rule properties + * Dispatches the validation rule to the given validator method and returns + * a boolean indicating whether the rule passed or not. If a string is returned + * it is assumed that the rule failed and the error message was given as a result. * - * @return array + * @param mixed $data The data to validate + * @param array $scopes associative array with objects or class names that will + * be passed as the last argument for the validation method + * @param boolean $newRecord whether or not the data to be validated belongs to + * a new record + * @return boolean|string */ - protected function _getPropertiesArray() { - $rule = $this->rule; - if (!is_string($rule)) { - unset($rule[0]); + public function process($data, $scopes, $newRecord) { + $scope = $scopes['default']; + $callable = [$scope, $this->_rule]; + $result = $callable($data, $scopes); + if ($result === false) { + return $this->_message ?: false; } - return array( - 'rule' => $rule, - 'on' => $this->on, - 'last' => $this->last, - 'message' => $this->message - ); - } - -/** - * Dispatches the validation rule to the given validator method - * - * @param string $field Field name - * @param array $data Data array - * @param array $methods Methods list - * @return boolean True if the rule could be dispatched, false otherwise - */ - public function process($field, $data, $methods) { - $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 (method_exists('Cake\Utility\Validation', $this->_rule)) { - $this->_valid = call_user_func_array(array('Cake\Utility\Validation', $this->_rule), $this->_ruleParams); - } elseif (is_string($validator['rule'])) { - $this->_valid = preg_match($this->_rule, $data[$field]); - } else { - 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 - * - * @param string|integer $key Array index - * @return array - */ - public function getOptions($key) { - if (!isset($this->_passedOptions[$key])) { - return null; - } - return $this->_passedOptions[$key]; + return $result; } /** @@ -203,29 +129,10 @@ protected function _addValidatorProps($validator = array()) { foreach ($validator as $key => $value) { if (isset($value) || !empty($value)) { if (in_array($key, array('rule', 'on', 'message', 'last'))) { - $this->{$key} = $validator[$key]; - } else { - $this->_passedOptions[$key] = $value; + $this->{"_$key"} = $validator[$key]; } } } } -/** - * Parses the rule and sets the rule and ruleParams - * - * @param string $field Field name - * @param array $data Data array - * @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]); - } - } - } diff --git a/Cake/ORM/Validation/ValidationSet.php b/Cake/ORM/Validation/ValidationSet.php index cd0d480287b..a7ba581b8b6 100644 --- a/Cake/ORM/Validation/ValidationSet.php +++ b/Cake/ORM/Validation/ValidationSet.php @@ -190,64 +190,6 @@ public function remove($name) { return $this; } -/** - * Fetches the correct error message for a failed validation - * - * @param string $name the name of the rule as it was configured - * @param Cake\Model\Validator\ValidationRule $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); - } - $args = $this->_translateArgs($args); - - $message = __d($this->_validationDomain, $result, $args); - } elseif (is_string($name)) { - if (is_array($rule->rule)) { - $args = array_slice($rule->rule, 1); - $args = $this->_translateArgs($args); - $message = __d($this->_validationDomain, $name, $args); - } else { - $message = __d($this->_validationDomain, $name); - } - } else { - $message = __d('cake', 'The provided value is invalid'); - } - - return $message; - } - -/** - * Applies translations to validator arguments. - * - * @param array $args The args to translate - * @return array Translated args. - */ - protected function _translateArgs($args) { - foreach ((array)$args as $k => $arg) { - if (is_string($arg)) { - $args[$k] = __d($this->_validationDomain, $arg); - } - } - return $args; - } - /** * Returns whether an index exists in the rule set * diff --git a/Cake/ORM/Validator.php b/Cake/ORM/Validator.php index f9879ded77a..7cc6f63df79 100644 --- a/Cake/ORM/Validator.php +++ b/Cake/ORM/Validator.php @@ -63,13 +63,31 @@ public function errors(array $data, $newRecord = true) { $errors = []; foreach ($this->_fields as $name => $field) { $keyPresent = array_key_exists($name, $data); + if (!$keyPresent && !$this->_checkPresence($field, $newRecord)) { $errors[$name][] = __d('cake', 'This field is required'); + continue; + } + + if (!$keyPresent) { + continue; } - if ($keyPresent && !$this->_checkEmpty($field, $newRecord)) { - if ($this->_fieldIsEmpty($data[$name])) { - $errors[$name][] = __d('cake', 'This field cannot be left empty'); - } + + $canBeEmpty = $this->_canBeEmpty($field, $newRecord); + $isEmpty = $this->_fieldIsEmpty($data[$name]); + + if (!$canBeEmpty && $isEmpty) { + $errors[$name][] = __d('cake', 'This field cannot be left empty'); + continue; + } + + if ($isEmpty) { + continue; + } + + $result = $this->_processRules($field, $data[$name], $newRecord); + if ($result) { + $errors[$name] = $result; } } @@ -308,7 +326,7 @@ protected function _checkPresence($field, $newRecord) { * @param boolean $newRecord whether the data to be validated is new or to be updated. * @return boolean */ - protected function _checkEmpty($field, $newRecord) { + protected function _canBeEmpty($field, $newRecord) { $allowed = $field->isEmptyAllowed(); if (in_array($allowed, array('create', 'update'), true)) { $allowed = ( @@ -333,4 +351,21 @@ protected function _fieldIsEmpty($data) { return false; } + protected function _processRules(ValidationSet $rules, $value, $newRecord) { + $errors = []; + $this->scope('default'); // Loading default scope in case there is none + foreach ($rules as $name => $rule) { + $result = $rule->process($value, $this->_scopes, $newRecord); + if ($result === true) { + continue; + } + + $errors[$name] = __d('cake', 'The provided value is invalid'); + if (is_string($result)) { + $errors[$name] = __d($this->_validationDomain, $result); + } + } + return $errors; + } + } diff --git a/Cake/Test/TestCase/ORM/ValidatorTest.php b/Cake/Test/TestCase/ORM/ValidatorTest.php index 2c0777bf2a7..f164131622d 100644 --- a/Cake/Test/TestCase/ORM/ValidatorTest.php +++ b/Cake/Test/TestCase/ORM/ValidatorTest.php @@ -225,4 +225,26 @@ public function testScope() { $this->assertEquals('\Cake\Utility\Validation', $validator->scope('default')); } +/** + * Tests errors() method when using validators from the default scope, this proves + * that it returns a default validation message and the custom one set in the rule + * + * @return void + */ + public function testErrorsFromDefaultScope() { + $validator = new Validator; + $validator + ->add('email', 'alpha', ['rule' => 'alphanumeric']) + ->add('email', 'notEmpty', ['rule' => 'notEmpty']) + ->add('email', 'email', ['rule' => 'email', 'message' => 'Y u no write email?']); + $errors = $validator->errors(['email' => 'not an email!']); + $expected = [ + 'email' => [ + 'alpha' => 'The provided value is invalid', + 'email' => 'Y u no write email?' + ] + ]; + $this->assertEquals($expected, $errors); + } + }