Skip to content

Commit 0e3645a

Browse files
authored
Allow passing enum values via callable (#1040)
1 parent b4cbc3a commit 0e3645a

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

src/Type/Definition/EnumType.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use GraphQL\Utils\Utils;
1616

1717
use function is_array;
18+
use function is_callable;
1819
use function is_iterable;
1920
use function is_string;
2021

@@ -26,10 +27,11 @@
2627
* description?: string|null,
2728
* astNode?: EnumValueDefinitionNode|null,
2829
* }
30+
* @phpstan-type EnumValues iterable<string, PartialEnumValueConfig>|iterable<string, mixed>|iterable<int, string>
2931
* @phpstan-type EnumTypeConfig array{
3032
* name?: string|null,
3133
* description?: string|null,
32-
* values: iterable<string, PartialEnumValueConfig>|iterable<string, mixed>|iterable<int, string>,
34+
* values: EnumValues|callable(): EnumValues,
3335
* astNode?: EnumTypeDefinitionNode|null,
3436
* extensionASTNodes?: array<int, EnumTypeExtensionNode>|null,
3537
* }
@@ -96,8 +98,13 @@ public function getValues(): array
9698
if (! isset($this->values)) {
9799
$this->values = [];
98100

101+
$values = $this->config['values'];
102+
if (is_callable($values)) {
103+
$values = $values();
104+
}
105+
99106
// We are just assuming the config option is set correctly here, validation happens in assertValid()
100-
foreach ($this->config['values'] as $name => $value) {
107+
foreach ($values as $name => $value) {
101108
if (is_string($name)) {
102109
if (is_array($value)) {
103110
$value += ['name' => $name, 'value' => $name];
@@ -183,10 +190,10 @@ public function assertValid(): void
183190

184191
$values = $this->config['values'] ?? null;
185192
// @phpstan-ignore-next-line should not happen if used correctly
186-
if (! is_iterable($values)) {
193+
if (! is_iterable($values) && ! is_callable($values)) {
187194
$notIterable = Utils::printSafe($values);
188195

189-
throw new InvariantViolation("{$this->name} values must be an iterable, got: {$notIterable}");
196+
throw new InvariantViolation("{$this->name} values must be an iterable or callable, got: {$notIterable}");
190197
}
191198

192199
$this->getValues();

tests/Type/DefinitionTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,19 @@ public function testAcceptsAWellDefinedEnumTypeWithInternalValueDefinition(): vo
16841684
self::assertEquals(20, $enumType->getValue('BAR')->value);
16851685
}
16861686

1687+
public function testAcceptsACallableAsEnumValues(): void
1688+
{
1689+
$enumType = new EnumType([
1690+
'name' => 'SomeEnum',
1691+
'values' => static function (): iterable {
1692+
yield 'FOO' => ['value' => 10];
1693+
yield 'BAR' => ['value' => 20];
1694+
},
1695+
]);
1696+
self::assertEquals(10, $enumType->getValue('FOO')->value);
1697+
self::assertEquals(20, $enumType->getValue('BAR')->value);
1698+
}
1699+
16871700
/**
16881701
* @see it('rejects an Enum type with incorrectly typed values')
16891702
*/
@@ -1701,6 +1714,20 @@ public function testRejectsAnEnumTypeWithIncorrectlyTypedValues(): void
17011714
$enumType->assertValid();
17021715
}
17031716

1717+
public function testRejectsAnEnumTypeWithIncorrectlyTypedValues2(): void
1718+
{
1719+
// @phpstan-ignore-next-line intentionally wrong
1720+
$enumType = new EnumType([
1721+
'name' => 'SomeEnum',
1722+
'values' => 'FOO',
1723+
]);
1724+
1725+
$this->expectExceptionObject(new InvariantViolation(
1726+
'SomeEnum values must be an iterable or callable, got: FOO'
1727+
));
1728+
$enumType->assertValid();
1729+
}
1730+
17041731
/**
17051732
* @see it('rejects a Schema which redefines a built-in type')
17061733
*/

tests/Type/EnumTypeTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,4 +655,40 @@ public function testCallsOverwrittenEnumTypeMethods(): void
655655
GraphQL::executeQuery($this->schema, $query, null, null, $variables)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)
656656
);
657657
}
658+
659+
public function testLazilyDefineValuesAsCallable(): void
660+
{
661+
$called = 0;
662+
663+
$ColorType = new EnumType([
664+
'name' => 'Color',
665+
'values' => static function () use (&$called): iterable {
666+
$called++;
667+
yield 'RED' => ['value' => 0];
668+
},
669+
]);
670+
671+
$QueryType = new ObjectType([
672+
'name' => 'Query',
673+
'fields' => [
674+
'colorEnum' => [
675+
'type' => $ColorType,
676+
'args' => [
677+
'fromEnum' => ['type' => $ColorType],
678+
],
679+
'resolve' => static function ($rootValue, array $args) {
680+
return $args['fromEnum'];
681+
},
682+
],
683+
],
684+
]);
685+
686+
$schema = new Schema(['query' => $QueryType]);
687+
688+
self::assertEquals(
689+
['data' => ['colorEnum' => 'RED']],
690+
GraphQL::executeQuery($schema, '{ colorEnum(fromEnum: RED) }')->toArray()
691+
);
692+
self::assertSame(1, $called);
693+
}
658694
}

0 commit comments

Comments
 (0)