From fd1b61cadd9596b5e2fa031a201e7acbb85f0680 Mon Sep 17 00:00:00 2001 From: Ettore Del Negro Date: Wed, 22 Apr 2026 21:23:30 +0200 Subject: [PATCH 01/15] Update annotation retrieval to use getGraphQLElement --- src/DependencyInjection/GraphQLiteCompilerPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/GraphQLiteCompilerPass.php b/src/DependencyInjection/GraphQLiteCompilerPass.php index 75246d6..74569a5 100644 --- a/src/DependencyInjection/GraphQLiteCompilerPass.php +++ b/src/DependencyInjection/GraphQLiteCompilerPass.php @@ -337,7 +337,7 @@ private function makePublicInjectedServices(ReflectionClass $refClass, Annotatio $services = $this->getCodeCache()->get($refClass, function() use ($refClass, $reader, $container, $isController): array { $services = []; foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - $field = $reader->getRequestAnnotation($method, Field::class) ?? $reader->getRequestAnnotation($method, Query::class) ?? $reader->getRequestAnnotation($method, Mutation::class); + $field = $reader->getGraphQLElementAnnotation($method, Field::class) ?? $reader->getGraphQLElementAnnotation($method, Query::class) ?? $reader->getGraphQLElementAnnotation($method, Mutation::class); if ($field !== null) { if ($isController) { $services[$refClass->getName()] = $refClass->getName(); From 9e75972fc2a5f3940ca0803d8d074cd4011a414f Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:20:13 +0200 Subject: [PATCH 02/15] :package: Move static analysis into separate job --- .github/workflows/test.yaml | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d69135c..3385c6d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -51,15 +51,44 @@ jobs: run: | vendor/bin/simple-phpunit --no-coverage + static-analysis: + runs-on: ubuntu-latest + name: static-analysis + steps: + - name: checkout + uses: actions/checkout@v6 + + - name: php + uses: shivammathur/setup-php@2.36.0 + with: + php-version: '8.4' + ini-values: zend.assertions=1 + + - name: composer-cache-dir + id: composercache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: composer-cache + uses: actions/cache@v5.0.5 + with: + path: ${{ steps.composercache.outputs.dir }} + key: composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + composer-${{ hashFiles('**/composer.json') }} + composer- + + - name: composer + run: | + composer update --no-interaction --no-progress --prefer-dist + - name: phpstan-cache uses: actions/cache@v5.0.5 with: - key: phpstan-${{ matrix.php-version }}-${{ matrix.install-args }}-${{ github.ref }}-${{ github.sha }} + key: phpstan-${{ github.ref }}-${{ github.sha }} path: .phpstan-cache restore-keys: | - phpstan-${{ matrix.php-version }}-${{ matrix.install-args }}-${{ github.ref }}- - phpstan-${{ matrix.php-version }}-${{ matrix.install-args }}- - phpstan-${{ matrix.php-version }}- + phpstan-${{ github.ref }}- phpstan- - name: phpstan From 9cc5dd8ccfb9b068ca5f60f0f9356d952cb74f49 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:24:47 +0200 Subject: [PATCH 03/15] :package: Introduce CI job to run tests with lowest deps --- .github/workflows/test.yaml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3385c6d..78d800e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,11 +12,26 @@ concurrency: jobs: test: runs-on: ubuntu-latest - name: test + name: test (${{ matrix.name }}) strategy: matrix: - install-args: [''] - php-version: ['8.2', '8.3', '8.4'] + include: + - name: php-8.2 + php-version: '8.2' + install-args: '' + composer-cache-suffix: default + - name: php-8.3 + php-version: '8.3' + install-args: '' + composer-cache-suffix: default + - name: php-8.4 + php-version: '8.4' + install-args: '' + composer-cache-suffix: default + - name: php-8.4-prefer-lowest + php-version: '8.4' + install-args: '--prefer-lowest' + composer-cache-suffix: prefer-lowest fail-fast: false steps: - name: checkout @@ -37,9 +52,9 @@ jobs: uses: actions/cache@v5.0.5 with: path: ${{ steps.composercache.outputs.dir }} - key: composer-${{ hashFiles('**/composer.json') }}-${{ matrix.install-args }} + key: composer-${{ hashFiles('**/composer.json') }}-${{ matrix.composer-cache-suffix }} restore-keys: | - composer-${{ hashFiles('**/composer.json') }}-${{ matrix.install-args }} + composer-${{ hashFiles('**/composer.json') }}-${{ matrix.composer-cache-suffix }} composer-${{ hashFiles('**/composer.json') }}- composer- From dc5e55d6ff8dc9b2311b01de332d61d5b907cb3f Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:25:18 +0200 Subject: [PATCH 04/15] :package: Require minimal 6.4.35 phpunit bridge due to bugs in the 6.4.0 with phpunit 11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e457869..0620945 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", "symfony/yaml": "^6.4 || ^7.0 || ^8.0", "beberlei/porpaginas": "^1.2 || ^2.0", - "symfony/phpunit-bridge": "^6.4 || ^7 || ^8.0", + "symfony/phpunit-bridge": "^6.4.35 || ^7 || ^8.0", "phpstan/phpstan": "^2", "phpstan/phpstan-symfony": "^2.0", "composer/package-versions-deprecated": "^1.8", From 094456940ae8695fe5f2c9edd8bd76be5fb02fc9 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:27:17 +0200 Subject: [PATCH 05/15] :package: Add testing PHP 8.5 on CI --- .github/workflows/test.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 78d800e..7809183 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -28,6 +28,10 @@ jobs: php-version: '8.4' install-args: '' composer-cache-suffix: default + - name: php-8.5 + php-version: '8.5' + install-args: '' + composer-cache-suffix: default - name: php-8.4-prefer-lowest php-version: '8.4' install-args: '--prefer-lowest' @@ -76,7 +80,7 @@ jobs: - name: php uses: shivammathur/setup-php@2.36.0 with: - php-version: '8.4' + php-version: '8.5' ini-values: zend.assertions=1 - name: composer-cache-dir From 3c7128187bed05d8948975324301007781673b78 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:28:27 +0200 Subject: [PATCH 06/15] :package: Use PHP 8.5 for prefer lowest CI --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7809183..096fb35 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,8 +32,8 @@ jobs: php-version: '8.5' install-args: '' composer-cache-suffix: default - - name: php-8.4-prefer-lowest - php-version: '8.4' + - name: php-8.5-prefer-lowest + php-version: '8.5' install-args: '--prefer-lowest' composer-cache-suffix: prefer-lowest fail-fast: false From 5f70049a25af8444fab4b7fc59d3538c907aa68e Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:57:39 +0200 Subject: [PATCH 07/15] :package: Avoid legacy array declaration form --- tests/GraphQLiteTestingKernel.php | 68 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/tests/GraphQLiteTestingKernel.php b/tests/GraphQLiteTestingKernel.php index 16e358c..bc2286f 100644 --- a/tests/GraphQLiteTestingKernel.php +++ b/tests/GraphQLiteTestingKernel.php @@ -100,9 +100,9 @@ public function registerBundles(): iterable public function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $loader->load(function(ContainerBuilder $container) { - $frameworkConf = array( + $frameworkConf = [ 'secret' => 'S0ME_SECRET' - ); + ]; $frameworkConf['cache'] =[ 'app' => 'cache.adapter.array', @@ -126,46 +126,52 @@ public function configureContainer(ContainerBuilder $c, LoaderInterface $loader) $extraConfig['enable_authenticator_manager'] = true; } - $container->loadFromExtension('security', array_merge(array( - 'providers' => [ - 'in_memory' => [ - 'memory' => [ - 'users' => [ - 'foo' => [ - 'password' => 'bar', - 'roles' => 'ROLE_USER', + $container->loadFromExtension( + 'security', + array_merge( + [ + 'providers' => [ + 'in_memory' => [ + 'memory' => [ + 'users' => [ + 'foo' => [ + 'password' => 'bar', + 'roles' => 'ROLE_USER', + ], + ], ], - ], - ], - ], - 'in_memory_other' => [ - 'memory' => [ - 'users' => [ - 'foo' => [ - 'password' => 'bar', - 'roles' => 'ROLE_USER', + ], + 'in_memory_other' => [ + 'memory' => [ + 'users' => [ + 'foo' => [ + 'password' => 'bar', + 'roles' => 'ROLE_USER', + ], + ], ], ], ], + 'firewalls' => [ + 'main' => [ + 'provider' => 'in_memory', + ], + ], + 'password_hashers' => [ + InMemoryUser::class => 'plaintext', + ], ], - ], - 'firewalls' => [ - 'main' => [ - 'provider' => 'in_memory' - ] - ], - 'password_hashers' => [ - InMemoryUser::class => 'plaintext', - ], - ), $extraConfig)); + $extraConfig, + ), + ); } - $graphqliteConf = array( + $graphqliteConf = [ 'namespace' => [ 'controllers' => $this->controllersNamespace, 'types' => $this->typesNamespace ], - ); + ]; if ($this->enableLogin) { $graphqliteConf['security']['enable_login'] = $this->enableLogin; From 8476e78fe629c7168137d5fa6f8645b77622a098 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 08:59:53 +0200 Subject: [PATCH 08/15] :package: Explicitly configure validation.email_validation_mode to cover symfony/validator v7 + symfony/framework-bundle v6.4 --- tests/GraphQLiteTestingKernel.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/GraphQLiteTestingKernel.php b/tests/GraphQLiteTestingKernel.php index bc2286f..def5347 100644 --- a/tests/GraphQLiteTestingKernel.php +++ b/tests/GraphQLiteTestingKernel.php @@ -101,7 +101,9 @@ public function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $loader->load(function(ContainerBuilder $container) { $frameworkConf = [ - 'secret' => 'S0ME_SECRET' + 'secret' => 'S0ME_SECRET', + // forward compatibility with symfony/validator v7 + 'validation' => ['email_validation_mode' => 'html5'], ]; $frameworkConf['cache'] =[ From d16a09dc64eb3f3fb2ed8122a86f182fb7380998 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:00:10 +0200 Subject: [PATCH 09/15] :package: Make test more explicit about expectations --- tests/FunctionalTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index b5ecc32..cdfe673 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -481,7 +481,10 @@ public function testValidation(): void $response = $kernel->handle($request); + $this->assertSame(400, $response->getStatusCode()); $result = json_decode($response->getContent(), true); + $this->assertIsArray($result); + $this->assertArrayHasKey('errors', $result); $errors = $result['errors']; $this->assertSame('This value is not a valid email address.', $errors[0]['message']); From 916297c71d5f1cb5ca7f3dc7a15beba86c309d0d Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:01:12 +0200 Subject: [PATCH 10/15] :package: Make test more stable Avoid fail because of the changed field order --- tests/Command/DumpSchemaCommandTest.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/Command/DumpSchemaCommandTest.php b/tests/Command/DumpSchemaCommandTest.php index e99fc48..17cb934 100644 --- a/tests/Command/DumpSchemaCommandTest.php +++ b/tests/Command/DumpSchemaCommandTest.php @@ -18,9 +18,20 @@ public function testExecute(): void $commandTester = new CommandTester($command); $commandTester->execute([]); + preg_match('/type Product \{(?P[\s\S]*?)}/', $commandTester->getDisplay(), $matches); + self::assertArrayHasKey('body', $matches); + + self::assertMatchesRegularExpression( + '/name: String!/', + $matches['body'] + ); + self::assertMatchesRegularExpression( + '/price: Float!/', + $matches['body'] + ); self::assertMatchesRegularExpression( - '/type Product {[\s"]*name: String!\s*price: Float!\s*seller: Contact\s*}/', - $commandTester->getDisplay() + '/seller: Contact/', + $matches['body'] ); } } From 2d02d4b67aa0e24ea9296f5d6c62a4907d9e96a0 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:01:50 +0200 Subject: [PATCH 11/15] :package: Min GraphQLite 8.1 Bug with webonyx for prefer lowest deps installation --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0620945..b800121 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require" : { "php" : ">=8.1", "ext-json": "*", - "thecodingmachine/graphqlite" : "^8", + "thecodingmachine/graphqlite" : "^8.1", "thecodingmachine/graphqlite-symfony-validator-bridge": "^7.1.1", "symfony/config": "^6.4 || ^7.0 || ^8.0", "symfony/console": "^6.4 || ^7.0 || ^8.0", From 0c3a66b5a4bed0ff91f57c5677494665986783a7 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:13:41 +0200 Subject: [PATCH 12/15] :fire: Drop obsolete conflict rules --- composer.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/composer.json b/composer.json index b800121..80fbe4d 100644 --- a/composer.json +++ b/composer.json @@ -41,12 +41,6 @@ "composer/semver": "^3.4", "overblog/graphiql-bundle": "^0.2 || ^0.3 || ^1" }, - "conflict": { - "symfony/event-dispatcher": "<4.3", - "symfony/security-core": "<4.3", - "symfony/routing": "<4.3", - "phpdocumentor/type-resolver": "<1.4" - }, "scripts": { "phpstan": "phpstan analyse -c phpstan.neon --no-progress" }, From d460637322f3ea12711e3b0b3d4837b3289dd61f Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:14:37 +0200 Subject: [PATCH 13/15] :package: Conflict with symfony/password-hasher version <6.4 ``` Error: Call to undefined method Symfony\Component\Security\Core\User\InMemoryUser::getSalt() /home/runner/work/graphqlite-bundle/graphqlite-bundle/vendor/symfony/password-hasher/Hasher/UserPasswordHasher.php:80 ``` --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 80fbe4d..5af612a 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,9 @@ "composer/semver": "^3.4", "overblog/graphiql-bundle": "^0.2 || ^0.3 || ^1" }, + "conflict": { + "symfony/password-hasher": "<6.4" + }, "scripts": { "phpstan": "phpstan analyse -c phpstan.neon --no-progress" }, From e65db59c2fd784ba35d7a3812b50a97a5b0349af Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:32:01 +0200 Subject: [PATCH 14/15] :package: Mark cache directories as optional for phpstan --- phpstan.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 544bd75..dfc0b56 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,8 +9,8 @@ parameters: - src excludePaths: - vendor - - cache - - .phpstan-cache + - cache (?) + - .phpstan-cache (?) - tests polluteScopeWithLoopInitialAssignments: false From ebf769f86d37953f1c645788bff0c3df7b12ba23 Mon Sep 17 00:00:00 2001 From: Andrii Dembitskyi Date: Mon, 27 Apr 2026 09:42:36 +0200 Subject: [PATCH 15/15] :package: [#282] Be compatible with graphqlite v8.1 also --- .../GraphQLiteCompilerPass.php | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/DependencyInjection/GraphQLiteCompilerPass.php b/src/DependencyInjection/GraphQLiteCompilerPass.php index 74569a5..c254004 100644 --- a/src/DependencyInjection/GraphQLiteCompilerPass.php +++ b/src/DependencyInjection/GraphQLiteCompilerPass.php @@ -337,13 +337,16 @@ private function makePublicInjectedServices(ReflectionClass $refClass, Annotatio $services = $this->getCodeCache()->get($refClass, function() use ($refClass, $reader, $container, $isController): array { $services = []; foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - $field = $reader->getGraphQLElementAnnotation($method, Field::class) ?? $reader->getGraphQLElementAnnotation($method, Query::class) ?? $reader->getGraphQLElementAnnotation($method, Mutation::class); - if ($field !== null) { + $resolveResult = $this->resolveFieldGraphqlElement($reader, $method); + if ($resolveResult['exists']) { if ($isController) { $services[$refClass->getName()] = $refClass->getName(); } + $services += $this->getListOfInjectedServices($method, $container); - if ($field instanceof Field && $field->getPrefetchMethod() !== null) { + + $field = $resolveResult['field']; + if (null !== $field && $field->getPrefetchMethod() !== null) { $services += $this->getListOfInjectedServices($refClass->getMethod($field->getPrefetchMethod()), $container); } } @@ -467,4 +470,29 @@ private function getClassList(string $namespace): Generator } } + /** + * @return array{exists: bool, field: Field|null} + */ + private function resolveFieldGraphqlElement(AnnotationReader $reader, ReflectionMethod $method): array + { + // backward compatibility with graphqlite v8.1 + // @phpstan-ignore function.alreadyNarrowedType + if (false === \method_exists($reader, 'getGraphQLElementAnnotation')) { + // graphqlite versions in this BC branch expose getRequestAnnotation instead. + // @phpstan-ignore method.notFound + $element = $reader->getRequestAnnotation($method, Field::class) + // @phpstan-ignore method.notFound + ?? $reader->getRequestAnnotation($method, Query::class) + // @phpstan-ignore method.notFound + ?? $reader->getRequestAnnotation($method, Mutation::class); + + return ['exists' => null !== $element, 'field' => ($element instanceof Field ? $element : null)]; + } + + $element = $reader->getGraphQLElementAnnotation($method, Field::class) + ?? $reader->getGraphQLElementAnnotation($method, Query::class) + ?? $reader->getGraphQLElementAnnotation($method, Mutation::class); + + return ['exists' => null !== $element, 'field' => ($element instanceof Field ? $element : null)]; + } }