diff --git a/.gitignore b/.gitignore index 432c0c1..4a38432 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.idea/ composer.phar composer.lock vendor diff --git a/README.md b/README.md index c2f7195..c6bd85a 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ V::lang('ar'); * `accepted` - Checkbox or Radio must be accepted (yes, on, 1, true) * `numeric` - Must be numeric * `integer` - Must be integer number + * `boolean` - Must be boolean * `array` - Must be array * `length` - String must be certain length * `lengthBetween` - String must be between given lengths diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index 4496e2d..4601a5b 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -37,6 +37,21 @@ class Validator */ protected $_labels = array(); + /** + * Contains all rules that are available to the current valitron instance. + * + * @var array + */ + protected $_instanceRules = array(); + + /** + * Contains all rule messages that are available to the current valitron + * instance + * + * @var array + */ + protected $_instanceRuleMessage = array(); + /** * @var string */ @@ -503,7 +518,7 @@ class Validator foreach ($this->validUrlPrefixes as $prefix) { if (strpos($value, $prefix) !== false) { $host = parse_url(strtolower($value), PHP_URL_HOST); - + return checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA') || checkdnsrr($host, 'CNAME'); } } @@ -710,7 +725,7 @@ class Validator } else { $cardRegex = array( 'visa' => '#^4[0-9]{12}(?:[0-9]{3})?$#', - 'mastercard' => '#^5[1-5][0-9]{14}$#', + 'mastercard' => '#^(5[1-5]|2[2-7])[0-9]{14}$#', 'amex' => '#^3[47][0-9]{13}$#', 'dinersclub' => '#^3(?:0[0-5]|[68][0-9])[0-9]{11}$#', 'discover' => '#^6(?:011|5[0-9]{2})[0-9]{12}$#', @@ -772,7 +787,8 @@ class Validator } //Validate optional field - protected function validateOptional($field, $value, $params) { + protected function validateOptional($field, $value, $params) + { //Always return true return true; } @@ -887,7 +903,7 @@ class Validator } // Dead end, abort - elseif ($identifier === NULL || ! isset($data[$identifier])) { + elseif ($identifier === null || ! isset($data[$identifier])) { return array(null, false); } @@ -921,8 +937,9 @@ class Validator } // Callback is user-specified or assumed method on class - if (isset(static::$_rules[$v['rule']])) { - $callback = static::$_rules[$v['rule']]; + $errors = $this->getRules(); + if (isset($errors[$v['rule']])) { + $callback = $errors[$v['rule']]; } else { $callback = array($this, 'validate' . ucfirst($v['rule'])); } @@ -945,6 +962,26 @@ class Validator return count($this->errors()) === 0; } + /** + * Returns all rule callbacks, the static and instance ones. + * + * @return array + */ + protected function getRules() + { + return array_merge($this->_instanceRules, static::$_rules); + } + + /** + * Returns all rule message, the static and instance ones. + * + * @return array + */ + protected function getRuleMessages() + { + return array_merge($this->_instanceRuleMessage, static::$_ruleMessages); + } + /** * Determine whether a field is being validated by the given rule. * @@ -965,46 +1002,117 @@ class Validator return false; } + protected static function assertRuleCallback($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'Second argument must be a valid callback. Given argument was not callable.' + ); + } + } + + /** + * Adds a new validation rule callback that is tied to the current + * instance only. + * + * @param string $name + * @param callable $callback + * @param string $message + * @throws \InvalidArgumentException + */ + public function addInstanceRule($name, $callback, $message = null) + { + static::assertRuleCallback($callback); + + $this->_instanceRules[$name] = $callback; + $this->_instanceRuleMessage[$name] = $message; + } + /** * Register new validation rule callback * * @param string $name - * @param mixed $callback + * @param callable $callback * @param string $message * @throws \InvalidArgumentException */ - public static function addRule($name, $callback, $message = self::ERROR_DEFAULT) + public static function addRule($name, $callback, $message = null) { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Second argument must be a valid callback. Given argument was not callable.'); + if ($message === null) { + $message = static::ERROR_DEFAULT; } + static::assertRuleCallback($callback); + static::$_rules[$name] = $callback; static::$_ruleMessages[$name] = $message; } + public function getUniqueRuleName($fields) + { + if (is_array($fields)) { + $fields = implode("_", $fields); + } + + $orgName = "{$fields}_rule"; + $name = $orgName; + $rules = $this->getRules(); + while (isset($rules[$name])) { + $name = $orgName . "_" . rand(0, 10000); + } + + return $name; + } + + /** + * Returns true if either a valdiator with the given name has been + * registered or there is a default validator by that name. + * + * @param string $name + * @return bool + */ + public function hasValidator($name) + { + $rules = $this->getRules(); + return method_exists($this, "validate" . ucfirst($name)) + || isset($rules[$name]); + } + /** * Convenience method to add a single validation rule * - * @param string $rule - * @param array $fields + * @param string|callback $rule + * @param array|string $fields * @return $this * @throws \InvalidArgumentException */ public function rule($rule, $fields) { - if (!isset(static::$_rules[$rule])) { + // Get any other arguments passed to function + $params = array_slice(func_get_args(), 2); + + if (is_callable($rule) + && !(is_string($rule) && $this->hasValidator($rule))) + { + $name = $this->getUniqueRuleName($fields); + $msg = isset($params[0]) ? $params[0] : null; + $this->addInstanceRule($name, $rule, $msg); + $rule = $name; + } + + $errors = $this->getRules(); + if (!isset($errors[$rule])) { $ruleMethod = 'validate' . ucfirst($rule); if (!method_exists($this, $ruleMethod)) { - throw new \InvalidArgumentException("Rule '" . $rule . "' has not been registered with " . __CLASS__ . "::addRule()."); + throw new \InvalidArgumentException( + "Rule '" . $rule . "' has not been registered with " . get_called_class() . "::addRule()." + ); } } // Ensure rule has an accompanying message - $message = isset(static::$_ruleMessages[$rule]) ? static::$_ruleMessages[$rule] : self::ERROR_DEFAULT; - - // Get any other arguments passed to function - $params = array_slice(func_get_args(), 2); + $messages = $this->getRuleMessages(); + $message = isset($messages[$rule]) ? $messages[$rule] : self::ERROR_DEFAULT; $this->_validations[] = array( 'rule' => $rule, @@ -1018,7 +1126,6 @@ class Validator /** * @param string $value - * @internal param array $labels * @return $this */ public function label($value) @@ -1031,7 +1138,7 @@ class Validator /** * @param array $labels - * @return string + * @return $this */ public function labels($labels = array()) { @@ -1085,4 +1192,19 @@ class Validator } } } + + /** + * Replace data on cloned instance + * + * @param array $data + * @param array $fields + * @return Validator + */ + public function withData($data, $fields = array()) + { + $clone = clone $this; + $clone->_fields = !empty($fields) ? array_intersect_key($data, array_flip($fields)) : $data; + $clone->_errors = array(); + return $clone; + } }