Skip to content

Commit 9baaf42

Browse files
authored
Allow native PHP enums as return values for SDL-based enums
1 parent cba88b5 commit 9baaf42

13 files changed

+147
-44
lines changed

.github/workflows/static-analysis.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
php-version:
18-
- 7.4
19-
- 8.0
20-
- 8.1
21-
- 8.2
22-
- 8.3
18+
- "7.4"
19+
- "8.0"
20+
- "8.1"
21+
- "8.2"
22+
- "8.3"
2323

2424
steps:
2525
- name: Checkout code
@@ -29,7 +29,7 @@ jobs:
2929
uses: shivammathur/setup-php@v2
3030
with:
3131
coverage: none
32-
php-version: ${{ matrix.php-version }}
32+
php-version: "${{ matrix.php-version }}"
3333
tools: cs2pr
3434

3535
- name: Install dependencies with Composer

.github/workflows/test.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ jobs:
1616
strategy:
1717
matrix:
1818
php-version:
19-
- 7.4
20-
- 8.0
21-
- 8.1
22-
- 8.2
23-
- 8.3
19+
- "7.4"
20+
- "8.0"
21+
- "8.1"
22+
- "8.2"
23+
- "8.3"
2424
dependencies:
2525
- highest
2626
include:
@@ -36,7 +36,7 @@ jobs:
3636
- name: Install PHP
3737
uses: shivammathur/setup-php@v2
3838
with:
39-
php-version: ${{ matrix.php-version }}
39+
php-version: "${{ matrix.php-version }}"
4040
coverage: pcov
4141

4242
- name: Install dependencies with Composer

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
],
7777
"php-cs-fixer": "php-cs-fixer fix",
7878
"rector": "rector process",
79-
"stan": "phpstan",
79+
"stan": "phpstan --verbose",
8080
"test": "php -d zend.exception_ignore_args=Off -d zend.assertions=On -d assert.active=On -d assert.exception=On vendor/bin/phpunit"
8181
}
8282
}

phpstan.neon.dist

+18-29
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,6 @@ parameters:
1414
missingCheckedExceptionInThrows: true
1515
tooWideThrowType: true
1616

17-
excludePaths:
18-
# PHP 8 attributes
19-
- src/Type/Definition/Deprecated.php
20-
- src/Type/Definition/Description.php
21-
# PHP 8.1 enums
22-
- src/Type/Definition/PhpEnumType.php
23-
- tests/Type/PhpEnumTypeTest.php
24-
- tests/Type/PhpEnumType
25-
2617
ignoreErrors:
2718
# Since this is a library that is supposed to be flexible, we don't
2819
# want to lock down every possible extension point.
@@ -33,10 +24,9 @@ parameters:
3324
- "~Variable method call on GraphQL\\\\Language\\\\Parser\\.~"
3425

3526
# Useful/necessary when dealing with arbitrary user data
36-
-
37-
message: "~Variable property access on object~"
38-
path: src/Utils/Utils.php
39-
count: 2
27+
- message: "~Variable property access on object~"
28+
path: src/Utils/Utils.php
29+
count: 2
4030

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

6252
services:
63-
-
64-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsAbstractTypeStaticMethodTypeSpecifyingExtension
65-
tags:
66-
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
67-
-
68-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension
69-
tags:
70-
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
71-
-
72-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension
73-
tags:
74-
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
75-
-
76-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension
77-
tags:
78-
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
53+
- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsAbstractTypeStaticMethodTypeSpecifyingExtension
54+
tags:
55+
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
56+
57+
- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension
58+
tags:
59+
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
60+
61+
- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension
62+
tags:
63+
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
64+
65+
- class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension
66+
tags:
67+
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension

phpstan/include-by-php-version.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
$includes = [];
44

5-
if (PHP_VERSION_ID >= 80200) {
6-
$includes[] = __DIR__ . '/php-82.neon';
5+
$phpversion = phpversion();
6+
if (version_compare($phpversion, '8.2', '>=')) {
7+
$includes[] = __DIR__ . '/php-at-least-8.2.neon';
8+
}
9+
if (version_compare($phpversion, '8.1', '<')) {
10+
$includes[] = __DIR__ . '/php-below-8.1.neon';
11+
}
12+
if (version_compare($phpversion, '8.0', '<')) {
13+
$includes[] = __DIR__ . '/php-below-8.0.neon';
714
}
815

916
$config = [];
File renamed without changes.

phpstan/php-below-8.0.neon

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
parameters:
2+
excludePaths:
3+
# PHP 8 attributes
4+
- ../src/Type/Definition/Deprecated.php
5+
- ../src/Type/Definition/Description.php
6+
ignoreErrors:
7+
# Native enums require PHP 8.1, but checking if a value is of an unknown class still works
8+
- path: ../src/Type/Definition/EnumType.php
9+
identifier: class.notFound

phpstan/php-below-8.1.neon

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
excludePaths:
3+
# PHP 8.1 enums
4+
- ../src/Type/Definition/PhpEnumType.php
5+
- ../tests/Type/EnumTypeTest.php
6+
- ../tests/Type/PhpEnumTypeTest.php
7+
- ../tests/Type/PhpEnumType

src/Type/Definition/EnumType.php

+8
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ public function serialize($value)
136136
return $lookup[$value]->name;
137137
}
138138

