Skip to content

Commit

Permalink
Allow native PHP enums as return values for SDL-based enums
Browse files Browse the repository at this point in the history
  • Loading branch information
Dahl99 authored Sep 10, 2024
1 parent cba88b5 commit 9baaf42
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 44 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ jobs:
fail-fast: false
matrix:
php-version:
- 7.4
- 8.0
- 8.1
- 8.2
- 8.3
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"

steps:
- name: Checkout code
Expand All @@ -29,7 +29,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: ${{ matrix.php-version }}
php-version: "${{ matrix.php-version }}"
tools: cs2pr

- name: Install dependencies with Composer
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ jobs:
strategy:
matrix:
php-version:
- 7.4
- 8.0
- 8.1
- 8.2
- 8.3
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
dependencies:
- highest
include:
Expand All @@ -36,7 +36,7 @@ jobs:
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
php-version: "${{ matrix.php-version }}"
coverage: pcov

- name: Install dependencies with Composer
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
],
"php-cs-fixer": "php-cs-fixer fix",
"rector": "rector process",
"stan": "phpstan",
"stan": "phpstan --verbose",
"test": "php -d zend.exception_ignore_args=Off -d zend.assertions=On -d assert.active=On -d assert.exception=On vendor/bin/phpunit"
}
}
47 changes: 18 additions & 29 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,6 @@ parameters:
missingCheckedExceptionInThrows: true
tooWideThrowType: true

excludePaths:
# PHP 8 attributes
- src/Type/Definition/Deprecated.php
- src/Type/Definition/Description.php
# PHP 8.1 enums
- src/Type/Definition/PhpEnumType.php
- tests/Type/PhpEnumTypeTest.php
- tests/Type/PhpEnumType

ignoreErrors:
# Since this is a library that is supposed to be flexible, we don't
# want to lock down every possible extension point.
Expand All @@ -33,10 +24,9 @@ parameters:
- "~Variable method call on GraphQL\\\\Language\\\\Parser\\.~"

# Useful/necessary when dealing with arbitrary user data
-
message: "~Variable property access on object~"
path: src/Utils/Utils.php
count: 2
- message: "~Variable property access on object~"
path: src/Utils/Utils.php
count: 2

# PHPStan does not play nicely with markTestSkipped()
- message: "~Unreachable statement - code above always terminates~"
Expand All @@ -60,19 +50,18 @@ includes:
- phpstan/include-by-php-version.php

services:
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsAbstractTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsAbstractTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension

- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension

- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension

- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
11 changes: 9 additions & 2 deletions phpstan/include-by-php-version.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

$includes = [];

if (PHP_VERSION_ID >= 80200) {
$includes[] = __DIR__ . '/php-82.neon';
$phpversion = phpversion();
if (version_compare($phpversion, '8.2', '>=')) {
$includes[] = __DIR__ . '/php-at-least-8.2.neon';
}
if (version_compare($phpversion, '8.1', '<')) {
$includes[] = __DIR__ . '/php-below-8.1.neon';
}
if (version_compare($phpversion, '8.0', '<')) {
$includes[] = __DIR__ . '/php-below-8.0.neon';
}

$config = [];
Expand Down
File renamed without changes.
9 changes: 9 additions & 0 deletions phpstan/php-below-8.0.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
parameters:
excludePaths:
# PHP 8 attributes
- ../src/Type/Definition/Deprecated.php
- ../src/Type/Definition/Description.php
ignoreErrors:
# Native enums require PHP 8.1, but checking if a value is of an unknown class still works
- path: ../src/Type/Definition/EnumType.php
identifier: class.notFound
7 changes: 7 additions & 0 deletions phpstan/php-below-8.1.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
excludePaths:
# PHP 8.1 enums
- ../src/Type/Definition/PhpEnumType.php
- ../tests/Type/EnumTypeTest.php
- ../tests/Type/PhpEnumTypeTest.php
- ../tests/Type/PhpEnumType
8 changes: 8 additions & 0 deletions src/Type/Definition/EnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ public function serialize($value)
return $lookup[$value]->name;
}

if (is_a($value, \BackedEnum::class)) {
return $value->value;
}

if (is_a($value, \UnitEnum::class)) {
return $value->name;
}

