From 7a75a73eb29ee3837cb935c3f05f68cfb37f1fba Mon Sep 17 00:00:00 2001 From: Jacob Thomason Date: Wed, 18 Dec 2024 02:26:29 -0500 Subject: [PATCH 01/10] Fixed Adapter class for compatibility with base ObjectType (#721) --- src/Mappers/Proxys/MutableAdapterTrait.php | 4 ++-- src/Mappers/Proxys/MutableObjectTypeAdapter.php | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Mappers/Proxys/MutableAdapterTrait.php b/src/Mappers/Proxys/MutableAdapterTrait.php index f4f2f8b3ad..5c780b2471 100644 --- a/src/Mappers/Proxys/MutableAdapterTrait.php +++ b/src/Mappers/Proxys/MutableAdapterTrait.php @@ -39,13 +39,13 @@ public function assertValid(): void $this->type->assertValid(); } - public function jsonSerialize():string + public function jsonSerialize(): string { return $this->type->jsonSerialize(); } - public function toString():string + public function toString(): string { return $this->type->toString(); } diff --git a/src/Mappers/Proxys/MutableObjectTypeAdapter.php b/src/Mappers/Proxys/MutableObjectTypeAdapter.php index cae384e1e0..b4256631a8 100644 --- a/src/Mappers/Proxys/MutableObjectTypeAdapter.php +++ b/src/Mappers/Proxys/MutableObjectTypeAdapter.php @@ -3,23 +3,11 @@ namespace TheCodingMachine\GraphQLite\Mappers\Proxys; -use Exception; -use GraphQL\Error\InvariantViolation; -use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; -use GraphQL\Utils\Utils; -use RuntimeException; -use TheCodingMachine\GraphQLite\Types\MutableInterface; use TheCodingMachine\GraphQLite\Types\MutableObjectType; -use TheCodingMachine\GraphQLite\Types\NoFieldsException; use function assert; -use function call_user_func; -use function is_array; -use function is_callable; -use function is_string; -use function sprintf; /** * An adapter class (actually a proxy) that adds the "mutable" feature to any Webonyx ObjectType. @@ -36,6 +24,7 @@ public function __construct(ObjectType $type, ?string $className = null) $this->type = $type; $this->className = $className; $this->name = $type->name; + $this->description = $type->description; $this->config = $type->config; $this->astNode = $type->astNode; $this->extensionASTNodes = $type->extensionASTNodes; From 7b7ae1623a54395835a5e0e473bd324acb2e8d24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:44:52 -0500 Subject: [PATCH 02/10] Bump actions/upload-artifact from 3 to 4 (#645) * Bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update continuous_integration.yml * Update continuous_integration.yml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Thomason --- .github/workflows/continuous_integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 15e2b3ca31..f975c8b9af 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -78,9 +78,9 @@ jobs: run: "composer cs-check" - name: "Archive code coverage results" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "codeCoverage" + name: codeCoverage-${{ matrix.php-version }} path: "build" overwrite: true From fbb20d267a7fd832f0984a64c012a6a080e17a11 Mon Sep 17 00:00:00 2001 From: Jacob Thomason Date: Wed, 18 Dec 2024 07:50:08 -0500 Subject: [PATCH 03/10] PHP 8.4 Support (#722) * PHP 8.4 Support Add PHP 8.4 to list of CI runners and confirm PHP 8.4 support * Updated dependencies and resolved PHPStan errors * Downgrade symfony/var-dumper for PHP 8.1 support * Support phpunit 10.5 for PHP 8.1 * Corrected call for enum interface inconsistency * Resolved PHPCS errors --- .github/workflows/continuous_integration.yml | 2 +- composer.json | 17 +++++----- phpstan.neon | 7 ++-- src/Annotations/Security.php | 6 ---- src/Exceptions/GraphQLAggregateException.php | 8 ++++- src/FactoryContext.php | 2 +- src/FieldsBuilder.php | 12 +++---- src/Http/Psr15GraphQLMiddlewareBuilder.php | 1 + src/Http/WebonyxGraphqlMiddleware.php | 22 ++++--------- src/InputTypeUtils.php | 1 - src/Mappers/Parameters/TypeHandler.php | 23 ++++--------- .../Proxys/MutableInterfaceTypeAdapter.php | 17 ++-------- .../Proxys/MutableObjectTypeAdapter.php | 2 +- src/Mappers/RecursiveTypeMapper.php | 3 +- src/Mappers/Root/EnumTypeMapper.php | 10 +++--- src/Mappers/Root/IteratorTypeMapper.php | 2 +- .../StaticClassListTypeMapperFactory.php | 2 +- src/Middlewares/SecurityFieldMiddleware.php | 32 ++++++------------- .../SecurityInputFieldMiddleware.php | 3 -- src/Schema.php | 6 +--- src/Types/EnumType.php | 2 +- src/Types/ID.php | 3 +- src/Types/TypeAnnotatedObjectType.php | 1 - tests/Integration/EndToEndTest.php | 1 - 24 files changed, 64 insertions(+), 121 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index f975c8b9af..e7d5c579ba 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: install-args: ['', '--prefer-lowest'] - php-version: ['8.1', '8.2', '8.3'] + php-version: ['8.1', '8.2', '8.3', '8.4'] fail-fast: false steps: diff --git a/composer.json b/composer.json index d4ef009482..33751a0af7 100644 --- a/composer.json +++ b/composer.json @@ -27,17 +27,16 @@ "kcs/class-finder": "^0.6.0" }, "require-dev": { - "beberlei/porpaginas": "^1.2 || ^2.0", - "doctrine/coding-standard": "^11.0 || ^12.0", + "beberlei/porpaginas": "^2.0", + "doctrine/coding-standard": "^12.0", "ecodev/graphql-upload": "^7.0", - "laminas/laminas-diactoros": "^2 || ^3", + "laminas/laminas-diactoros": "^3.5", "myclabs/php-enum": "^1.6.6", - "php-coveralls/php-coveralls": "^2.1", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^10.1 || ^11.0", - "symfony/var-dumper": "^5.4 || ^6.0 || ^7", - "thecodingmachine/phpstan-strict-rules": "^1.0" + "php-coveralls/php-coveralls": "^2.7", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5 || ^11.0", + "symfony/var-dumper": "^6.4" }, "suggest": { "beberlei/porpaginas": "If you want automatic pagination in your GraphQL types", diff --git a/phpstan.neon b/phpstan.neon index 86c9770f50..11a355ed6b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,12 +4,7 @@ parameters: tmpDir: .phpstan-cache paths: - src - excludePaths: - # TODO: exlude only for PHP < 8.1 - - src/Mappers/Root/EnumTypeMapper.php - - src/Types/EnumType.php level: 8 - checkGenericClassInNonGenericObjectType: false reportUnmatchedIgnoredErrors: false treatPhpDocTypesAsCertain: false ignoreErrors: @@ -42,3 +37,5 @@ parameters: - message: '#Call to an undefined method object::__toString\(\)#' path : src/Types/ID.php + - + identifier: missingType.generics diff --git a/src/Annotations/Security.php b/src/Annotations/Security.php index c41c396f60..6e334e3e45 100644 --- a/src/Annotations/Security.php +++ b/src/Annotations/Security.php @@ -6,13 +6,9 @@ use Attribute; use BadMethodCallException; -use TypeError; use function array_key_exists; -use function gettype; -use function is_array; use function is_string; -use function sprintf; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class Security implements MiddlewareAnnotationInterface @@ -37,8 +33,6 @@ public function __construct(array|string $data = [], string|null $expression = n { if (is_string($data)) { $data = ['expression' => $data]; - } elseif (! is_array($data)) { - throw new TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, gettype($data))); } $this->expression = $data['value'] ?? $data['expression'] ?? $expression; diff --git a/src/Exceptions/GraphQLAggregateException.php b/src/Exceptions/GraphQLAggregateException.php index d63ca3a0c3..5c7c1909b8 100644 --- a/src/Exceptions/GraphQLAggregateException.php +++ b/src/Exceptions/GraphQLAggregateException.php @@ -6,6 +6,7 @@ use Exception; use GraphQL\Error\ClientAware; +use RuntimeException; use Throwable; use function array_map; @@ -22,7 +23,7 @@ class GraphQLAggregateException extends Exception implements GraphQLAggregateExc /** @param (ClientAware&Throwable)[] $exceptions */ public function __construct(iterable $exceptions = []) { - parent::__construct('Many exceptions have be thrown:'); + parent::__construct('Many exceptions have been thrown:'); foreach ($exceptions as $exception) { $this->add($exception); @@ -56,6 +57,11 @@ private function updateCode(): void $codes = array_map(static function (Throwable $t) { return $t->getCode(); }, $this->exceptions); + + if (count($codes) === 0) { + throw new RuntimeException('Unable to determine code for exception'); + } + $this->code = max($codes); } diff --git a/src/FactoryContext.php b/src/FactoryContext.php index 0aaf40ac42..672d90ca99 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -103,7 +103,7 @@ public function getClassFinderComputedCache(): ClassFinderComputedCache return $this->classFinderComputedCache; } - public function getClassBoundCache(): ClassBoundCache|null + public function getClassBoundCache(): ClassBoundCache { return $this->classBoundCache; } diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php index d8ab99cc6b..10b0c620ae 100644 --- a/src/FieldsBuilder.php +++ b/src/FieldsBuilder.php @@ -497,6 +497,7 @@ private function getFieldsByMethodAnnotations( $resolver = is_string($controller) ? new SourceMethodResolver($refMethod) + /** @phpstan-ignore argument.type */ : new ServiceResolver([$controller, $methodName]); $fieldDescriptor = new QueryFieldDescriptor( @@ -512,7 +513,7 @@ private function getFieldsByMethodAnnotations( ); $field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface { - public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null + public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition { return QueryField::fromFieldDescriptor($fieldDescriptor); } @@ -605,7 +606,7 @@ private function getFieldsByPropertyAnnotations( ); $field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface { - public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null + public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition { return QueryField::fromFieldDescriptor($fieldDescriptor); } @@ -744,7 +745,7 @@ private function getQueryFieldsFromSourceFields( ->withMiddlewareAnnotations($sourceField->getMiddlewareAnnotations()); $field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface { - public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null + public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition { return QueryField::fromFieldDescriptor($fieldDescriptor); } @@ -822,7 +823,6 @@ private function resolvePhpType( $context = $this->docBlockFactory->createContext($refClass); $phpdocType = $typeResolver->resolve($phpTypeStr, $context); - assert($phpdocType !== null); $fakeDocBlock = new DocBlock('', null, [new DocBlock\Tags\Return_($phpdocType)], $context); return $this->typeMapper->mapReturnType($refMethod, $fakeDocBlock); @@ -1080,7 +1080,7 @@ private function getInputFieldsByMethodAnnotations( ); $field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface { - public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null + public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField { return InputField::fromFieldDescriptor($inputFieldDescriptor); } @@ -1175,7 +1175,7 @@ private function getInputFieldsByPropertyAnnotations( ); $field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface { - public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null + public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField { return InputField::fromFieldDescriptor($inputFieldDescriptor); } diff --git a/src/Http/Psr15GraphQLMiddlewareBuilder.php b/src/Http/Psr15GraphQLMiddlewareBuilder.php index 76bb4d6de8..c36ee03633 100644 --- a/src/Http/Psr15GraphQLMiddlewareBuilder.php +++ b/src/Http/Psr15GraphQLMiddlewareBuilder.php @@ -51,6 +51,7 @@ public function __construct(Schema $schema) $this->config->setSchema($schema); $this->config->setDebugFlag(DebugFlag::RETHROW_UNSAFE_EXCEPTIONS); $this->config->setErrorFormatter([WebonyxErrorHandler::class, 'errorFormatter']); + /** @phpstan-ignore argument.type */ $this->config->setErrorsHandler([WebonyxErrorHandler::class, 'errorHandler']); $this->config->setContext(new Context()); $this->config->setPersistedQueryLoader(new NotSupportedPersistedQueryLoader()); diff --git a/src/Http/WebonyxGraphqlMiddleware.php b/src/Http/WebonyxGraphqlMiddleware.php index ac49cc2939..0bd7fa095e 100644 --- a/src/Http/WebonyxGraphqlMiddleware.php +++ b/src/Http/WebonyxGraphqlMiddleware.php @@ -19,6 +19,7 @@ use TheCodingMachine\GraphQLite\Context\ResetableContextInterface; use function array_map; +use function count; use function explode; use function in_array; use function is_array; @@ -99,11 +100,7 @@ private function processResult(ExecutionResult|array|Promise $result): array }, $result); } - if ($result instanceof Promise) { - throw new RuntimeException('Only SyncPromiseAdapter is supported'); - } - - throw new RuntimeException('Unexpected response from StandardServer::executePsrRequest'); // @codeCoverageIgnore + throw new RuntimeException('Only SyncPromiseAdapter is supported'); } /** @param ExecutionResult|array|Promise $result */ @@ -118,19 +115,14 @@ private function decideHttpCode(ExecutionResult|array|Promise $result): int return $this->httpCodeDecider->decideHttpStatusCode($executionResult); }, $result); - return (int) max($codes); - } + if (count($codes) === 0) { + throw new RuntimeException('Unable to determine HTTP status code'); + } - // @codeCoverageIgnoreStart - // Code unreachable because exceptions will be triggered in processResult first. - // We keep it for defensive programming purpose - if ($result instanceof Promise) { - throw new RuntimeException('Only SyncPromiseAdapter is supported'); + return (int) max($codes); } - throw new RuntimeException('Unexpected response from StandardServer::executePsrRequest'); - - // @codeCoverageIgnoreEnd + throw new RuntimeException('Only SyncPromiseAdapter is supported'); } private function isGraphqlRequest(ServerRequestInterface $request): bool diff --git a/src/InputTypeUtils.php b/src/InputTypeUtils.php index 3e4ad37fbb..319fc7eebf 100644 --- a/src/InputTypeUtils.php +++ b/src/InputTypeUtils.php @@ -73,7 +73,6 @@ private function validateReturnType(ReflectionMethod $refMethod): Fqsen $typeResolver = new TypeResolver(); $phpdocType = $typeResolver->resolve($type); - assert($phpdocType !== null); $phpdocType = $this->resolveSelf($phpdocType, $refMethod->getDeclaringClass()); if (! $phpdocType instanceof Object_) { throw MissingTypeHintRuntimeException::invalidReturnType($refMethod); diff --git a/src/Mappers/Parameters/TypeHandler.php b/src/Mappers/Parameters/TypeHandler.php index b4b8d098ee..393368fa2e 100644 --- a/src/Mappers/Parameters/TypeHandler.php +++ b/src/Mappers/Parameters/TypeHandler.php @@ -53,7 +53,6 @@ use function explode; use function in_array; use function iterator_to_array; -use function method_exists; use function reset; use function trim; @@ -74,7 +73,10 @@ public function __construct( $this->phpDocumentorTypeResolver = new PhpDocumentorTypeResolver(); } - public function mapReturnType(ReflectionMethod $refMethod, DocBlock $docBlockObj): GraphQLType&OutputType + public function mapReturnType( + ReflectionMethod $refMethod, + DocBlock $docBlockObj, + ): GraphQLType&OutputType { $returnType = $refMethod->getReturnType(); if ($returnType !== null) { @@ -94,7 +96,7 @@ public function mapReturnType(ReflectionMethod $refMethod, DocBlock $docBlockObj $refMethod, $docBlockObj, ); - assert($type instanceof GraphQLType && $type instanceof OutputType); + assert(! $type instanceof InputType); } catch (CannotMapTypeExceptionInterface $e) { $e->addReturnInfo($refMethod); throw $e; @@ -318,21 +320,14 @@ public function mapInputProperty( } if ($isNullable === null) { - $isNullable = false; - // getType function on property reflection is available only since PHP 7.4 - if (method_exists($refProperty, 'getType')) { - $refType = $refProperty->getType(); - if ($refType !== null) { - $isNullable = $refType->allowsNull(); - } - } + $isNullable = $refProperty->getType()?->allowsNull() ?? false; } if ($inputTypeName) { $inputType = $this->typeResolver->mapNameToInputType($inputTypeName); } else { $inputType = $this->mapPropertyType($refProperty, $docBlock, true, $argumentName, $isNullable); - assert($inputType instanceof InputType && $inputType instanceof GraphQLType); + assert(! $inputType instanceof OutputType); } $hasDefault = $defaultValue !== null || $isNullable; @@ -452,8 +447,6 @@ private function reflectionTypeToPhpDocType(ReflectionType $type, ReflectionClas assert($type instanceof ReflectionNamedType || $type instanceof ReflectionUnionType); if ($type instanceof ReflectionNamedType) { $phpdocType = $this->phpDocumentorTypeResolver->resolve($type->getName()); - assert($phpdocType !== null); - $phpdocType = $this->resolveSelf($phpdocType, $reflectionClass); if ($type->allowsNull()) { @@ -467,8 +460,6 @@ private function reflectionTypeToPhpDocType(ReflectionType $type, ReflectionClas function ($namedType) use ($reflectionClass): Type { assert($namedType instanceof ReflectionNamedType); $phpdocType = $this->phpDocumentorTypeResolver->resolve($namedType->getName()); - assert($phpdocType !== null); - $phpdocType = $this->resolveSelf($phpdocType, $reflectionClass); if ($namedType->allowsNull()) { diff --git a/src/Mappers/Proxys/MutableInterfaceTypeAdapter.php b/src/Mappers/Proxys/MutableInterfaceTypeAdapter.php index 2f179ab4e1..ffa954aa91 100644 --- a/src/Mappers/Proxys/MutableInterfaceTypeAdapter.php +++ b/src/Mappers/Proxys/MutableInterfaceTypeAdapter.php @@ -3,22 +3,9 @@ namespace TheCodingMachine\GraphQLite\Mappers\Proxys; -use Exception; -use GraphQL\Error\InvariantViolation; -use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InterfaceType; -use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\ResolveInfo; -use GraphQL\Utils\Utils; -use RuntimeException; -use TheCodingMachine\GraphQLite\Types\MutableInterface; +use TheCodingMachine\GraphQLite\Mappers\Proxys\MutableAdapterTrait; use TheCodingMachine\GraphQLite\Types\MutableInterfaceType; -use TheCodingMachine\GraphQLite\Types\NoFieldsException; -use function call_user_func; -use function is_array; -use function is_callable; -use function is_string; -use function sprintf; /** * An adapter class (actually a proxy) that adds the "mutable" feature to any Webonyx ObjectType. @@ -27,7 +14,6 @@ */ final class MutableInterfaceTypeAdapter extends MutableInterfaceType { - /** @use MutableAdapterTrait */ use MutableAdapterTrait; public function __construct(InterfaceType $type, ?string $className = null) @@ -35,6 +21,7 @@ public function __construct(InterfaceType $type, ?string $className = null) $this->type = $type; $this->className = $className; $this->name = $type->name; + $this->description = $type->description; $this->config = $type->config; $this->astNode = $type->astNode; $this->extensionASTNodes = $type->extensionASTNodes; diff --git a/src/Mappers/Proxys/MutableObjectTypeAdapter.php b/src/Mappers/Proxys/MutableObjectTypeAdapter.php index b4256631a8..53ac3e4fc6 100644 --- a/src/Mappers/Proxys/MutableObjectTypeAdapter.php +++ b/src/Mappers/Proxys/MutableObjectTypeAdapter.php @@ -6,6 +6,7 @@ use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; +use TheCodingMachine\GraphQLite\Mappers\Proxys\MutableAdapterTrait; use TheCodingMachine\GraphQLite\Types\MutableObjectType; use function assert; @@ -16,7 +17,6 @@ */ final class MutableObjectTypeAdapter extends MutableObjectType { - /** @use MutableAdapterTrait */ use MutableAdapterTrait; public function __construct(ObjectType $type, ?string $className = null) diff --git a/src/Mappers/RecursiveTypeMapper.php b/src/Mappers/RecursiveTypeMapper.php index f55c341419..12f6d8af43 100644 --- a/src/Mappers/RecursiveTypeMapper.php +++ b/src/Mappers/RecursiveTypeMapper.php @@ -237,7 +237,6 @@ public function findInterfaces(string $className): array { $interfaces = []; - /** @var array> $implements */ $implements = class_implements($className); foreach ($implements as $interface) { if (! $this->typeMapper->canMapClassToType($interface)) { @@ -359,7 +358,7 @@ public function mapClassToInterfaceOrType(string $className, OutputType|null $su $supportedClasses = $this->getClassTree(); if ($objectType instanceof ObjectFromInterfaceType) { $this->interfaces[$cacheKey] = $objectType->getInterfaces()[0]; - } elseif ($objectType instanceof MutableObjectType && isset($supportedClasses[$closestClassName]) && ! empty($supportedClasses[$closestClassName]->getChildren())) { + } elseif (isset($supportedClasses[$closestClassName]) && ! empty($supportedClasses[$closestClassName]->getChildren())) { // Cast as an interface $this->interfaces[$cacheKey] = new InterfaceFromObjectType($this->namingStrategy->getInterfaceNameFromConcreteName($objectType->name), $objectType, $subType, $this); $this->typeRegistry->registerType($this->interfaces[$cacheKey]); diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 64d4696b9c..87e575bb6c 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -17,6 +17,7 @@ use ReflectionMethod; use ReflectionProperty; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Annotations\Type as TypeAnnotation; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; @@ -119,10 +120,11 @@ private function mapByClassName(string $enumClass): EnumType|null $typeName = $typeAnnotation?->getName() ?? $reflectionEnum->getShortName(); // Expose values instead of names if specifically configured to and if enum is string-backed - $useValues = $typeAnnotation !== null && - $typeAnnotation->useEnumValues() && - $reflectionEnum->isBacked() && - (string) $reflectionEnum->getBackingType() === 'string'; + $useValues = $typeAnnotation !== null + && $typeAnnotation instanceof TypeAnnotation + && $typeAnnotation->useEnumValues() + && $reflectionEnum->isBacked() + && (string) $reflectionEnum->getBackingType() === 'string'; $enumDescription = $this->docBlockFactory ->create($reflectionEnum) diff --git a/src/Mappers/Root/IteratorTypeMapper.php b/src/Mappers/Root/IteratorTypeMapper.php index 8d9f9fb1da..767f0489f6 100644 --- a/src/Mappers/Root/IteratorTypeMapper.php +++ b/src/Mappers/Root/IteratorTypeMapper.php @@ -146,7 +146,7 @@ private function toGraphQLType(Compound $type, Closure $topToGraphQLType, bool $ // By convention, we trim the NonNull part of the "$subGraphQlType" if ($subGraphQlType instanceof NonNull) { $subGraphQlType = $subGraphQlType->getWrappedType(); - assert($subGraphQlType instanceof OutputType && $subGraphQlType instanceof GraphQLType); + assert($subGraphQlType instanceof OutputType); } } else { $subGraphQlType = null; diff --git a/src/Mappers/StaticClassListTypeMapperFactory.php b/src/Mappers/StaticClassListTypeMapperFactory.php index 09fca187ae..4e82017f20 100644 --- a/src/Mappers/StaticClassListTypeMapperFactory.php +++ b/src/Mappers/StaticClassListTypeMapperFactory.php @@ -16,7 +16,7 @@ final class StaticClassListTypeMapperFactory implements TypeMapperFactoryInterfa /** * StaticClassListTypeMapperFactory constructor. * - * @param array $classList The list of classes to be scanned. + * @param list $classList The list of classes to be scanned. */ public function __construct( private array $classList, diff --git a/src/Middlewares/SecurityFieldMiddleware.php b/src/Middlewares/SecurityFieldMiddleware.php index d9fabaa0c1..f9d744afa7 100644 --- a/src/Middlewares/SecurityFieldMiddleware.php +++ b/src/Middlewares/SecurityFieldMiddleware.php @@ -19,19 +19,23 @@ use function array_combine; use function array_keys; use function assert; -use function is_array; /** * A field middleware that reads "Security" Symfony annotations. */ class SecurityFieldMiddleware implements FieldMiddlewareInterface { - public function __construct(private readonly ExpressionLanguage $language, private readonly AuthenticationServiceInterface $authenticationService, private readonly AuthorizationServiceInterface $authorizationService/*, ?LoggerInterface $logger = null*/) - { - /*$this->logger = $logger;*/ + public function __construct( + private readonly ExpressionLanguage $language, + private readonly AuthenticationServiceInterface $authenticationService, + private readonly AuthorizationServiceInterface $authorizationService, + ) { } - public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): FieldDefinition|null + public function process( + QueryFieldDescriptor $queryFieldDescriptor, + FieldHandlerInterface $fieldHandler, + ): FieldDefinition|null { $annotations = $queryFieldDescriptor->getMiddlewareAnnotations(); /** @var Security[] $securityAnnotations */ @@ -116,24 +120,6 @@ private function getVariables(array $args, array $parameters, object|null $sourc $argsName = array_keys($parameters); $argsByName = array_combine($argsName, $args); - assert(is_array($argsByName)); - - /*if ($diff = array_intersect(array_keys($variables), array_keys($argsName))) { - foreach ($diff as $key => $variableName) { - if ($variables[$variableName] !== $argsByName[$variableName]) { - continue; - } - - unset($diff[$key]); - } - - if ($diff) { - $singular = count($diff) === 1; - if ($this->logger !== null) { - $this->logger->warning(sprintf('Controller argument%s "%s" collided with the built-in security expression variables. The built-in value%s are being used for the @Security expression.', $singular ? '' : 's', implode('", "', $diff), $singular ? 's' : '')); - } - } - }*/ return $variables + $argsByName; } diff --git a/src/Middlewares/SecurityInputFieldMiddleware.php b/src/Middlewares/SecurityInputFieldMiddleware.php index 9eaba0b626..c2e77c207b 100644 --- a/src/Middlewares/SecurityInputFieldMiddleware.php +++ b/src/Middlewares/SecurityInputFieldMiddleware.php @@ -15,8 +15,6 @@ use function array_combine; use function array_keys; -use function assert; -use function is_array; /** * A field input middleware that reads "Security" Symfony annotations. @@ -85,7 +83,6 @@ private function getVariables(array $args, array $parameters, object|null $sourc $argsName = array_keys($parameters); $argsByName = array_combine($argsName, $args); - assert(is_array($argsByName)); return $variables + $argsByName; } diff --git a/src/Schema.php b/src/Schema.php index 2e53d3c1c7..f90c7fbbff 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -11,8 +11,6 @@ use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; -use function assert; - /** * A GraphQL schema that takes into constructor argument a QueryProvider. * @@ -112,9 +110,7 @@ public function __construct( return $subscription; } - $type = $rootTypeMapper->mapNameToType($name); - assert($type instanceof Type); - return $type; + return $rootTypeMapper->mapNameToType($name); }); $typeResolver->registerSchema($this); diff --git a/src/Types/EnumType.php b/src/Types/EnumType.php index 9e48f98d22..25ff52af43 100644 --- a/src/Types/EnumType.php +++ b/src/Types/EnumType.php @@ -19,7 +19,7 @@ class EnumType extends BaseEnumType { /** * @param class-string $enumName - * @param array $caseDescriptions + * @param array $caseDescriptions * @param array $caseDeprecationReasons */ public function __construct( diff --git a/src/Types/ID.php b/src/Types/ID.php index 950214a4ed..64cf0599c4 100644 --- a/src/Types/ID.php +++ b/src/Types/ID.php @@ -7,7 +7,6 @@ use InvalidArgumentException; use function is_bool; -use function is_object; use function is_scalar; use function method_exists; @@ -21,7 +20,7 @@ class ID */ public function __construct(private readonly bool|float|int|string|object $value) { - if (! is_scalar($value) && (! is_object($value) || ! method_exists($value, '__toString'))) { + if (! is_scalar($value) && ! method_exists($value, '__toString')) { throw new InvalidArgumentException('ID constructor cannot be passed a non scalar value.'); } } diff --git a/src/Types/TypeAnnotatedObjectType.php b/src/Types/TypeAnnotatedObjectType.php index 9954c4daef..0fcf296432 100644 --- a/src/Types/TypeAnnotatedObjectType.php +++ b/src/Types/TypeAnnotatedObjectType.php @@ -61,7 +61,6 @@ public static function createFromAnnotatedClass(string $typeName, string $classN // FIXME: add an interface with a @Type that is implemented by noone. // Check that it does not trigger an exception. - /** @var array> $interfaces */ $interfaces = class_implements($className); foreach ($interfaces as $interface) { if (! $recursiveTypeMapper->canMapClassToType($interface)) { diff --git a/tests/Integration/EndToEndTest.php b/tests/Integration/EndToEndTest.php index 24a055dd1a..2423be089d 100644 --- a/tests/Integration/EndToEndTest.php +++ b/tests/Integration/EndToEndTest.php @@ -29,7 +29,6 @@ use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException; -use TheCodingMachine\GraphQLite\Middlewares\PrefetchFieldMiddleware; use TheCodingMachine\GraphQLite\Schema; use TheCodingMachine\GraphQLite\SchemaFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; From 436e69e3b6c5cb2db181ac1d56ace66fc9876fe6 Mon Sep 17 00:00:00 2001 From: Jacob Thomason Date: Wed, 18 Dec 2024 08:22:03 -0500 Subject: [PATCH 04/10] Fixed incorrect assertions (#723) --- src/Mappers/Parameters/TypeHandler.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Mappers/Parameters/TypeHandler.php b/src/Mappers/Parameters/TypeHandler.php index 393368fa2e..cd1a2d6b5e 100644 --- a/src/Mappers/Parameters/TypeHandler.php +++ b/src/Mappers/Parameters/TypeHandler.php @@ -96,7 +96,7 @@ public function mapReturnType( $refMethod, $docBlockObj, ); - assert(! $type instanceof InputType); + assert($type instanceof OutputType); } catch (CannotMapTypeExceptionInterface $e) { $e->addReturnInfo($refMethod); throw $e; @@ -160,7 +160,12 @@ private function getDocBlockPropertyType(DocBlock $docBlock, ReflectionProperty return reset($varTags)->getType(); } - public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, Type|null $paramTagType, ParameterAnnotations $parameterAnnotations): ParameterInterface + public function mapParameter( + ReflectionParameter $parameter, + DocBlock $docBlock, + Type|null $paramTagType, + ParameterAnnotations $parameterAnnotations, + ): ParameterInterface { $hideParameter = $parameterAnnotations->getAnnotationByType(HideParameter::class); if ($hideParameter) { @@ -327,7 +332,7 @@ public function mapInputProperty( $inputType = $this->typeResolver->mapNameToInputType($inputTypeName); } else { $inputType = $this->mapPropertyType($refProperty, $docBlock, true, $argumentName, $isNullable); - assert(! $inputType instanceof OutputType); + assert($inputType instanceof InputType); } $hasDefault = $defaultValue !== null || $isNullable; From 37c06a9ee307980fd6be0f423d789412eda7c9f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 14:49:15 -0500 Subject: [PATCH 05/10] Bump codecov/codecov-action from 5.1.1 to 5.1.2 (#724) --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index e7d5c579ba..a64e09d68e 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -84,7 +84,7 @@ jobs: path: "build" overwrite: true - - uses: codecov/codecov-action@v5.1.1 # upload the coverage to codecov + - uses: codecov/codecov-action@v5.1.2 # upload the coverage to codecov with: fail_ci_if_error: false # optional (default = false) - Need CODECOV_TOKEN # Do not upload in forks, and only on php8.3, latest deps From 434b7ea39176a27f69c24ab0086db1c9cfa4cda7 Mon Sep 17 00:00:00 2001 From: Jacob Thomason Date: Tue, 11 Mar 2025 22:40:57 -0400 Subject: [PATCH 06/10] Houskeeping (#736) * CS fixes * Resolved additional phpunit issues and deprecations * Allow PHPUnit 10.x * Restore deprecated ReflectionMethod construction for - + + ./tests/ ./tests/Bootstrap.php + + src/ diff --git a/src/Annotations/EnumType.php b/src/Annotations/EnumType.php index 77f624bd6e..e83431fbaf 100644 --- a/src/Annotations/EnumType.php +++ b/src/Annotations/EnumType.php @@ -20,11 +20,8 @@ #[Attribute(Attribute::TARGET_CLASS)] class EnumType { - /** @var string|null */ - private $name; - - /** @var bool */ - private $useValues; + private string|null $name; + private bool $useValues; /** @param mixed[] $attributes */ public function __construct(array $attributes = [], string|null $name = null, bool|null $useValues = null) diff --git a/src/Annotations/ExtendType.php b/src/Annotations/ExtendType.php index 609c141138..55831b23ea 100644 --- a/src/Annotations/ExtendType.php +++ b/src/Annotations/ExtendType.php @@ -19,13 +19,15 @@ class ExtendType { /** @var class-string|null */ - private $class; - /** @var string|null */ - private $name; + private string|null $class; + private string|null $name; /** @param mixed[] $attributes */ - public function __construct(array $attributes = [], string|null $class = null, string|null $name = null) - { + public function __construct( + array $attributes = [], + string|null $class = null, + string|null $name = null, + ) { $className = isset($attributes['class']) ? ltrim($attributes['class'], '\\') : null; $className = $className ?? $class; if ($className !== null && ! class_exists($className) && ! interface_exists($className)) { diff --git a/src/Annotations/Factory.php b/src/Annotations/Factory.php index 9d617e4744..6032d8dddb 100644 --- a/src/Annotations/Factory.php +++ b/src/Annotations/Factory.php @@ -13,10 +13,8 @@ #[Attribute(Attribute::TARGET_METHOD)] class Factory { - /** @var string|null */ - private $name; - /** @var bool */ - private $default; + private string|null $name; + private bool $default; /** @param mixed[] $attributes */ public function __construct(array $attributes = [], string|null $name = null, bool|null $default = null) diff --git a/src/Annotations/FailWith.php b/src/Annotations/FailWith.php index 9e87eb0d80..b647908ae2 100644 --- a/src/Annotations/FailWith.php +++ b/src/Annotations/FailWith.php @@ -15,10 +15,8 @@ class FailWith implements MiddlewareAnnotationInterface { /** * The default value to use if the right is not enforced. - * - * @var mixed */ - private $value; + private mixed $value; /** @throws BadMethodCallException */ public function __construct(mixed $values = [], mixed $value = '__fail__with__magic__key__') diff --git a/src/Annotations/Field.php b/src/Annotations/Field.php index 12712bd6e8..82044c5d29 100644 --- a/src/Annotations/Field.php +++ b/src/Annotations/Field.php @@ -13,28 +13,31 @@ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class Field extends AbstractRequest { - /** @var string|null */ - private $prefetchMethod; + private string|null $prefetchMethod; /** * Input/Output type names for which this fields should be applied to. * * @var string[]|null */ - private $for = null; + private array|null $for = null; - /** @var string|null */ - private $description; - - /** @var string|null */ - private $inputType; + private string|null $description; + private string|null $inputType; /** * @param mixed[] $attributes * @param string|string[] $for */ - public function __construct(array $attributes = [], string|null $name = null, string|null $outputType = null, string|null $prefetchMethod = null, string|array|null $for = null, string|null $description = null, string|null $inputType = null) - { + public function __construct( + array $attributes = [], + string|null $name = null, + string|null $outputType = null, + string|null $prefetchMethod = null, + string|array|null $for = null, + string|null $description = null, + string|null $inputType = null, + ) { parent::__construct($attributes, $name, $outputType); $this->prefetchMethod = $prefetchMethod ?? $attributes['prefetchMethod'] ?? null; diff --git a/src/Annotations/MagicField.php b/src/Annotations/MagicField.php index 650143ff2e..306bddd92c 100644 --- a/src/Annotations/MagicField.php +++ b/src/Annotations/MagicField.php @@ -16,44 +16,56 @@ #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] class MagicField implements SourceFieldInterface { - /** @var string */ - private $name; + private string $name; + private string|null $outputType; + private string|null $phpType; + private string|null $description; + private string|null $sourceName; - /** @var string|null */ - private $outputType; - - /** @var string|null */ - private $phpType; - - /** @var string|null */ - private $description; - - /** @var string|null */ - private $sourceName; - - /** @var MiddlewareAnnotations */ - private $middlewareAnnotations; + private MiddlewareAnnotations $middlewareAnnotations; /** @var array */ - private $parameterAnnotations; + private array $parameterAnnotations; /** * @param mixed[] $attributes * @param array $annotations */ - public function __construct(array $attributes = [], string|null $name = null, string|null $outputType = null, string|null $phpType = null, string|null $description = null, string|null $sourceName = null, array $annotations = []) - { - $this->name = $attributes['name'] ?? $name; + public function __construct( + array $attributes = [], + string|null $name = null, + string|null $outputType = null, + string|null $phpType = null, + string|null $description = null, + string|null $sourceName = null, + array $annotations = [], + ) { + $name = $attributes['name'] ?? $name; + if (! $name) { + throw new BadMethodCallException( + 'The #[MagicField] attribute must be passed a name. For instance: #[MagicField(name: "phone")]', + ); + } + + $this->name = $name; $this->outputType = $attributes['outputType'] ?? $outputType ?? null; $this->phpType = $attributes['phpType'] ?? $phpType ?? null; $this->description = $attributes['description'] ?? $description ?? null; $this->sourceName = $attributes['sourceName'] ?? $sourceName ?? null; - if (! $this->name || (! $this->outputType && ! $this->phpType)) { - throw new BadMethodCallException('The #[MagicField] attribute must be passed a name and an output type or a php type. For instance: "#[MagicField(name: \'phone\', outputType: \'String!\')]" or "#[MagicField(name: \'phone\', phpType: \'string\')]"'); + if (! $this->outputType && ! $this->phpType) { + throw new BadMethodCallException( + "The #[MagicField] attribute must be passed an output type or a php type. + For instance: #[MagicField(name: 'phone', outputType: 'String!')] + or #[MagicField(name: 'phone', phpType: 'string')]", + ); } if (isset($this->outputType) && $this->phpType) { - throw new BadMethodCallException('In a #[MagicField] attribute, you cannot use the outputType and the phpType at the same time. For instance: "#[MagicField(name: \'phone\', outputType: \'String!\')]" or "#[MagicField(name: \'phone\', phpType: \'string\')]"'); + throw new BadMethodCallException( + "In a #[MagicField] attribute, you cannot use the outputType and the phpType at the + same time. For instance: #[MagicField(name: 'phone', outputType: 'String!')] + or #[MagicField(name: 'phone', phpType: 'string')]", + ); } $middlewareAnnotations = []; $parameterAnnotations = []; @@ -67,13 +79,18 @@ public function __construct(array $attributes = [], string|null $name = null, st } elseif ($annotation instanceof ParameterAnnotationInterface) { $parameterAnnotations[$annotation->getTarget()][] = $annotation; } else { - throw new BadMethodCallException('The #[MagicField] attribute\'s "annotations" attribute must be passed an array of annotations implementing either MiddlewareAnnotationInterface or ParameterAnnotationInterface."'); + throw new BadMethodCallException( + "The #[MagicField] attribute's 'annotation' attribute must be passed an array + of annotations implementing either MiddlewareAnnotationInterface or + ParameterAnnotationInterface.", + ); } } $this->middlewareAnnotations = new MiddlewareAnnotations($middlewareAnnotations); - $this->parameterAnnotations = array_map(static function (array $parameterAnnotationsForAttribute): ParameterAnnotations { - return new ParameterAnnotations($parameterAnnotationsForAttribute); - }, $parameterAnnotations); + $this->parameterAnnotations = array_map( + static fn (array $parameterAnnotationsForAttribute): ParameterAnnotations => new ParameterAnnotations($parameterAnnotationsForAttribute), + $parameterAnnotations, + ); } /** diff --git a/src/Annotations/Right.php b/src/Annotations/Right.php index d14597a326..63f5a5b380 100644 --- a/src/Annotations/Right.php +++ b/src/Annotations/Right.php @@ -12,8 +12,7 @@ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class Right implements MiddlewareAnnotationInterface { - /** @var string */ - private $name; + private string $name; /** * @param array|string $name diff --git a/src/Annotations/Security.php b/src/Annotations/Security.php index 6e334e3e45..13db0c93db 100644 --- a/src/Annotations/Security.php +++ b/src/Annotations/Security.php @@ -13,33 +13,35 @@ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class Security implements MiddlewareAnnotationInterface { - /** @var string */ - private $expression; - /** @var mixed */ - private $failWith; - /** @var bool */ - private $failWithIsSet = false; - /** @var int */ - private $statusCode; - /** @var string */ - private $message; + private string $expression; + private mixed $failWith; + private bool $failWithIsSet = false; + private int $statusCode; + private string $message; /** * @param array|string $data data array managed by the Doctrine Annotations library or the expression * * @throws BadMethodCallException */ - public function __construct(array|string $data = [], string|null $expression = null, mixed $failWith = '__fail__with__magic__key__', string|null $message = null, int|null $statusCode = null) - { + public function __construct( + array|string $data = [], + string|null $expression = null, + mixed $failWith = '__fail__with__magic__key__', + string|null $message = null, + int|null $statusCode = null, + ) { if (is_string($data)) { $data = ['expression' => $data]; } - $this->expression = $data['value'] ?? $data['expression'] ?? $expression; - if (! $this->expression) { + $expression = $data['value'] ?? $data['expression'] ?? $expression; + if (! $expression) { throw new BadMethodCallException('The #[Security] attribute must be passed an expression. For instance: "#[Security("is_granted(\'CAN_EDIT_STUFF\')")]"'); } + $this->expression = $expression; + if (array_key_exists('failWith', $data)) { $this->failWith = $data['failWith']; $this->failWithIsSet = true; diff --git a/tests/AnnotationReaderTest.php b/tests/AnnotationReaderTest.php index f7493dead9..36e1a1f220 100644 --- a/tests/AnnotationReaderTest.php +++ b/tests/AnnotationReaderTest.php @@ -9,15 +9,12 @@ use ReflectionMethod; use TheCodingMachine\GraphQLite\Annotations\Autowire; use TheCodingMachine\GraphQLite\Annotations\Exceptions\ClassNotFoundException; -use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; use TheCodingMachine\GraphQLite\Annotations\Field; use TheCodingMachine\GraphQLite\Annotations\Security; use TheCodingMachine\GraphQLite\Annotations\Type; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidClassAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidExtendTypeAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidTypeAnnotation; -use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithTargetMethodParameterAnnotation; -use TheCodingMachine\GraphQLite\Fixtures\Annotations\TargetMethodParameterAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Attributes\TestType; class AnnotationReaderTest extends TestCase @@ -26,7 +23,10 @@ public function testBadAnnotation(): void { $annotationReader = new AnnotationReader(); - $type = $annotationReader->getTypeAnnotation(new ReflectionClass(ClassWithInvalidClassAnnotation::class)); + $type = $annotationReader->getTypeAnnotation( + new ReflectionClass(ClassWithInvalidClassAnnotation::class), + ); + $this->assertNull($type); } @@ -34,14 +34,20 @@ public function testSmellyAnnotation(): void { $annotationReader = new AnnotationReader(); - $this->assertNull($annotationReader->getTypeAnnotation(new ReflectionClass(ClassWithInvalidTypeAnnotation::class))); + $this->assertNull($annotationReader->getTypeAnnotation( + new ReflectionClass(ClassWithInvalidTypeAnnotation::class)), + ); } public function testGetAnnotationsWithBadAnnotation(): void { $annotationReader = new AnnotationReader(); - $types = $annotationReader->getClassAnnotations(new ReflectionClass(ClassWithInvalidClassAnnotation::class), Type::class); + $types = $annotationReader->getClassAnnotations( + new ReflectionClass(ClassWithInvalidClassAnnotation::class), + Type::class, + ); + $this->assertSame([], $types); } @@ -49,7 +55,10 @@ public function testMethodWithBadAnnotation(): void { $annotationReader = new AnnotationReader(); - $type = $annotationReader->getRequestAnnotation(new ReflectionMethod(ClassWithInvalidClassAnnotation::class, 'testMethod'), Field::class); + $type = $annotationReader->getRequestAnnotation( + new ReflectionMethod(ClassWithInvalidClassAnnotation::class, 'testMethod'), + Field::class, + ); $this->assertNull($type); } @@ -59,14 +68,20 @@ public function testExtendAnnotationException(): void $this->expectException(ClassNotFoundException::class); $this->expectExceptionMessage("Could not autoload class 'foo' defined in #[ExtendType] attribute of class 'TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidExtendTypeAnnotation'"); - $annotationReader->getExtendTypeAnnotation(new ReflectionClass(ClassWithInvalidExtendTypeAnnotation::class)); + $annotationReader->getExtendTypeAnnotation( + new ReflectionClass(ClassWithInvalidExtendTypeAnnotation::class), + ); } public function testMethodsWithBadAnnotation(): void { $annotationReader = new AnnotationReader(); - $type = $annotationReader->getMethodAnnotations(new ReflectionMethod(ClassWithInvalidClassAnnotation::class, 'testMethod'), Field::class); + $type = $annotationReader->getMethodAnnotations( + new ReflectionMethod(ClassWithInvalidClassAnnotation::class, 'testMethod'), + Field::class, + ); + $this->assertSame([], $type); } @@ -102,7 +117,11 @@ public function testPhp8AttributeMethodAnnotation(): void { $annotationReader = new AnnotationReader(); - $type = $annotationReader->getRequestAnnotation(new ReflectionMethod(TestType::class, 'getField'), Field::class); + $type = $annotationReader->getRequestAnnotation( + new ReflectionMethod(TestType::class, 'getField'), + Field::class, + ); + $this->assertInstanceOf(Field::class, $type); } @@ -110,7 +129,9 @@ public function testPhp8AttributeMethodAnnotations(): void { $annotationReader = new AnnotationReader(); - $middlewareAnnotations = $annotationReader->getMiddlewareAnnotations(new ReflectionMethod(TestType::class, 'getField')); + $middlewareAnnotations = $annotationReader->getMiddlewareAnnotations( + new ReflectionMethod(TestType::class, 'getField'), + ); /** @var Security[] $securitys */ $securitys = $middlewareAnnotations->getAnnotationsByType(Security::class); @@ -124,34 +145,14 @@ public function testPhp8AttributeParameterAnnotations(): void { $annotationReader = new AnnotationReader(); - $parameterAnnotations = $annotationReader->getParameterAnnotationsPerParameter((new ReflectionMethod(self::class, 'method1'))->getParameters()); - - $this->assertInstanceOf(Autowire::class, $parameterAnnotations['dao']->getAnnotationByType(Autowire::class)); - } - - /** - * This functionality can be dropped with next major release (8.0) with added explicit deprecations before release. - */ - public function testPhp8AttributeParameterAnnotationsForTargetMethod(): void - { - $annotationReader = new AnnotationReader(); - - $parameterAnnotations = $annotationReader->getParameterAnnotationsPerParameter((new ReflectionMethod(ClassWithTargetMethodParameterAnnotation::class, 'method'))->getParameters()); - - $this->assertInstanceOf(TargetMethodParameterAnnotation::class, $parameterAnnotations['bar']->getAnnotationByType(TargetMethodParameterAnnotation::class)); - } - - /** - * This functionality can be dropped with next major release (8.0) with added explicit deprecations before release. - */ - public function testPhp8AttributeParameterAnnotationsForTargetMethodWithInvalidTargetParameter(): void - { - $annotationReader = new AnnotationReader(); - - $this->expectException(InvalidParameterException::class); - $this->expectExceptionMessage('Parameter "unexistent" declared in annotation "TheCodingMachine\GraphQLite\Fixtures\Annotations\TargetMethodParameterAnnotation" of method "TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithTargetMethodParameterAnnotation::methodWithInvalidAnnotation()" does not exist.'); + $parameterAnnotations = $annotationReader->getParameterAnnotationsPerParameter( + (new ReflectionMethod(self::class, 'method1'))->getParameters(), + ); - $annotationReader->getParameterAnnotationsPerParameter((new ReflectionMethod(ClassWithTargetMethodParameterAnnotation::class, 'methodWithInvalidAnnotation'))->getParameters()); + $this->assertInstanceOf( + Autowire::class, + $parameterAnnotations['dao']->getAnnotationByType(Autowire::class), + ); } /** @noinspection PhpUnusedPrivateMethodInspection Used in {@see testPhp8AttributeParameterAnnotations} */ diff --git a/tests/Annotations/SecurityTest.php b/tests/Annotations/SecurityTest.php index a35275092f..3f82ae129d 100644 --- a/tests/Annotations/SecurityTest.php +++ b/tests/Annotations/SecurityTest.php @@ -4,8 +4,6 @@ use BadMethodCallException; use PHPUnit\Framework\TestCase; -use stdClass; -use TypeError; class SecurityTest extends TestCase { diff --git a/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php b/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php deleted file mode 100644 index 6830f2bbdf..0000000000 --- a/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php +++ /dev/null @@ -1,21 +0,0 @@ -target; - } -} From d17c80a37bab07f192ba93843618d2c5ec911145 Mon Sep 17 00:00:00 2001 From: Jaap van Otterdijk Date: Mon, 24 Mar 2025 19:19:17 +0100 Subject: [PATCH 07/10] Fix issue with kept deprecation reason (#735) When a type contains multiple fields and one of them is deprecated the following fields will be marked as deprecated. By resetting the value in each run we make sure the value is not kept. --- src/FieldsBuilder.php | 1 + tests/FieldsBuilderTest.php | 18 ++++++++++++++++ tests/Fixtures/TestDeprecatedField.php | 16 ++++++++++++++ .../TestObjectWithDeprecatedField.php | 21 +++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 tests/Fixtures/TestDeprecatedField.php create mode 100644 tests/Fixtures/TestObjectWithDeprecatedField.php diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php index 10b0c620ae..c0dc7d27d1 100644 --- a/src/FieldsBuilder.php +++ b/src/FieldsBuilder.php @@ -689,6 +689,7 @@ private function getQueryFieldsFromSourceFields( $docBlockComment = rtrim($docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render()); $deprecated = $docBlockObj->getTagsByName('deprecated'); + $deprecationReason = null; if (count($deprecated) >= 1) { $deprecationReason = trim((string) $deprecated[0]); } diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index 0cd11d0d49..c280218fec 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -37,11 +37,13 @@ use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithParamDateTime; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithReturnDateTime; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithUnionInputParam; +use TheCodingMachine\GraphQLite\Fixtures\TestDeprecatedField; use TheCodingMachine\GraphQLite\Fixtures\TestDoubleReturnTag; use TheCodingMachine\GraphQLite\Fixtures\TestEnum; use TheCodingMachine\GraphQLite\Fixtures\TestFieldBadInputType; use TheCodingMachine\GraphQLite\Fixtures\TestFieldBadOutputType; use TheCodingMachine\GraphQLite\Fixtures\TestObject; +use TheCodingMachine\GraphQLite\Fixtures\TestObjectWithDeprecatedField; use TheCodingMachine\GraphQLite\Fixtures\TestSelfType; use TheCodingMachine\GraphQLite\Fixtures\TestSourceFieldBadOutputType; use TheCodingMachine\GraphQLite\Fixtures\TestSourceFieldBadOutputType2; @@ -922,4 +924,20 @@ public function testSourceNameInSourceAndMagicFields(): void $result = $resolve($source, [], null, $this->createMock(ResolveInfo::class)); $this->assertSame('bar value', $result); } + + public function testDeprecationInDocblock(): void + { + $fieldsBuilder = $this->buildFieldsBuilder(); + $inputFields = $fieldsBuilder->getFields( + new TestDeprecatedField(), + 'Test', + ); + + $this->assertCount(2, $inputFields); + + $this->assertEquals('this is deprecated', $inputFields['deprecatedField']->deprecationReason); + $this->assertTrue( $inputFields['deprecatedField']->isDeprecated()); + $this->assertNull( $inputFields['name']->deprecationReason); + $this->assertFalse( $inputFields['name']->isDeprecated()); + } } diff --git a/tests/Fixtures/TestDeprecatedField.php b/tests/Fixtures/TestDeprecatedField.php new file mode 100644 index 0000000000..c417836ceb --- /dev/null +++ b/tests/Fixtures/TestDeprecatedField.php @@ -0,0 +1,16 @@ + Date: Tue, 25 Mar 2025 22:47:26 -0400 Subject: [PATCH 08/10] Bump codecov/codecov-action from 5.1.2 to 5.4.0 (#732) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.2 to 5.4.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.1.2...v5.4.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index a64e09d68e..67525172fe 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -84,7 +84,7 @@ jobs: path: "build" overwrite: true - - uses: codecov/codecov-action@v5.1.2 # upload the coverage to codecov + - uses: codecov/codecov-action@v5.4.0 # upload the coverage to codecov with: fail_ci_if_error: false # optional (default = false) - Need CODECOV_TOKEN # Do not upload in forks, and only on php8.3, latest deps From f99dfb8dd6aae22be84fba298b7c40174a80f72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:50:34 -0400 Subject: [PATCH 09/10] Bump JamesIves/github-pages-deploy-action from 4.7.2 to 4.7.3 (#730) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.7.2 to 4.7.3. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.7.2...v4.7.3) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/doc_generation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc_generation.yml b/.github/workflows/doc_generation.yml index e2a2abaf92..aeeb34dba5 100644 --- a/.github/workflows/doc_generation.yml +++ b/.github/workflows/doc_generation.yml @@ -36,7 +36,7 @@ jobs: - name: "Deploy website" if: "${{ github.event_name == 'push' || github.event_name == 'release' }}" - uses: JamesIves/github-pages-deploy-action@v4.7.2 + uses: JamesIves/github-pages-deploy-action@v4.7.3 with: token: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages # The branch the action should deploy to. From 6e36bfda5f7d6c255a43daf85a4647b65bc47b06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 23:56:41 -0400 Subject: [PATCH 10/10] Update doctrine/coding-standard requirement from ^12.0 to ^12.0 || ^13.0 (#741) * Update doctrine/coding-standard requirement from ^12.0 to ^12.0 || ^13.0 Updates the requirements on [doctrine/coding-standard](https://github.com/doctrine/coding-standard) to permit the latest version. - [Release notes](https://github.com/doctrine/coding-standard/releases) - [Commits](https://github.com/doctrine/coding-standard/compare/12.0.0...13.0.0) --- updated-dependencies: - dependency-name: doctrine/coding-standard dependency-type: direct:development ... Signed-off-by: dependabot[bot] * Run PHPCS on the target PHP version * Specify phpcs vendor/bin path * Only run CS checks on the minimum supported version * Resolve duplicate code coverage artifact name collision --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Thomason --- .github/workflows/continuous_integration.yml | 13 +++++++------ composer.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 67525172fe..49638e7e49 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -74,21 +74,22 @@ jobs: - name: "Run static code analysis with phpstan/phpstan" run: "composer phpstan" - - name: "Run coding standard checks with squizlabs/php_codesniffer" - run: "composer cs-check" + - name: "Run coding standard checks with squizlabs/php_codesniffer on minimum supported PHP version" + if: matrix.php-version == '8.1' + run: composer cs-check - name: "Archive code coverage results" uses: actions/upload-artifact@v4 with: - name: codeCoverage-${{ matrix.php-version }} + name: codeCoverage-${{ matrix.php-version }}-${{ github.run_id }} path: "build" overwrite: true - uses: codecov/codecov-action@v5.4.0 # upload the coverage to codecov with: fail_ci_if_error: false # optional (default = false) - Need CODECOV_TOKEN - # Do not upload in forks, and only on php8.3, latest deps - if: ${{ github.repository == 'thecodingmachine/graphqlite' && matrix.php-version == '8.3' && matrix.install-args == '' }} + # Do not upload in forks, and only on php8.4, latest deps + if: ${{ github.repository == 'thecodingmachine/graphqlite' && matrix.php-version == '8.4' && matrix.install-args == '' }} examples: name: Check Examples @@ -103,7 +104,7 @@ jobs: - name: "Install PHP with extensions" uses: "shivammathur/setup-php@v2" with: - php-version: "8.2" + php-version: "8.4" tools: composer:v2 - name: "Install dependencies with composer" working-directory: "examples/${{ matrix.example }}" diff --git a/composer.json b/composer.json index 33751a0af7..15e98ee2bd 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "beberlei/porpaginas": "^2.0", - "doctrine/coding-standard": "^12.0", + "doctrine/coding-standard": "^12.0 || ^13.0", "ecodev/graphql-upload": "^7.0", "laminas/laminas-diactoros": "^3.5", "myclabs/php-enum": "^1.6.6",