Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize phpenum type #1375

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
37 changes: 21 additions & 16 deletions src/Type/Definition/PhpEnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,36 @@

use GraphQL\Error\SerializationError;
use GraphQL\Utils\PhpDoc;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Utils\Utils;

/**
* @see EnumValueDefinitionNode
* @phpstan-import-type PartialEnumValueConfig from EnumType
*
* @phpstan-type PhpEnumTypeConfig array{
* name?: string|null,
* description?: string|null,
* enumClass: class-string<\UnitEnum>,
* astNode?: EnumTypeDefinitionNode|null,
* extensionASTNodes?: array<int, EnumTypeExtensionNode>|null
* }
*/
class PhpEnumType extends EnumType
{
public const MULTIPLE_DESCRIPTIONS_DISALLOWED = 'Using more than 1 Description attribute is not supported.';
public const MULTIPLE_DEPRECATIONS_DISALLOWED = 'Using more than 1 Deprecated attribute is not supported.';

/** @var class-string<\UnitEnum> */
protected string $enumClass;

/**
* @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
* @phpstan-param PhpEnumTypeConfig $config
*/
public function __construct(string $enum, ?string $name = null)
public function __construct(array $config)
{
$this->enumClass = $enum;
$reflection = new \ReflectionEnum($enum);
$enumClass = $config['enumClass'];
$reflection = new \ReflectionEnum($enumClass);

/**
* @var array<string, PartialEnumValueConfig> $enumDefinitions
*/
$enumDefinitions = [];
foreach ($reflection->getCases() as $case) {
$enumDefinitions[$case->name] = [
Expand All @@ -39,19 +44,19 @@ public function __construct(string $enum, ?string $name = null)
}

parent::__construct([
'name' => $name ?? $this->baseName($enum),
'name' => $config['name'] ?? $this->baseName($enumClass),
'values' => $enumDefinitions,
'description' => $this->extractDescription($reflection),
'description' => $config['description'] ?? $this->extractDescription($reflection),
'enumClass' => $enumClass
]);
}

public function serialize($value): string
{
if (! is_a($value, $this->enumClass)) {
if (! is_a($value, $this->config['enumClass'])) {
$notEnum = Utils::printSafe($value);
throw new SerializationError("Cannot serialize value as enum: {$notEnum}, expected instance of {$this->enumClass}.");
throw new SerializationError("Cannot serialize value as enum: {$notEnum}, expected instance of {$this->config['enumClass']}.");
}

return $value->name;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use GraphQL\Type\Definition\Description;

#[Description(description: 'foo')]
enum PhpEnum
enum MyCustomPhpEnum
spawnia marked this conversation as resolved.
Show resolved Hide resolved
{
#[Description(description: 'bar')]
case A;
Expand Down
39 changes: 26 additions & 13 deletions tests/Type/PhpEnumTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use GraphQL\Tests\Type\PhpEnumType\MultipleDeprecationsPhpEnum;
use GraphQL\Tests\Type\PhpEnumType\MultipleDescriptionsCasePhpEnum;
use GraphQL\Tests\Type\PhpEnumType\MultipleDescriptionsPhpEnum;
use GraphQL\Tests\Type\PhpEnumType\PhpEnum;
use GraphQL\Tests\Type\PhpEnumType\MyCustomPhpEnum;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\PhpEnumType;
use GraphQL\Type\Definition\Type;
Expand All @@ -30,11 +30,13 @@ protected function setUp(): void

public function testConstructEnumTypeFromPhpEnum(): void
{
$enumType = new PhpEnumType(PhpEnum::class);
$enumType = new PhpEnumType([
'enumClass' => MyCustomPhpEnum::class,
]);
self::assertSame(
<<<'GRAPHQL'
"foo"
enum PhpEnum {
enum MyCustomPhpEnum {
"bar"
A
B @deprecated
Expand All @@ -47,7 +49,10 @@ enum PhpEnum {

public function testConstructEnumTypeFromPhpEnumWithCustomName(): void
{
$enumType = new PhpEnumType(PhpEnum::class, 'CustomNamedPhpEnum');
$enumType = new PhpEnumType([
'enumClass' => MyCustomPhpEnum::class,
'name' => 'CustomNamedPhpEnum',
]);
self::assertSame(
<<<'GRAPHQL'
"foo"
Expand All @@ -64,7 +69,7 @@ enum CustomNamedPhpEnum {

public function testConstructEnumTypeFromPhpEnumWithDocBlockDescriptions(): void
{
$enumType = new PhpEnumType(DocBlockPhpEnum::class);
$enumType = new PhpEnumType(['enumClass' => DocBlockPhpEnum::class]);
self::assertSame(
<<<'GRAPHQL'
"foo"
Expand All @@ -86,24 +91,30 @@ enum DocBlockPhpEnum {
public function testMultipleDescriptionsDisallowed(): void
{
self::expectExceptionObject(new \Exception(PhpEnumType::MULTIPLE_DESCRIPTIONS_DISALLOWED));
new PhpEnumType(MultipleDescriptionsPhpEnum::class);
new PhpEnumType([
'enumClass' => MultipleDescriptionsPhpEnum::class,
]);
}

public function testMultipleDescriptionsDisallowedOnCase(): void
{
self::expectExceptionObject(new \Exception(PhpEnumType::MULTIPLE_DESCRIPTIONS_DISALLOWED));
new PhpEnumType(MultipleDescriptionsCasePhpEnum::class);
new PhpEnumType([
'enumClass' => MultipleDescriptionsCasePhpEnum::class,
]);
}

public function testMultipleDeprecationsDisallowed(): void
{
self::expectExceptionObject(new \Exception(PhpEnumType::MULTIPLE_DEPRECATIONS_DISALLOWED));
new PhpEnumType(MultipleDeprecationsPhpEnum::class);
new PhpEnumType([
'enumClass' => MultipleDeprecationsPhpEnum::class,
]);
}

public function testExecutesWithEnumTypeFromPhpEnum(): void
{
$enumType = new PhpEnumType(PhpEnum::class);
$enumType = new PhpEnumType(['enumClass' => MyCustomPhpEnum::class]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
Expand All @@ -115,9 +126,9 @@ public function testExecutesWithEnumTypeFromPhpEnum(): void
'type' => Type::nonNull($enumType),
],
],
'resolve' => static function ($_, array $args): PhpEnum {
'resolve' => static function ($_, array $args): MyCustomPhpEnum {
$bar = $args['bar'];
assert($bar === PhpEnum::A);
assert($bar === MyCustomPhpEnum::A);

return $bar;
},
Expand All @@ -138,7 +149,9 @@ public function testExecutesWithEnumTypeFromPhpEnum(): void

public function testFailsToSerializeNonEnum(): void
{
$enumType = new PhpEnumType(PhpEnum::class);
$enumType = new PhpEnumType([
'enumClass' => MyCustomPhpEnum::class,
]);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
Expand All @@ -153,7 +166,7 @@ public function testFailsToSerializeNonEnum(): void

$result = GraphQL::executeQuery($schema, '{ foo }');

self::expectExceptionObject(new SerializationError('Cannot serialize value as enum: "A", expected instance of GraphQL\\Tests\\Type\\PhpEnumType\\PhpEnum.'));
self::expectExceptionObject(new SerializationError('Cannot serialize value as enum: "A", expected instance of GraphQL\\Tests\\Type\\PhpEnumType\\MyCustomPhpEnum.'));
$result->toArray(DebugFlag::RETHROW_INTERNAL_EXCEPTIONS);
}
}