diff --git a/src/Illuminate/Validation/ValidationRuleParser.php b/src/Illuminate/Validation/ValidationRuleParser.php index 4895f922614c..653cbd6bd59d 100644 --- a/src/Illuminate/Validation/ValidationRuleParser.php +++ b/src/Illuminate/Validation/ValidationRuleParser.php @@ -85,8 +85,12 @@ protected function explodeRules($rules) protected function explodeExplicitRule($rule, $attribute) { if (is_string($rule)) { - return explode('|', $rule); - } elseif (is_object($rule)) { + [$name] = static::parseStringRule($rule); + + return static::ruleIsRegex($name) ? [$rule] : explode('|', $rule); + } + + if (is_object($rule)) { return Arr::wrap($this->prepareRule($rule, $attribute)); } @@ -272,13 +276,18 @@ protected static function parseStringRule($rule) */ protected static function parseParameters($rule, $parameter) { - $rule = strtolower($rule); - - if (in_array($rule, ['regex', 'not_regex', 'notregex'], true)) { - return [$parameter]; - } + return static::ruleIsRegex($rule) ? [$parameter] : str_getcsv($parameter); + } - return str_getcsv($parameter); + /** + * Determine if the rule is a regular expression. + * + * @param string $rule + * @return bool + */ + protected static function ruleIsRegex($rule) + { + return in_array(strtolower($rule), ['regex', 'not_regex', 'notregex'], true); } /** diff --git a/tests/Validation/ValidationForEachTest.php b/tests/Validation/ValidationForEachTest.php index 876b9d0987b3..c836844680b5 100644 --- a/tests/Validation/ValidationForEachTest.php +++ b/tests/Validation/ValidationForEachTest.php @@ -201,6 +201,62 @@ public function testForEachCallbacksCanReturnDifferentRules() ], $v->getMessageBag()->toArray()); } + public function testForEachCallbacksDoNotBreakRegexRules() + { + $data = [ + 'items' => [ + ['users' => [['type' => 'super'], ['type' => 'invalid']]], + ], + ]; + + $rules = [ + 'items.*' => Rule::forEach(function () { + return ['users.*.type' => 'regex:/^(super|admin)$/i']; + }), + ]; + + $trans = $this->getIlluminateArrayTranslator(); + + $v = new Validator($trans, $data, $rules); + + $this->assertFalse($v->passes()); + + $this->assertEquals([ + 'items.0.users.1.type' => ['validation.regex'], + ], $v->getMessageBag()->toArray()); + } + + public function testForEachCallbacksCanContainMultipleRegexRules() + { + $data = [ + 'items' => [ + ['users' => [['type' => 'super'], ['type' => 'invalid']]], + ], + ]; + + $rules = [ + 'items.*' => Rule::forEach(function () { + return ['users.*.type' => [ + 'regex:/^(super)$/i', + 'notregex:/^(invalid)$/i', + ]]; + }), + ]; + + $trans = $this->getIlluminateArrayTranslator(); + + $v = new Validator($trans, $data, $rules); + + $this->assertFalse($v->passes()); + + $this->assertEquals([ + 'items.0.users.1.type' => [ + 'validation.regex', + 'validation.notregex', + ], + ], $v->getMessageBag()->toArray()); + } + protected function getTranslator() { return m::mock(TranslatorContract::class); diff --git a/tests/Validation/ValidationRuleParserTest.php b/tests/Validation/ValidationRuleParserTest.php index c54f56278f3b..49b36dbf168f 100644 --- a/tests/Validation/ValidationRuleParserTest.php +++ b/tests/Validation/ValidationRuleParserTest.php @@ -89,6 +89,66 @@ public function testEmptyConditionalRulesArePreserved() ], $rules); } + public function testExplodeProperlyParsesSingleRegexRule() + { + $data = ['items' => [['type' => 'foo']]]; + + $exploded = (new ValidationRuleParser($data))->explode( + ['items.*.type' => 'regex:/^(foo|bar)$/i'] + ); + + $this->assertEquals('regex:/^(foo|bar)$/i', $exploded->rules['items.0.type'][0]); + } + + public function testExplodeProperlyParsesRegexWithArrayOfRules() + { + $data = ['items' => [['type' => 'foo']]]; + + $exploded = (new ValidationRuleParser($data))->explode( + ['items.*.type' => ['in:foo', 'regex:/^(foo|bar)$/i']] + ); + + $this->assertEquals('in:foo', $exploded->rules['items.0.type'][0]); + $this->assertEquals('regex:/^(foo|bar)$/i', $exploded->rules['items.0.type'][1]); + } + + public function testExplodeProperlyParsesRegexThatDoesNotContainPipe() + { + $data = ['items' => [['type' => 'foo']]]; + + $exploded = (new ValidationRuleParser($data))->explode( + ['items.*.type' => 'in:foo|regex:/^(bar)$/i'] + ); + + $this->assertEquals('in:foo', $exploded->rules['items.0.type'][0]); + $this->assertEquals('regex:/^(bar)$/i', $exploded->rules['items.0.type'][1]); + } + + public function testExplodeFailsParsingRegexWithOtherRulesInSingleString() + { + $data = ['items' => [['type' => 'foo']]]; + + $exploded = (new ValidationRuleParser($data))->explode( + ['items.*.type' => 'in:foo|regex:/^(foo|bar)$/i'] + ); + + $this->assertEquals('in:foo', $exploded->rules['items.0.type'][0]); + $this->assertEquals('regex:/^(foo', $exploded->rules['items.0.type'][1]); + $this->assertEquals('bar)$/i', $exploded->rules['items.0.type'][2]); + } + + public function testExplodeProperlyFlattensRuleArraysOfArrays() + { + $data = ['items' => [['type' => 'foo']]]; + + $exploded = (new ValidationRuleParser($data))->explode( + ['items.*.type' => ['in:foo', [[['regex:/^(foo|bar)$/i']]]]] + ); + + $this->assertEquals('in:foo', $exploded->rules['items.0.type'][0]); + $this->assertEquals('regex:/^(foo|bar)$/i', $exploded->rules['items.0.type'][1]); + } + public function testExplodeGeneratesNestedRules() { $parser = (new ValidationRuleParser([ diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 113c01f521a3..a0bf005441fa 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -3820,6 +3820,9 @@ public function testValidateRegex() $v = new Validator($trans, ['x' => 12], ['x' => 'Regex:/^12$/i']); $this->assertTrue($v->passes()); + + $v = new Validator($trans, ['x' => ['y' => ['z' => 'james']]], ['x.*.z' => ['Regex:/^(taylor|james)$/i']]); + $this->assertTrue($v->passes()); } public function testValidateNotRegex()