Merge pull request #166 from Matt3o12/addTmpRule

Add anonymous and instance rules
This commit is contained in:
Willem Wollebrants 2016-11-13 13:13:10 +01:00 committed by GitHub
commit 633c9ff57f
3 changed files with 263 additions and 12 deletions

View File

@ -188,6 +188,29 @@ Valitron\Validator::addRule('alwaysFail', function($field, $value, array $params
}, 'Everything you do is wrong. You fail.'); }, 'Everything you do is wrong. You fail.');
``` ```
You can also use one-off rules that are only valid for the specified
fields.
```php
$v = new Valitron\Validator(array("foo" => "bar"));
$v->rule(function($field, $value, $params, $fields) {
return true;
}, "foo")->message("{field} failed...");
```
This is useful because such rules can have access to variables
defined in the scope where the `Validator` lives. The Closure's
signature is identical to `Validator::addRule` callback's
signature.
If you wish to add your own rules that are not static (i.e.,
your rule is not static and available to call `Validator`
instances), you need to use `Validator::addInstanceRule`.
This rule will take the same parameters as
`Validator::addRule` but it has to be called on a `Validator`
instance.
## Alternate syntax for adding rules ## Alternate syntax for adding rules
As the number of rules grows, you may prefer the alternate syntax As the number of rules grows, you may prefer the alternate syntax

View File

