From 42e6856678ad68a2dbd75dc1cd7a22265273edbe Mon Sep 17 00:00:00 2001 From: John Okoroafor Date: Fri, 6 Feb 2026 20:30:35 +0100 Subject: [PATCH] =?UTF-8?q?Add=20introspection=20config=20pass=E2=80=91tho?= =?UTF-8?q?ugh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++ src/EndpointConfig.php | 12 ++++ src/Introspector.php | 7 +- tests/Unit/IntrospectorTest.php | 112 +++++++++++++++++++++++++++++++- 4 files changed, 129 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a583d67..30e9c296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Add `EndpointConfig::introspectionConfig()` to pass options to `Introspection::getIntrospectionQuery()`, defaulting to `includeDeprecated: true` + ## v1.1.2 ### Fixed diff --git a/src/EndpointConfig.php b/src/EndpointConfig.php index 955b9308..6f7fbcc6 100644 --- a/src/EndpointConfig.php +++ b/src/EndpointConfig.php @@ -59,6 +59,18 @@ public function errorsAreClientSafe(): bool return false; } + /** + * Options passed to GraphQL\Type\Introspection::getIntrospectionQuery. + * + * @return array + */ + public function introspectionConfig(): array + { + return [ + 'includeDeprecated' => true, + ]; + } + /** * Return a map from type names to a TypeConfig describing how to deal with them. * diff --git a/src/Introspector.php b/src/Introspector.php index 6547bfef..063c23a4 100644 --- a/src/Introspector.php +++ b/src/Introspector.php @@ -49,10 +49,11 @@ public function introspect(): void protected function fetchIntrospectionResult(Client $client, bool $directiveIsRepeatable): Response { + $options = $this->endpointConfig->introspectionConfig(); + $options['directiveIsRepeatable'] = $directiveIsRepeatable; + $response = $client->request( - Introspection::getIntrospectionQuery([ - 'directiveIsRepeatable' => $directiveIsRepeatable, - ]) + Introspection::getIntrospectionQuery($options) ); if (isset($response->errors)) { diff --git a/tests/Unit/IntrospectorTest.php b/tests/Unit/IntrospectorTest.php index 45c213e2..6d439415 100644 --- a/tests/Unit/IntrospectorTest.php +++ b/tests/Unit/IntrospectorTest.php @@ -29,6 +29,14 @@ final class IntrospectorTest extends TestCase GRAPHQL; + public const SCHEMA_WITH_DEPRECATED = /* @lang GraphQL */ <<<'GRAPHQL' + type Query { + oldField: String @deprecated(reason: "Use newField") + newField: String + } + + GRAPHQL; + public const PATH = __DIR__ . '/schema.graphql'; /** @@ -55,6 +63,42 @@ public function testFailsIntrospectionIfFallbackAlsoThrows(): void ->introspect(); } + public function testIncludesDeprecationsByDefault(): void + { + $introspector = $this->makeIntrospector(static function (): Response { + return self::introspectionWithDeprecatedMock(true); + }); + + $introspector->introspect(); + + self::assertFileExists(self::PATH); + $schema = file_get_contents(self::PATH); + self::assertStringContainsString('@deprecated', $schema); + self::assertStringContainsString('Use newField', $schema); + + unlink(self::PATH); + } + + public function testCanDisableDeprecations(): void + { + $introspector = $this->makeIntrospector( + static function (): Response { + return self::introspectionWithDeprecatedMock(false); + }, + ['includeDeprecated' => false] + ); + + $introspector->introspect(); + + self::assertFileExists(self::PATH); + $schema = file_get_contents(self::PATH); + self::assertStringNotContainsString('@deprecated', $schema); + self::assertStringNotContainsString('oldField', $schema); + self::assertStringContainsString('newField', $schema); + + unlink(self::PATH); + } + /** @return iterable */ public static function validRequests(): iterable { @@ -88,15 +132,19 @@ static function (): Response { } /** @param Request $request */ - private function makeIntrospector(callable $request): Introspector + private function makeIntrospector(callable $request, ?array $introspectionConfig = null): Introspector { - $endpointConfig = new class($request) extends EndpointConfig { + $endpointConfig = new class($request, $introspectionConfig) extends EndpointConfig { /** @var callable */ private $request; - public function __construct(callable $request) + /** @var array|null */ + private ?array $introspectionConfig; + + public function __construct(callable $request, ?array $introspectionConfig) { $this->request = $request; + $this->introspectionConfig = $introspectionConfig; } public function makeClient(): Client @@ -123,6 +171,11 @@ public function finder(): Finder { return new DirectoryFinder('bar'); } + + public function introspectionConfig(): array + { + return $this->introspectionConfig ?? parent::introspectionConfig(); + } }; return new Introspector($endpointConfig, 'foo', 'bar'); @@ -140,6 +193,59 @@ public static function successfulIntrospectionMock(): Response return $response; } + public static function introspectionWithDeprecatedMock(bool $includeDeprecated): Response + { + $schema = BuildSchema::build(self::SCHEMA_WITH_DEPRECATED); + $introspection = Introspection::fromSchema($schema); + if (! $includeDeprecated) { + $introspection = self::stripDeprecations($introspection); + } + + $response = new Response(); + // @phpstan-ignore-next-line We know an associative array converts to a stdClass + $response->data = Json::assocToStdClass($introspection); + + return $response; + } + + /** + * @param array $introspection + * + * @return array + */ + private static function stripDeprecations(array $introspection): array + { + if (! isset($introspection['__schema']['types']) || ! is_array($introspection['__schema']['types'])) { + return $introspection; + } + + foreach ($introspection['__schema']['types'] as &$type) { + if (isset($type['fields']) && is_array($type['fields'])) { + $type['fields'] = array_values(array_filter($type['fields'], static function (array $field): bool { + return empty($field['isDeprecated']); + })); + + foreach ($type['fields'] as &$field) { + if (isset($field['args']) && is_array($field['args'])) { + $field['args'] = array_values(array_filter($field['args'], static function (array $arg): bool { + return empty($arg['isDeprecated']); + })); + } + } + unset($field); + } + + if (isset($type['enumValues']) && is_array($type['enumValues'])) { + $type['enumValues'] = array_values(array_filter($type['enumValues'], static function (array $enumValue): bool { + return empty($enumValue['isDeprecated']); + })); + } + } + unset($type); + + return $introspection; + } + private static function responseWithErrorsMock(): Response { $response = new Response();