diff --git a/README.md b/README.md index c6bd85a..f0747c6 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,29 @@ Valitron\Validator::addRule('alwaysFail', function($field, $value, array $params }, '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 As the number of rules grows, you may prefer the alternate syntax @@ -285,6 +308,19 @@ $v->validate(); This introduces a new set of tags to your error language file which looks like `{field}`, if you are using a rule like `equals` you can access the second value in the language file by incrementing the field with a value like `{field1}`. +## Re-use of validation rules + +You can re-use your validation rules to quickly validate different data with the same rules by using the withData method: + +```php +$v = new Valitron\Validator(array()); +$v->rule('required', 'name')->message('{field} is required'); +$v->validate(); //false + +$v2 = $v->withData(array('name'=>'example')); +$v2->validate(); //true +``` + ## Running Tests The test suite depends on the Composer autoloader to load and run the diff --git a/lang/en.php b/lang/en.php index 57053eb..3765c96 100644 --- a/lang/en.php +++ b/lang/en.php @@ -7,14 +7,14 @@ return array( 'accepted' => "must be accepted", 'numeric' => "must be numeric", 'integer' => "must be an integer (0-9)", - 'length' => "must be at least %d long", + 'length' => "must be %d characters long", 'min' => "must be at least %s", 'max' => "must be no more than %s", 'in' => "contains invalid value", 'notIn' => "contains invalid value", 'ip' => "is not a valid IP address", 'email' => "is not a valid email address", - 'url' => "not a URL", + 'url' => "is not a valid URL", 'urlActive' => "must be an active domain", 'alpha' => "must contain only letters a-z", 'alphaNum' => "must contain only letters a-z and/or numbers 0-9", @@ -28,7 +28,7 @@ return array( 'boolean' => "must be a boolean", 'lengthBetween' => "must be between %d and %d characters", 'creditCard' => "must be a valid credit card number", - "lengthMin" => "must contain greater than %d characters", - "lengthMax" => "must contain less than %d characters", - "instanceOf" => "must be an instance of '%s'" + 'lengthMin' => "must be at least %d characters long", + 'lengthMax' => "must not exceed %d characters", + 'instanceOf' => "must be an instance of '%s'" ); diff --git a/lang/no.php b/lang/no.php new file mode 100644 index 0000000..44a8304 --- /dev/null +++ b/lang/no.php @@ -0,0 +1,34 @@ + "er nødvendig", + 'equals' => "må være de samme som '%s'", + 'different' => "må være annerledes enn '%s'", + 'accepted' => "må aksepteres", + 'numeric' => "må være numerisk", + 'integer' => "må være et heltall (0-9)", + 'length' => "må være %d tegn", + 'min' => "må være minst %s", + 'max' => "må ikke være mer enn %s", + 'in' => "inneholder ugyldig verdi", + 'notIn' => "inneholder ugyldig verdi", + 'ip' => "er ikkje ein gyldig IP Adresse", + 'email' => "er ikkje ein gyldig E-post adresse", + 'url' => "er ikkje ein gyldig URL", + 'urlActive' => "må være eit aktivt domene", + 'alpha' => "må bare innholde bokstaver a-z", + 'alphaNum' => "må bare innholde bokstaver a-z og/eller tall 0-9", + 'slug' => "må bare innholde bokstaver a-z og/eller tall 0-9, bindestreker og understreker", + 'regex' => "inneholder ulovlige tegn", + 'date' => "er ikkje ein gylid dato", + 'dateFormat' => "må være ein dato med formatet '%s'", + 'dateBefore' => "må være ein dato før '%s'", + 'dateAfter' => "må være ein dato etter '%s'", + 'contains' => "må inneholde %s", + 'boolean' => "må være ein boolsk verdi", + 'lengthBetween' => "må være imellom %d og %d tegn", + 'creditCard' => "må være et gyldig kredittkortnummer", + 'lengthMin' => "må være minst %d tegn", + 'lengthMax' => "må ikkje overstige %d tegn", + 'instanceOf' => "må være ein instans av '%s'" +); diff --git a/lang/ru.php b/lang/ru.php index 78fe98f..2ce1cad 100644 --- a/lang/ru.php +++ b/lang/ru.php @@ -6,7 +6,7 @@ return array( 'different' => "должно отличаться от '%s'", 'accepted' => "должно быть указано", 'numeric' => "должно содержать числовое значение", - 'integer' => "должно быть числом", + 'integer' => "должно быть числом (0-9)", 'length' => "должно быть длиннее, чем %d", 'min' => "должно быть больше, чем %s", 'max' => "должно быть меньше, чем %s", @@ -14,7 +14,7 @@ return array( 'notIn' => "содержит неверное значение", 'ip' => "не является валидным IP адресом", 'email' => "не является валидным email адресом", - 'url' => "не является ссылкой", + 'url' => "не является валидной ссылкой", 'urlActive' => "содержит не активную ссылку", 'alpha' => "должно содержать только латинские символы", 'alphaNum' => "должно содержать только латинские символы и/или цифры", @@ -28,6 +28,7 @@ return array( 'boolean' => "должно содержать логическое значение", 'lengthBetween' => "должно содержать от %d до %d символов", 'creditCard' => "должно быть номером кредитной карты", - "lengthMin" => "должно содержать более %d символов", - "lengthMax" => "должно содержать менее %d символов" + 'lengthMin' => "должно содержать более %d символов", + 'lengthMax' => "должно содержать менее %d символов", + 'instanceOf' => "должно быть объектом класса '%s'" ); diff --git a/lang/sk.php b/lang/sk.php new file mode 100644 index 0000000..8894716 --- /dev/null +++ b/lang/sk.php @@ -0,0 +1,34 @@ + "je povinná položka", + 'equals' => "musí byť rovnaký ako '%s'", + 'different' => "musí byť rôzny od '%s'", + 'accepted' => "musí byť akceptovaný", + 'numeric' => "musí byť číslo", + 'integer' => "musí byť celé číslo (0-9)", + 'length' => "musí byť dlhý aspoň %d", + 'min' => "musí byť dlhý minimálne %s", + 'max' => "musí byť maximálne %s", + 'in' => "obsahuje nepovolenú hodnotu", + 'notIn' => "obsahuje nepovolenú hodnotu", + 'ip' => "nie je korektná IP adresa", + 'email' => "nie je korektný e-mail", + 'url' => "nie je URL", + 'urlActive' => "musí byť aktívna URL", + 'alpha' => "musí obsahovať len písmená a-z", + 'alphaNum' => "musí obsahovať len písmená a-z a/alebo čísla 0-9", + 'slug' => "musí obsahovať len písmená a-z, čísla 0-9, pomlčky alebo podtržítka", + 'regex' => "obsahuje nepovolené znaky", + 'date' => "nie je korektný formáť", + 'dateFormat' => "musí byť dátum formátu '%s'", + 'dateBefore' => "musí byť dátum pred '%s'", + 'dateAfter' => "musí byť dátum po '%s'", + 'contains' => "musí obsahovať %s", + 'boolean' => "musí byť pravdivostná hodnota (boolean)", + 'lengthBetween' => "musí byť %d až %d znakov dlhý", + 'creditCard' => "musí byť korektné číslo kreditnej karty", + "lengthMin" => "musí byť aspoň %d znakov dlhý", + "lengthMax" => "musí byť najviac %d znakov dlhý", + "instanceOf" => "musí byť inštanciou '%s'" +); diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index 4601a5b..e78ea57 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -989,6 +989,7 @@ class Validator * @param string $field The name of the field * @return boolean */ + protected function hasRule($name, $field) { foreach ($this->_validations as $validation) { diff --git a/tests/Valitron/ValidateAddInstanceRuleTest.php b/tests/Valitron/ValidateAddInstanceRuleTest.php new file mode 100644 index 0000000..ddefac3 --- /dev/null +++ b/tests/Valitron/ValidateAddInstanceRuleTest.php @@ -0,0 +1,120 @@ +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); + } +} diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index ac3ffca..4a91b33 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -994,7 +994,7 @@ class ValidateTest extends BaseTestCase public function testCreditCardValid() { $visa = array(4539511619543489, 4532949059629052, 4024007171194938, 4929646403373269, 4539135861690622); - $mastercard = array(5162057048081965, 5382687859049349, 5484388880142230, 5464941521226434, 5473481232685965); + $mastercard = array(5162057048081965, 5382687859049349, 5484388880142230, 5464941521226434, 5473481232685965, 2223000048400011, 2223520043560014); $amex = array(371442067262027, 340743030537918, 345509167493596, 343665795576848, 346087552944316); $dinersclub = array(30363194756249, 30160097740704, 38186521192206, 38977384214552, 38563220301454); $discover = array(6011712400392605, 6011536340491809, 6011785775263015, 6011984124619056, 6011320958064251); @@ -1155,7 +1155,26 @@ class ValidateTest extends BaseTestCase $v = new Validator(array()); $v->rule('optional', 'address')->rule('email', 'address'); $this->assertTrue($v->validate()); - } + } + + public function testWithData() + { + $v = new Validator(array()); + $v->rule('required', 'name'); + //validation failed, so must have errors + $this->assertFalse($v->validate()); + $this->assertNotEmpty($v->errors()); + + //create copy with valid data + $v2 = $v->withData(array('name' => 'Chester Tester')); + $this->assertTrue($v2->validate()); + $this->assertEmpty($v2->errors()); + + //create copy with invalid data + $v3 = $v->withData(array('firstname' => 'Chester')); + $this->assertFalse($v3->validate()); + $this->assertNotEmpty($v3->errors()); + } } function sampleFunctionCallback($field, $value, array $params) {