@ -37,6 +37,21 @@ class Validator
*/ */
protected $_labels = array(); 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 * @var string
*/ */
@ -503,7 +518,7 @@ class Validator
foreach ($this->validUrlPrefixes as $prefix) { foreach ($this->validUrlPrefixes as $prefix) {
if (strpos($value, $prefix) !== false) { if (strpos($value, $prefix) !== false) {
$host = parse_url(strtolower($value), PHP_URL_HOST); $host = parse_url(strtolower($value), PHP_URL_HOST);
return checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA') || checkdnsrr($host, 'CNAME'); return checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA') || checkdnsrr($host, 'CNAME');
} }
} }
@ -921,8 +936,9 @@ class Validator
} }
// Callback is user-specified or assumed method on class // Callback is user-specified or assumed method on class
if (isset(static::$_rules[$v['rule']])) { $errors = $this->getRules();
$callback = static::$_rules[$v['rule']]; if (isset($errors[$v['rule']])) {
$callback = $errors[$v['rule']];
} else { } else {
$callback = array($this, 'validate' . ucfirst($v['rule'])); $callback = array($this, 'validate' . ucfirst($v['rule']));
} }
@ -945,6 +961,26 @@ class Validator
return count($this->errors()) === 0; 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. * Determine whether a field is being validated by the given rule.
* *
@ -952,6 +988,7 @@ class Validator
* @param string $field The name of the field * @param string $field The name of the field
* @return boolean * @return boolean
*/ */
protected function hasRule($name, $field) protected function hasRule($name, $field)
{ {
foreach ($this->_validations as $validation) { foreach ($this->_validations as $validation) {
@ -965,6 +1002,31 @@ class Validator
return false; 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 mixed $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 * Register new validation rule callback
* *
@ -973,27 +1035,75 @@ class Validator
* @param string $message * @param string $message
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public static function addRule($name, $callback, $message = self::ERROR_DEFAULT) public static function addRule($name, $callback, $message = null)
{ {
if (!is_callable($callback)) { if ($message === null)
throw new \InvalidArgumentException('Second argument must be a valid callback. Given argument was not callable.'); {
$message = static::ERROR_DEFAULT;
} }
static::assertRuleCallback($callback);
static::$_rules[$name] = $callback; static::$_rules[$name] = $callback;
static::$_ruleMessages[$name] = $message; 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 * Convenience method to add a single validation rule
* *
* @param string $rule * @param string|callback $rule
* @param array $fields * @param array $fields
* @return $this * @return $this
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function rule($rule, $fields) 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); $ruleMethod = 'validate' . ucfirst($rule);
if (!method_exists($this, $ruleMethod)) { 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 " . __CLASS__ . "::addRule().");
@ -1001,10 +1111,8 @@ class Validator
} }
// Ensure rule has an accompanying message // Ensure rule has an accompanying message
$message = isset(static::$_ruleMessages[$rule]) ? static::$_ruleMessages[$rule] : self::ERROR_DEFAULT; $msgs = $this->getRuleMessages();
$message = isset($msgs[$rule]) ? $msgs[$rule] : self::ERROR_DEFAULT;
// Get any other arguments passed to function
$params = array_slice(func_get_args(), 2);
$this->_validations[] = array( $this->_validations[] = array(
'rule' => $rule, 'rule' => $rule,

View File

@ -0,0 +1,120 @@
<?php
use Valitron\Validator;
function callbackTestFunction($item, $value)
{
return $value === "bar";
}
class ValidateAddInstanceRuleTest extends BaseTestCase
{
protected function assertValid($v)
{
$msg = "\tErrors:\n";
$status = $v->validate();
foreach ($v->errors() as $label => $messages)
{
foreach ($messages as $theMessage)
{
$msg .= "\n\t{$label}: {$theMessage}";
}
}
$this->assertTrue($v->validate(), $msg);
}
public function testAddInstanceRule()
{
$v = new Validator(array(
"foo" => "bar",
"fuzz" => "bazz",
));
$v->addInstanceRule("fooRule", function($field, $value)
{
return $field !== "foo" || $value !== "barz";
});
Validator::addRule("fuzzerRule", function($field, $value)
{
return $field !== "fuzz" || $value === "bazz";
});
$v->rule("required", array("foo", "fuzz"));
$v->rule("fuzzerRule", "fuzz");
$v->rule("fooRule", "foo");
$this->assertValid($v);
}
public function testAddInstanceRuleFail()
{
$v = new Validator(array("foo" => "bar"));
$v->addInstanceRule("fooRule", function($field)
{
return $field === "for";
});
$v->rule("fooRule", "foo");
$this->assertFalse($v->validate());
}
public function testAddAddRuleWithCallback()
{
$v = new Validator(array("foo" => "bar"));
$v->rule(function($field, $value) {
return $field === "foo" && $value === "bar";
}, "foo");
$this->assertValid($v);
}
public function testAddAddRuleWithCallbackFail()
{
$v = new Validator(array("foo" => "baz"));
$v->rule(function($field, $value) {
return $field === "foo" && $value === "bar";
}, "foo");
$this->assertFalse($v->validate());
}
public function testAddAddRuleWithCallbackFailMessage()
{
$v = new Validator(array("foo" => "baz"));
$v->rule(function($field, $value) {
return $field === "foo" && $value === "bar";
}, "foo", "test error message");
$this->assertFalse($v->validate());
$errors = $v->errors();
$this->assertArrayHasKey("foo", $errors);
$this->assertCount(1, $errors["foo"]);
$this->assertEquals("Foo test error message", $errors["foo"][0]);
}
public function testAddRuleWithNamedCallbackOk()
{
$v = new Validator(array("bar" => "foo"));
$v->rule("callbackTestFunction", "bar");
$this->assertFalse($v->validate());
}
public function testAddRuleWithNamedCallbackErr()
{
$v = new Validator(array("foo" => "bar"));
$v->rule("callbackTestFunction", "foo");
$this->assertTrue($v->validate());
}
public function testUniqueRuleName()
{
$v = new Validator(array());
$args = array("foo", "bar");
$this->assertEquals("foo_bar_rule", $v->getUniqueRuleName($args));
$this->assertEquals("foo_rule", $v->getUniqueRuleName("foo"));
$v->addInstanceRule("foo_rule", function() {});
$u = $v->getUniqueRuleName("foo");
$this->assertRegExp("/^foo_rule_[0-9]{1,5}$/", $u);
}
}