Skip to content

Commit

Permalink
PHP 8.4 Support (#722)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
oojacoboo authored Dec 18, 2024
1 parent 7b7ae16 commit fbb20d2
Show file tree
Hide file tree
Showing 24 changed files with 64 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 8 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 2 additions & 5 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -42,3 +37,5 @@ parameters:
-
message: '#Call to an undefined method object::__toString\(\)#'
path : src/Types/ID.php
-
identifier: missingType.generics
6 changes: 0 additions & 6 deletions src/Annotations/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion src/Exceptions/GraphQLAggregateException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Exception;
use GraphQL\Error\ClientAware;
use RuntimeException;
use Throwable;

use function array_map;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/FactoryContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public function getClassFinderComputedCache(): ClassFinderComputedCache
return $this->classFinderComputedCache;
}

public function getClassBoundCache(): ClassBoundCache|null
public function getClassBoundCache(): ClassBoundCache
{
return $this->classBoundCache;
}
Expand Down
12 changes: 6 additions & 6 deletions src/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions src/Http/Psr15GraphQLMiddlewareBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
22 changes: 7 additions & 15 deletions src/Http/WebonyxGraphqlMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<int,ExecutionResult>|Promise $result */
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/InputTypeUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 7 additions & 16 deletions src/Mappers/Parameters/TypeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand All @@ -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()) {
Expand Down
17 changes: 2 additions & 15 deletions src/Mappers/Proxys/MutableInterfaceTypeAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -27,14 +14,14 @@
*/
final class MutableInterfaceTypeAdapter extends MutableInterfaceType
{
/** @use MutableAdapterTrait */
use MutableAdapterTrait;

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;
Expand Down
2 changes: 1 addition & 1 deletion src/Mappers/Proxys/MutableObjectTypeAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -16,7 +17,6 @@
*/
final class MutableObjectTypeAdapter extends MutableObjectType
{
/** @use MutableAdapterTrait */
use MutableAdapterTrait;

public function __construct(ObjectType $type, ?string $className = null)
Expand Down
3 changes: 1 addition & 2 deletions src/Mappers/RecursiveTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ public function findInterfaces(string $className): array
{
$interfaces = [];

/** @var array<int, class-string<object>> $implements */
$implements = class_implements($className);
foreach ($implements as $interface) {
if (! $this->typeMapper->canMapClassToType($interface)) {
Expand Down Expand Up @@ -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]);
Expand Down
Loading

0 comments on commit fbb20d2

Please sign in to comment.