139+
if (is_a($value, \BackedEnum::class)) {
140+
return $value->value;
141+
}
142+
143+
if (is_a($value, \UnitEnum::class)) {
144+
return $value->name;
145+
}
146+
139147
$safeValue = Utils::printSafe($value);
140148
throw new SerializationError("Cannot serialize value as enum: {$safeValue}");
141149
}

src/Type/Definition/PhpEnumType.php

+9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class PhpEnumType extends EnumType
1818
/**
1919
* @param class-string<\UnitEnum> $enum
2020
* @param string|null $name The name the enum will have in the schema, defaults to the basename of the given class
21+
*
22+
* @throws \Exception
23+
* @throws \ReflectionException
2124
*/
2225
public function __construct(string $enum, ?string $name = null)
2326
{
@@ -82,6 +85,11 @@ protected function baseName(string $class): string
8285
return end($parts);
8386
}
8487

88+
/**
89+
* @param \ReflectionClassConstant|\ReflectionClass<\UnitEnum> $reflection
90+
*
91+
* @throws \Exception
92+
*/
8593
protected function extractDescription(\ReflectionClassConstant|\ReflectionClass $reflection): ?string
8694
{
8795
$attributes = $reflection->getAttributes(Description::class);
@@ -100,6 +108,7 @@ protected function extractDescription(\ReflectionClassConstant|\ReflectionClass
100108
return PhpDoc::unwrap($unpadded);
101109
}
102110

111+
/** @throws \Exception */
103112
protected function deprecationReason(\ReflectionClassConstant $reflection): ?string
104113
{
105114
$attributes = $reflection->getAttributes(Deprecated::class);

tests/Type/EnumTypeTest.php

+62
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
66
use GraphQL\Error\DebugFlag;
77
use GraphQL\GraphQL;
8+
use GraphQL\Language\Parser;
89
use GraphQL\Language\SourceLocation;
10+
use GraphQL\Tests\Type\PhpEnumType\BackedPhpEnum;
11+
use GraphQL\Tests\Type\PhpEnumType\PhpEnum;
912
use GraphQL\Tests\Type\TestClasses\OtherEnumType;
1013
use GraphQL\Type\Definition\EnumType;
1114
use GraphQL\Type\Definition\EnumValueDefinition;
1215
use GraphQL\Type\Definition\ObjectType;
1316
use GraphQL\Type\Definition\Type;
1417
use GraphQL\Type\Introspection;
1518
use GraphQL\Type\Schema;
19+
use GraphQL\Utils\BuildSchema;
1620
use PHPUnit\Framework\TestCase;
1721

1822
final class EnumTypeTest extends TestCase
@@ -661,4 +665,62 @@ public function testLazilyDefineValuesAsCallable(): void
661665
// @phpstan-ignore-next-line $called is mutated
662666
self::assertSame(1, $called, 'Should call enum values callable exactly once');
663667
}
668+
669+
public function testSerializesNativeBackedEnums(): void
670+
{
671+
if (version_compare(phpversion(), '8.1', '<')) {
672+
self::markTestSkipped('Native PHP enums are only available with PHP 8.1');
673+
}
674+
675+
$documentNode = Parser::parse(<<<'SDL'
676+
type Query {
677+
phpEnum(fromEnum: PhpEnum!): PhpEnum!
678+
}
679+
680+
enum PhpEnum {
681+
A
682+
B
683+
C
684+
}
685+
SDL);
686+
687+
$this->schema = BuildSchema::build($documentNode);
688+
$resolvers = [
689+
'phpEnum' => fn (): BackedPhpEnum => BackedPhpEnum::A,
690+
];
691+
692+
self::assertSame(
693+
['data' => ['phpEnum' => 'A']],
694+
GraphQL::executeQuery($this->schema, '{ phpEnum(fromEnum: A) }', $resolvers)->toArray()
695+
);
696+
}
697+
698+
public function testSerializesNativeUnitEnums(): void
699+
{
700+
if (version_compare(phpversion(), '8.1', '<')) {
701+
self::markTestSkipped('Native PHP enums are only available with PHP 8.1');
702+
}
703+
704+
$documentNode = Parser::parse(<<<'SDL'
705+
type Query {
706+
phpEnum(fromEnum: PhpEnum!): PhpEnum!
707+
}
708+
709+
enum PhpEnum {
710+
A
711+
B
712+
C
713+
}
714+
SDL);
715+
716+
$this->schema = BuildSchema::build($documentNode);
717+
$resolvers = [
718+
'phpEnum' => fn (): PhpEnum => PhpEnum::B,
719+
];
720+
721+
self::assertSame(
722+
['data' => ['phpEnum' => 'B']],
723+
GraphQL::executeQuery($this->schema, '{ phpEnum(fromEnum: B) }', $resolvers)->toArray()
724+
);
725+
}
664726
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace GraphQL\Tests\Type\PhpEnumType;
4+
5+
enum BackedPhpEnum: string
6+
{
7+
case A = 'A';
8+
case B = 'B';
9+
case C = 'C';
10+
}

tests/Type/PhpEnumTypeTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ public function testAcceptsEnumFromVariableValues(): void
178178
$bar = $args['bar'];
179179
assert($bar === PhpEnum::A);
180180

181+
assert($schema instanceof Schema);
182+
181183
if ($executeAgain) {
182184
$executionResult = GraphQL::executeQuery(
183185
$schema,

0 commit comments

Comments
 (0)