$safeValue = Utils::printSafe($value);
throw new SerializationError("Cannot serialize value as enum: {$safeValue}");
}
Expand Down
9 changes: 9 additions & 0 deletions src/Type/Definition/PhpEnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class PhpEnumType extends EnumType
/**
* @param class-string<\UnitEnum> $enum
* @param string|null $name The name the enum will have in the schema, defaults to the basename of the given class
*
* @throws \Exception
* @throws \ReflectionException
*/
public function __construct(string $enum, ?string $name = null)
{
Expand Down Expand Up @@ -82,6 +85,11 @@ protected function baseName(string $class): string
return end($parts);
}

/**
* @param \ReflectionClassConstant|\ReflectionClass<\UnitEnum> $reflection
*
* @throws \Exception
*/
protected function extractDescription(\ReflectionClassConstant|\ReflectionClass $reflection): ?string
{
$attributes = $reflection->getAttributes(Description::class);
Expand All @@ -100,6 +108,7 @@ protected function extractDescription(\ReflectionClassConstant|\ReflectionClass
return PhpDoc::unwrap($unpadded);
}

/** @throws \Exception */
protected function deprecationReason(\ReflectionClassConstant $reflection): ?string
{
$attributes = $reflection->getAttributes(Deprecated::class);
Expand Down
62 changes: 62 additions & 0 deletions tests/Type/EnumTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use GraphQL\Error\DebugFlag;
use GraphQL\GraphQL;
use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Tests\Type\PhpEnumType\BackedPhpEnum;
use GraphQL\Tests\Type\PhpEnumType\PhpEnum;
use GraphQL\Tests\Type\TestClasses\OtherEnumType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\EnumValueDefinition;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use GraphQL\Utils\BuildSchema;
use PHPUnit\Framework\TestCase;

final class EnumTypeTest extends TestCase
Expand Down Expand Up @@ -661,4 +665,62 @@ public function testLazilyDefineValuesAsCallable(): void
// @phpstan-ignore-next-line $called is mutated
self::assertSame(1, $called, 'Should call enum values callable exactly once');
}

public function testSerializesNativeBackedEnums(): void
{
if (version_compare(phpversion(), '8.1', '<')) {
self::markTestSkipped('Native PHP enums are only available with PHP 8.1');
}

$documentNode = Parser::parse(<<<'SDL'
type Query {
phpEnum(fromEnum: PhpEnum!): PhpEnum!
}
enum PhpEnum {
A
B
C
}
SDL);

$this->schema = BuildSchema::build($documentNode);
$resolvers = [
'phpEnum' => fn (): BackedPhpEnum => BackedPhpEnum::A,
];

self::assertSame(
['data' => ['phpEnum' => 'A']],
GraphQL::executeQuery($this->schema, '{ phpEnum(fromEnum: A) }', $resolvers)->toArray()
);
}

public function testSerializesNativeUnitEnums(): void
{
if (version_compare(phpversion(), '8.1', '<')) {
self::markTestSkipped('Native PHP enums are only available with PHP 8.1');
}

$documentNode = Parser::parse(<<<'SDL'
type Query {
phpEnum(fromEnum: PhpEnum!): PhpEnum!
}
enum PhpEnum {
A
B
C
}
SDL);

$this->schema = BuildSchema::build($documentNode);
$resolvers = [
'phpEnum' => fn (): PhpEnum => PhpEnum::B,
];

self::assertSame(
['data' => ['phpEnum' => 'B']],
GraphQL::executeQuery($this->schema, '{ phpEnum(fromEnum: B) }', $resolvers)->toArray()
);
}
}
10 changes: 10 additions & 0 deletions tests/Type/PhpEnumType/BackedPhpEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace GraphQL\Tests\Type\PhpEnumType;

enum BackedPhpEnum: string
{
case A = 'A';
case B = 'B';
case C = 'C';
}
2 changes: 2 additions & 0 deletions tests/Type/PhpEnumTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ public function testAcceptsEnumFromVariableValues(): void
$bar = $args['bar'];
assert($bar === PhpEnum::A);

assert($schema instanceof Schema);

if ($executeAgain) {
$executionResult = GraphQL::executeQuery(
$schema,
Expand Down

0 comments on commit 9baaf42

Please sign in to comment.