From 16e42b23c76baa5dcfd2534d44ec6a4a4867d122 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 5 Aug 2026 03:13:13 +0200 Subject: [PATCH 1/2] CI: switched Coveralls upload to the official action The php-coveralls phar is replaced by coverallsapp/github-action, which resolves the token and the absolute paths in the Clover report on its own, so tests/.coveralls.yml is no longer needed. Coverage is now uploaded even when tests crash under phpdbg. Being phpdbg-compatible is not a goal, but until now the failing step aborted the job before the upload ever ran. The job-level continue-on-error is gone, so genuine failures, including a rejected upload, are visible. --- .github/workflows/tests.yml | 10 +++++----- tests/.coveralls.yml | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 tests/.coveralls.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cdca0c074..f8baedae9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,6 @@ jobs: code_coverage: name: Code Coverage runs-on: ubuntu-latest - continue-on-error: true steps: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 @@ -44,7 +43,8 @@ jobs: - run: composer install --no-progress --prefer-dist - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src - - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: php php-coveralls.phar --verbose --config tests/.coveralls.yml + continue-on-error: true + - uses: coverallsapp/github-action@v2 + with: + file: coverage.xml + format: clover diff --git a/tests/.coveralls.yml b/tests/.coveralls.yml deleted file mode 100644 index 845038250..000000000 --- a/tests/.coveralls.yml +++ /dev/null @@ -1,4 +0,0 @@ -# for php-coveralls -service_name: github-actions -coverage_clover: coverage.xml -json_path: coverage.json From dc5e00bdb1a95bc136f10dedf08d3ff4219e4a41 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 4 Sep 2026 09:22:41 +0200 Subject: [PATCH 2/2] fix(Validators): allow pipe in pattern regular expressions The 'pattern:' argument of Validators::is() crashed on a valid regular expression containing a pipe, e.g. Validators::is('a', 'pattern:(a|b)'). Two parsing bugs combined: the expression was split into validators on every '|', tearing the regexp apart, and the pipe was also used as the PCRE delimiter. A 'pattern:' argument now consumes the rest of the expression and the delimiter is changed to '~', with the pattern anchored as a whole so bare alternations work too. Pipe-as-OR semantics for combined validators are kept. Fixes nette/utils#206 --- src/Utils/Validators.php | 21 +++++++++++++-------- tests/Utils/Validators.is().phpt | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index b11d7e1e6..ef624eb98 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 c15eaf439..012c04246 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'));