diff --git a/src/Illuminate/Validation/Rule.php b/src/Illuminate/Validation/Rule.php index c15772bba7d5..4a0e3576a39c 100644 --- a/src/Illuminate/Validation/Rule.php +++ b/src/Illuminate/Validation/Rule.php @@ -18,6 +18,7 @@ use Illuminate\Validation\Rules\NotIn; use Illuminate\Validation\Rules\Numeric; use Illuminate\Validation\Rules\ProhibitedIf; +use Illuminate\Validation\Rules\RegExp; use Illuminate\Validation\Rules\RequiredIf; use Illuminate\Validation\Rules\Unique; @@ -243,4 +244,15 @@ public static function numeric() { return new Numeric; } + + /** + * Get a regex rule builder rule instance. + * + * @param string $expression + * @param string[]|\Illuminate\Contracts\Support\Arrayable $extraFlags + */ + public static function regex(string $expression, array|Arrayable $extraFlags = []) + { + return new RegExp($expression, $extraFlags); + } } diff --git a/src/Illuminate/Validation/Rules/RegExp.php b/src/Illuminate/Validation/Rules/RegExp.php new file mode 100644 index 000000000000..ebfe0a037121 --- /dev/null +++ b/src/Illuminate/Validation/Rules/RegExp.php @@ -0,0 +1,81 @@ +afterLast('/')); + + if ($extraFlags instanceof Arrayable) { + $extraFlags = $extraFlags->toArray(); + } + + $this->expression = $str->beforeLast('/')->append('/')->toString(); + $this->flags = array_unique([...$currentFlags, ...$extraFlags]); + } + + /** + * @param string[]|\Illuminate\Contracts\Support\Arrayable $flags + */ + public function flags($flags = []) + { + $this->flags = match (true) { + $flags instanceof Arrayable => $flags->toArray(), + ! is_array($flags) => func_get_args(), + default => $flags, + }; + + return $this; + } + + public function flag(string $flag) + { + $this->flags = array_unique([...$this->flags, $flag]); + + return $this; + } + + public function not() + { + $this->negated = true; + + return $this; + } + + /** + * Convert the rule to a validation string. + * + * @return string + */ + public function __toString() + { + return sprintf( + '%s:%s%s', + $this->negated ? 'not_regex' : 'regex', + $this->expression, + implode('', $this->flags), + ); + } +} diff --git a/tests/Validation/ValidationRegExpRuleTest.php b/tests/Validation/ValidationRegExpRuleTest.php new file mode 100644 index 000000000000..3d62089da0c6 --- /dev/null +++ b/tests/Validation/ValidationRegExpRuleTest.php @@ -0,0 +1,77 @@ +assertSame('regex:/[a-z]/i', (string) $rule); + } + + public function testNotRegExpRuleStringification() + { + $rule = Rule::regex('/[a-z]/i')->not(); + + $this->assertSame('not_regex:/[a-z]/i', (string) $rule); + } + + #[TestWith(['/[a-z]/', ['i'], 'regex:/[a-z]/i'])] + #[TestWith(['/[a-z]/i', ['i'], 'regex:/[a-z]/i'])] + #[TestWith(['/[a-z]/g', ['i'], 'regex:/[a-z]/gi'])] + public function testRegExpRuleConstructorFlagsStringification(string $input, array $flags, string $output) + { + $rule = Rule::regex($input, $flags); + + $this->assertSame($output, (string) $rule); + } + + public function tesRegExpRuleConstructorFlagDataTypesStringification() + { + $rule = Rule::regex('/[a-z]/', []); + + $this->assertSame('regex:/[a-z]/', (string) $rule); + + $rule = Rule::regex('/[a-z]/', ['i']); + + $this->assertSame('regex:/[a-z]/i', (string) $rule); + + $rule = Rule::regex('/[a-z]/', collect(['i'])); + + $this->assertSame('regex:/[a-z]/i', (string) $rule); + } + + public function testRegExpRuleFlagsStringification() + { + $rule = Rule::regex('/[a-z]/')->flags(); + + $this->assertSame('regex:/[a-z]/', (string) $rule); + + $rule = Rule::regex('/[a-z]/')->flags([]); + + $this->assertSame('regex:/[a-z]/', (string) $rule); + + $rule = Rule::regex('/[a-z]/')->flags(['i']); + + $this->assertSame('regex:/[a-z]/i', (string) $rule); + + $rule = Rule::regex('/[a-z]/')->flags(collect(['i'])); + + $this->assertSame('regex:/[a-z]/i', (string) $rule); + } + + #[TestWith(['i', 'regex:/[a-z]/i'])] + #[TestWith(['g', 'regex:/[a-z]/ig'])] + public function testRegExpRuleFlagStringification(string $input, string $output) + { + $rule = Rule::regex('/[a-z]/i')->flag($input); + + $this->assertSame($output, (string) $rule); + } +}