diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index b11d7e1e..ef624eb9 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -137,7 +137,9 @@ public static function assertField( */ public static function is(mixed $value, string $expected): bool { - foreach (explode('|', $expected) as $item) { + $items = explode('|', $expected); + for ($i = 0, $count = count($items); $i < $count; $i++) { + $item = $items[$i]; if (str_ends_with($item, '[]')) { if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { return true; @@ -152,7 +154,16 @@ public static function is(mixed $value, string $expected): bool } [$type] = $item = explode(':', $item, 2); - if (isset(static::$validators[$type])) { + if ($type === 'pattern') { + // A pattern may contain pipes, so the rest belongs to it and must not be + // split into further validators. + $pattern = implode('|', array_merge(array_slice($item, 1), array_slice($items, $i + 1))); + if (Strings::match($value, '~^(?:' . $pattern . ')$~D')) { + return true; + } + + break; + } elseif (isset(static::$validators[$type])) { try { if (!static::$validators[$type]($value)) { continue; @@ -160,12 +171,6 @@ public static function is(mixed $value, string $expected): bool } catch (\TypeError) { continue; } - } elseif ($type === 'pattern') { - if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { - return true; - } - - continue; } elseif (!$value instanceof $type) { continue; } diff --git a/tests/Utils/Validators.is().phpt b/tests/Utils/Validators.is().phpt index c15eaf43..012c0424 100644 --- a/tests/Utils/Validators.is().phpt +++ b/tests/Utils/Validators.is().phpt @@ -259,6 +259,25 @@ test('validates string against a regular expression pattern', function () { }); +test('allows pipe inside a regular expression pattern', function () { + // grouped alternation, https://github.com/nette/utils/issues/206 + Assert::true(Validators::is('a', 'pattern:(a|b)')); + Assert::true(Validators::is('b', 'pattern:(a|b)')); + Assert::false(Validators::is('c', 'pattern:(a|b)')); + + // bare alternation must no longer throw and is anchored as a whole + Assert::true(Validators::is('a', 'pattern:a|b')); + Assert::true(Validators::is('b', 'pattern:a|b')); + Assert::false(Validators::is('c', 'pattern:a|b')); + Assert::false(Validators::is('ab', 'pattern:a|b')); + + // a pattern combined with other validators + Assert::true(Validators::is(5, 'int|pattern:(a|b)')); + Assert::true(Validators::is('a', 'int|pattern:(a|b)')); + Assert::false(Validators::is('c', 'int|pattern:(a|b)')); +}); + + test('ensures alphanumeric string meets minimum length', function () { Assert::false(Validators::is('', 'alnum')); Assert::false(Validators::is('a-1', 'alnum'));