Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Patch regex rule parsing due to Rule::forEach() #40941

Merged
merged 12 commits into from
Feb 11, 2022
25 changes: 17 additions & 8 deletions src/Illuminate/Validation/ValidationRuleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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);
}

/**
Expand Down
56 changes: 56 additions & 0 deletions tests/Validation/ValidationForEachTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
60 changes: 60 additions & 0 deletions tests/Validation/ValidationRuleParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
3 changes: 3 additions & 0 deletions tests/Validation/ValidationValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down