From 36561dc8836cf27366c42633b55e270668c87b72 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sun, 14 Dec 2025 07:07:37 +0100 Subject: [PATCH 01/18] Various fixes and minor improvements --- .editorconfig | 14 ++++++++++++++ composer.json | 8 ++++---- features/bootstrap/FeatureContext.php | 12 ++++++++---- src/Exception/ArrayContainsComparatorException.php | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ee3f235 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{json,yaml,yml,xml}] +indent_style = space +indent_size = 2 + +[*.php] +indent_style = space +indent_size = 4 diff --git a/composer.json b/composer.json index 74a3baf..5feb849 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ ], "support": { "source": "https://github.com/imbo/behat-api-extension", - "docs": "http://behat-api-extension.readthedocs.io/", + "docs": "https://behat-api-extension.readthedocs.io/", "issues": "https://github.com/imbo/behat-api-extension/issues" }, "require": { @@ -60,7 +60,7 @@ } }, "scripts": { - "behat": "vendor/bin/behat --strict", + "behat": "vendor/bin/behat --strict --colors", "ci": [ "@behat", "@cs", @@ -71,8 +71,8 @@ "cs:fix": "vendor/bin/php-cs-fixer fix --diff", "dev": "php -S localhost:8080 -t ./features/bootstrap", "docs": "cd docs; make html", - "phpunit": "vendor/bin/phpunit", - "phpunit:coverage": "vendor/bin/phpunit --coverage-html build/coverage", + "phpunit": "vendor/bin/phpunit --colors=always", + "phpunit:coverage": "@phpunit --coverage-html build/coverage", "sa": "vendor/bin/phpstan", "test": [ "@behat", diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 638ad2b..ece09b0 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -81,7 +81,11 @@ public function createFile(string $filename, PyStringNode $content, bool $readab file_put_contents($filename, $content); if (!$readable) { - chmod($filename, 0000); + if (PHP_OS_FAMILY === 'Windows') { + exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(F)'); + } else { + chmod($filename, 0000); + } } } @@ -218,7 +222,7 @@ private function getOutput(): string $output = $this->process->getErrorOutput().$this->process->getOutput(); - return trim((string) preg_replace('/ +$/m', '', $output)); + return trim((string) preg_replace(['/ +$/m', '/\r\n/'], ['', "\n"], $output)); } /** @@ -235,11 +239,11 @@ private static function rmdir(string $path): void if (is_dir($file)) { self::rmdir($file); } else { - unlink($file); + @unlink($file); } } // Remove the remaining directory - rmdir($path); + @rmdir($path); } } diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index fbd0bc4..9440406 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -16,7 +16,7 @@ class ArrayContainsComparatorException extends AssertionFailedException { public function __construct(string $message, int $code = 0, ?Throwable $previous = null, mixed $needle = null, mixed $haystack = null) { - $message .= PHP_EOL.PHP_EOL.sprintf( + $message .= "\n\n" . sprintf( << Date: Sun, 14 Dec 2025 08:01:57 +0100 Subject: [PATCH 02/18] Leaner composer packages; support local config files --- .gitattributes | 11 +++++++++++ .php-cs-fixer.dist.php | 1 + 2 files changed, 12 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2ccac70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +.github export-ignore +docs export-ignore +features export-ignore +tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php-cs-fixer.dist.php export-ignore +.readthedocs.yaml export-ignore +phpstan.dist.neon export-ignore +phpunit.xml.dist export-ignore diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 62a0c66..965e7ff 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,4 +1,5 @@ Date: Wed, 14 Jan 2026 21:51:21 +0100 Subject: [PATCH 03/18] Couple of minor fixes --- features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index ece09b0..03b0f17 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -82,7 +82,7 @@ public function createFile(string $filename, PyStringNode $content, bool $readab if (!$readable) { if (PHP_OS_FAMILY === 'Windows') { - exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(F)'); + exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(R)'); } else { chmod($filename, 0000); } From 5646357b58da8aeef92efd966b1c1d0b4237299e Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 14 Jan 2026 21:54:13 +0100 Subject: [PATCH 04/18] CS fix --- features/bootstrap/FeatureContext.php | 4 ++-- src/Exception/ArrayContainsComparatorException.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 03b0f17..fc238fe 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -81,8 +81,8 @@ public function createFile(string $filename, PyStringNode $content, bool $readab file_put_contents($filename, $content); if (!$readable) { - if (PHP_OS_FAMILY === 'Windows') { - exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(R)'); + if (\PHP_OS_FAMILY === 'Windows') { + exec('icacls '.escapeshellarg($filename).' /deny Everyone:(R)'); } else { chmod($filename, 0000); } diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index 9440406..00b5e86 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -7,7 +7,6 @@ use function sprintf; use const JSON_PRETTY_PRINT; -use const PHP_EOL; /** * Array contains comparator exception. From 4a20a5effad63b2e180355634ed8fba0c8414f92 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 24 Jan 2026 16:02:26 +0100 Subject: [PATCH 05/18] Test on Windows --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6503468..4ac605a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,11 @@ on: - main jobs: ci: - runs-on: ubuntu-latest strategy: fail-fast: false matrix: + os: + - ubuntu-latest composer-script: - behat - cs @@ -19,6 +20,11 @@ jobs: - "8.3" - "8.4" - "8.5" + include: + - os: windows-latest + php: "8.5" + composer-script: phpunit + runs-on: ${{ matrix.php }} name: composer run ${{ matrix.composer-script }} / PHP ${{ matrix.php }} steps: - uses: actions/checkout@v6 From 7c7011b8fc2caddb2b64ae4d389996ce2cf311cf Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 24 Jan 2026 16:21:45 +0100 Subject: [PATCH 06/18] CS fix --- features/bootstrap/index.php | 32 +++++++++---------- src/ArrayContainsComparator.php | 2 +- .../Matcher/VariableType.php | 2 +- src/Context/ApiContext.php | 2 +- .../ArrayContainsComparatorException.php | 2 +- tests/ArrayContainsComparatorTest.php | 6 ++-- tests/Context/ApiContextTest.php | 2 +- .../ApiClientAwareInitializerTest.php | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/features/bootstrap/index.php b/features/bootstrap/index.php index b7c1c23..956008b 100644 --- a/features/bootstrap/index.php +++ b/features/bootstrap/index.php @@ -24,7 +24,7 @@ /** * Front page. */ -$app->any('/', function (Request $request, Response $response): Response { +$app->any('/', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ 'null' => null, 'string' => 'value', @@ -60,7 +60,7 @@ /** * List with objects. */ -$app->any('/list', function (Request $request, Response $response): Response { +$app->any('/list', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ [ 'integer' => 123, @@ -75,7 +75,7 @@ /** * Echo the request body. */ -$app->any('/echo', function (Request $request, Response $response): Response { +$app->any('/echo', static function (Request $request, Response $response): Response { // Set the same Content-Type header in the response as found in the request if ($contentType = $request->getHeaderLine('Content-Type')) { $response = $response->withHeader('Content-Type', $contentType); @@ -96,7 +96,7 @@ /** * Return information about uploaded files. */ -$app->post('/files', function (Request $request, Response $response): Response { +$app->post('/files', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode($_FILES)); return $response->withHeader('Content-Type', 'application/json'); @@ -105,7 +105,7 @@ /** * Return information about the request. */ -$app->any('/requestInfo', function (Request $request, Response $response): Response { +$app->any('/requestInfo', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ '_GET' => $_GET, '_POST' => $_POST, @@ -120,7 +120,7 @@ /** * Return the HTTP method. */ -$app->any('/echoHttpMethod', function (Request $request, Response $response): Response { +$app->any('/echoHttpMethod', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ 'method' => $request->getMethod(), ])); @@ -131,7 +131,7 @@ /** * Return the authenticated user name. */ -$app->any('/basicAuth', function (Request $request, Response $response) { +$app->any('/basicAuth', static function (Request $request, Response $response) { $response->getBody()->write((string) json_encode([ 'user' => explode(':', $request->getUri()->getUserInfo())[0], ])); @@ -142,7 +142,7 @@ /** * Return access token given the correct credentials. */ -$app->any('/oauth/token', function (Request $request, Response $response) { +$app->any('/oauth/token', static function (Request $request, Response $response) { /** @var array{username: string, password: string} */ $body = $request->getParsedBody(); @@ -165,7 +165,7 @@ /** * Return secured resource if Authorization header is valid. */ -$app->any('/securedWithOAuth', function (Request $request, Response $response) { +$app->any('/securedWithOAuth', static function (Request $request, Response $response) { if ('Bearer some_access_token' === $request->getHeaderLine('Authorization')) { $responseBody = [ 'users' => [ @@ -187,7 +187,7 @@ /** * Return a client error. */ -$app->any('/clientError', function (Request $request, Response $response): Response { +$app->any('/clientError', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ 'error' => 'client error', ])); @@ -200,7 +200,7 @@ /** * @see https://github.com/imbo/behat-api-extension/issues/13 */ -$app->any('/issue-13', function (Request $request, Response $response): Response { +$app->any('/issue-13', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([ 'customer' => [ 'id' => '12345', @@ -231,7 +231,7 @@ /** * Return a response with a custom reason phrase. */ -$app->get('/customReasonPhrase', function (Request $request, Response $response): Response { +$app->get('/customReasonPhrase', static function (Request $request, Response $response): Response { $params = $request->getQueryParams(); return $response->withStatus( @@ -243,7 +243,7 @@ /** * Return a response with an empty array. */ -$app->get('/emptyArray', function (Request $request, Response $response): Response { +$app->get('/emptyArray', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode([])); return $response->withHeader('Content-Type', 'application/json'); @@ -252,7 +252,7 @@ /** * Return a response with an empty object. */ -$app->get('/emptyObject', function (Request $request, Response $response): Response { +$app->get('/emptyObject', static function (Request $request, Response $response): Response { $response->getBody()->write((string) json_encode(new stdClass())); return $response->withHeader('Content-Type', 'application/json'); @@ -261,14 +261,14 @@ /** * Return a response with an empty body. */ -$app->get('/empty', function (Request $request, Response $response): Response { +$app->get('/empty', static function (Request $request, Response $response): Response { return $response->withStatus(204); }); /** * Return a response with 403 Forbidden. */ -$app->get('/403', function (Request $request, Response $response): Response { +$app->get('/403', static function (Request $request, Response $response): Response { return $response->withStatus(403); }); diff --git a/src/ArrayContainsComparator.php b/src/ArrayContainsComparator.php index 723cf24..9a94d9a 100644 --- a/src/ArrayContainsComparator.php +++ b/src/ArrayContainsComparator.php @@ -148,7 +148,7 @@ protected function compareValues(mixed $needleValue, mixed $haystackValue): bool // List of available function names $functions = array_map( - fn (string $value): string => preg_quote($value, '/'), + static fn (string $value): string => preg_quote($value, '/'), array_keys($this->functions), ); diff --git a/src/ArrayContainsComparator/Matcher/VariableType.php b/src/ArrayContainsComparator/Matcher/VariableType.php index fc68ef1..2f4799f 100644 --- a/src/ArrayContainsComparator/Matcher/VariableType.php +++ b/src/ArrayContainsComparator/Matcher/VariableType.php @@ -81,7 +81,7 @@ public function __invoke(mixed $variable, string $expectedTypes): bool protected function normalizeTypes(string $types): array { $types = array_map( - fn (string $type): string => trim(strtolower($type)), + static fn (string $type): string => trim(strtolower($type)), explode('|', $types), ); diff --git a/src/Context/ApiContext.php b/src/Context/ApiContext.php index 80a07b1..e107c89 100644 --- a/src/Context/ApiContext.php +++ b/src/Context/ApiContext.php @@ -386,7 +386,7 @@ public function addJwtToken(string $name, string $secret, PyStringNode $payload) { $jwtMatcher = $this->arrayContainsComparator->getMatcherFunction('jwt'); - if (!($jwtMatcher instanceof JwtMatcher)) { + if (!$jwtMatcher instanceof JwtMatcher) { throw new RuntimeException(sprintf('Matcher registered for the @jwt() matcher function must be an instance of %s', JwtMatcher::class)); } diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index 00b5e86..f285cc6 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -15,7 +15,7 @@ class ArrayContainsComparatorException extends AssertionFailedException { public function __construct(string $message, int $code = 0, ?Throwable $previous = null, mixed $needle = null, mixed $haystack = null) { - $message .= "\n\n" . sprintf( + $message .= "\n\n".sprintf( << [ 'function' => 'customFunction', - 'callback' => fn (string $subject, string $param): bool => strtoupper($subject) === $param, + 'callback' => static fn (string $subject, string $param): bool => strtoupper($subject) === $param, 'needle' => [ 'key' => '@customFunction(BAR)', ], @@ -494,7 +494,7 @@ public static function getCustomFunctionsAndDataThatWillFail(): array // @customFunction [ 'function' => 'customFunction', - 'callback' => function (): void { + 'callback' => static function (): void { throw new InvalidArgumentException('Some custom error message'); }, 'needle' => [ @@ -1062,7 +1062,7 @@ public function testSupportsInArrayCheckWhenListsAreInADeepStructure(): void public function testCanReturnRegisteredMatcherFunction(): void { - $f = fn (): bool => true; + $f = static fn (): bool => true; $this->comparator->addFunction('function', $f); $this->assertSame( $f, diff --git a/tests/Context/ApiContextTest.php b/tests/Context/ApiContextTest.php index e4965bc..07aa362 100644 --- a/tests/Context/ApiContextTest.php +++ b/tests/Context/ApiContextTest.php @@ -863,7 +863,7 @@ public function testCanAssertWhichGroupTheResponseIsNotIn(string $group, array $ $this->mockHandler->append(new Response($code)); $this->context->requestPath('/some/path'); - foreach (array_filter($groups, fn (string $g): bool => $g !== $group) as $g) { + foreach (array_filter($groups, static fn (string $g): bool => $g !== $group) as $g) { // Assert that the response is not in any of the other groups $this->assertTrue($this->context->assertResponseIsNot($g)); } diff --git a/tests/Context/Initializer/ApiClientAwareInitializerTest.php b/tests/Context/Initializer/ApiClientAwareInitializerTest.php index cd05adc..bb2af76 100644 --- a/tests/Context/Initializer/ApiClientAwareInitializerTest.php +++ b/tests/Context/Initializer/ApiClientAwareInitializerTest.php @@ -21,7 +21,7 @@ public function testInjectsClientWhenInitializingContext(): void // Set up a socket for the test case, try all ports between 8000 and 8079. If no ports are // available the test case will be marked as skipped. This is to get past the base URI // validation - set_error_handler(fn () => true); + set_error_handler(static fn () => true); $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (false === $sock) { From e07109be39efdde6c23c33de415946121924eee7 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sun, 14 Dec 2025 07:07:37 +0100 Subject: [PATCH 07/18] Various fixes and minor improvements --- .editorconfig | 14 ++++++++++++++ composer.json | 6 +++--- features/bootstrap/FeatureContext.php | 12 ++++++++---- src/Exception/ArrayContainsComparatorException.php | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ee3f235 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{json,yaml,yml,xml}] +indent_style = space +indent_size = 2 + +[*.php] +indent_style = space +indent_size = 4 diff --git a/composer.json b/composer.json index 3ea3430..0c1f291 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,7 @@ } }, "scripts": { - "behat": "vendor/bin/behat --strict", + "behat": "vendor/bin/behat --strict --colors", "ci": [ "@behat", "@cs", @@ -71,8 +71,8 @@ "cs:fix": "vendor/bin/php-cs-fixer fix --diff", "dev": "php -S localhost:8080 -t ./features/bootstrap", "docs": "cd docs; make html", - "phpunit": "vendor/bin/phpunit", - "phpunit:coverage": "vendor/bin/phpunit --coverage-html build/coverage", + "phpunit": "vendor/bin/phpunit --colors=always", + "phpunit:coverage": "@phpunit --coverage-html build/coverage", "sa": "vendor/bin/phpstan", "test": [ "@behat", diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 638ad2b..ece09b0 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -81,7 +81,11 @@ public function createFile(string $filename, PyStringNode $content, bool $readab file_put_contents($filename, $content); if (!$readable) { - chmod($filename, 0000); + if (PHP_OS_FAMILY === 'Windows') { + exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(F)'); + } else { + chmod($filename, 0000); + } } } @@ -218,7 +222,7 @@ private function getOutput(): string $output = $this->process->getErrorOutput().$this->process->getOutput(); - return trim((string) preg_replace('/ +$/m', '', $output)); + return trim((string) preg_replace(['/ +$/m', '/\r\n/'], ['', "\n"], $output)); } /** @@ -235,11 +239,11 @@ private static function rmdir(string $path): void if (is_dir($file)) { self::rmdir($file); } else { - unlink($file); + @unlink($file); } } // Remove the remaining directory - rmdir($path); + @rmdir($path); } } diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index fbd0bc4..9440406 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -16,7 +16,7 @@ class ArrayContainsComparatorException extends AssertionFailedException { public function __construct(string $message, int $code = 0, ?Throwable $previous = null, mixed $needle = null, mixed $haystack = null) { - $message .= PHP_EOL.PHP_EOL.sprintf( + $message .= "\n\n" . sprintf( << Date: Sun, 14 Dec 2025 08:01:57 +0100 Subject: [PATCH 08/18] Leaner composer packages; support local config files --- .gitattributes | 11 +++++++++++ .php-cs-fixer.dist.php | 1 + 2 files changed, 12 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2ccac70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +.github export-ignore +docs export-ignore +features export-ignore +tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php-cs-fixer.dist.php export-ignore +.readthedocs.yaml export-ignore +phpstan.dist.neon export-ignore +phpunit.xml.dist export-ignore diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 62a0c66..965e7ff 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,4 +1,5 @@ Date: Wed, 14 Jan 2026 21:51:21 +0100 Subject: [PATCH 09/18] Couple of minor fixes --- features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index ece09b0..03b0f17 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -82,7 +82,7 @@ public function createFile(string $filename, PyStringNode $content, bool $readab if (!$readable) { if (PHP_OS_FAMILY === 'Windows') { - exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(F)'); + exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(R)'); } else { chmod($filename, 0000); } From 6ec3d3c228f8a62e98057ded959b946f8eab10ec Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 14 Jan 2026 21:54:13 +0100 Subject: [PATCH 10/18] CS fix --- features/bootstrap/FeatureContext.php | 4 ++-- src/Exception/ArrayContainsComparatorException.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 03b0f17..fc238fe 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -81,8 +81,8 @@ public function createFile(string $filename, PyStringNode $content, bool $readab file_put_contents($filename, $content); if (!$readable) { - if (PHP_OS_FAMILY === 'Windows') { - exec('icacls ' . escapeshellarg($filename) . ' /deny Everyone:(R)'); + if (\PHP_OS_FAMILY === 'Windows') { + exec('icacls '.escapeshellarg($filename).' /deny Everyone:(R)'); } else { chmod($filename, 0000); } diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index 9440406..00b5e86 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -7,7 +7,6 @@ use function sprintf; use const JSON_PRETTY_PRINT; -use const PHP_EOL; /** * Array contains comparator exception. From fa21456bb4fb470a23e4de2b553380e40cf22f6c Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 24 Jan 2026 16:02:26 +0100 Subject: [PATCH 11/18] Test on Windows --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6503468..4ac605a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,11 @@ on: - main jobs: ci: - runs-on: ubuntu-latest strategy: fail-fast: false matrix: + os: + - ubuntu-latest composer-script: - behat - cs @@ -19,6 +20,11 @@ jobs: - "8.3" - "8.4" - "8.5" + include: + - os: windows-latest + php: "8.5" + composer-script: phpunit + runs-on: ${{ matrix.php }} name: composer run ${{ matrix.composer-script }} / PHP ${{ matrix.php }} steps: - uses: actions/checkout@v6 From d22077859ca71c1f96db55867479e27c6c2bc9ea Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 24 Jan 2026 16:21:45 +0100 Subject: [PATCH 12/18] CS fix --- src/Exception/ArrayContainsComparatorException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/ArrayContainsComparatorException.php b/src/Exception/ArrayContainsComparatorException.php index 00b5e86..f285cc6 100644 --- a/src/Exception/ArrayContainsComparatorException.php +++ b/src/Exception/ArrayContainsComparatorException.php @@ -15,7 +15,7 @@ class ArrayContainsComparatorException extends AssertionFailedException { public function __construct(string $message, int $code = 0, ?Throwable $previous = null, mixed $needle = null, mixed $haystack = null) { - $message .= "\n\n" . sprintf( + $message .= "\n\n".sprintf( << Date: Thu, 29 Jan 2026 09:31:58 +0100 Subject: [PATCH 13/18] build(ci): specify correct matrix variable for the runner --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ac605a..ff4a6be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - os: windows-latest php: "8.5" composer-script: phpunit - runs-on: ${{ matrix.php }} + runs-on: ${{ matrix.os }} name: composer run ${{ matrix.composer-script }} / PHP ${{ matrix.php }} steps: - uses: actions/checkout@v6 From 5561362824bf56f2005c3c15ff18977f44e19988 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 29 Jan 2026 09:49:31 +0100 Subject: [PATCH 14/18] Use bash shell on all workflows Allows us to use bash instead of powershell on all runners --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff4a6be..ce4f3df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: php-version: ${{ matrix.php }} - id: composer-cache-dir run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + shell: bash - id: composer-cache uses: actions/cache@v5 with: @@ -42,6 +43,7 @@ jobs: - run: composer install - if: ${{ matrix.composer-script == 'behat' }} run: php -S localhost:8080 -t ./features/bootstrap > httpd.log 2>&1 & + shell: bash - run: composer run ${{ matrix.composer-script }} env: PHP_CS_FIXER_IGNORE_ENV: 1 From 2d2d6dfef03b4b72636570c3c5b758b279a69c3f Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 29 Jan 2026 10:28:11 +0100 Subject: [PATCH 15/18] Add ext-fileinfo requirement to composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 0c1f291..760339c 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "require": { "php": ">=8.3", "ext-json": "*", + "ext-fileinfo": "*", "beberlei/assert": "^3", "behat/behat": "^3", "firebase/php-jwt": "^6", From 5d3d9ae0341983bbaeaf77889e8538172519dcba Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 29 Jan 2026 10:29:24 +0100 Subject: [PATCH 16/18] Add PHP extensions in CI workflow Added json and fileinfo extensions for PHP setup. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce4f3df..830c2be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + extensions: json, fileinfo - id: composer-cache-dir run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT shell: bash From 9a99f7317a68bf5d1b6a63c650d9415cb3c6528d Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 29 Jan 2026 23:09:18 +0100 Subject: [PATCH 17/18] Force LF EOL everywhere --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 2ccac70..49e6ab8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +* text eol=lf .github export-ignore docs export-ignore features export-ignore From ea2f6920823ac73b3dfc24d8633ec3ed0f359d3a Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 29 Jan 2026 23:17:51 +0100 Subject: [PATCH 18/18] Require and add sockets extension --- .github/workflows/ci.yml | 2 +- composer.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 830c2be..d80a6f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: json, fileinfo + extensions: json, fileinfo, sockets - id: composer-cache-dir run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT shell: bash diff --git a/composer.json b/composer.json index 760339c..9557964 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "guzzlehttp/guzzle": "^7.7" }, "require-dev": { + "ext-sockets": "*", "friendsofphp/php-cs-fixer": "^3.92", "imbo/imbo-coding-standard": "^3.0", "phpstan/extension-installer": "^1.4",