Skip to content

Commit 539bbbd

Browse files
committed
Support defining enum values by callable and/or generator
See webonyx/graphql-php#1040
1 parent 96d1255 commit 539bbbd

File tree

2 files changed

+63
-17
lines changed

2 files changed

+63
-17
lines changed

src/EventListener/TypeDecoratorListener.php

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,24 +136,41 @@ private function decorateCustomScalarType(CustomScalarType $type, ResolverMapInt
136136

137137
private function decorateEnumType(EnumType $type, ResolverMapInterface $resolverMap): void
138138
{
139-
$fieldNames = [];
140-
foreach ($type->config['values'] as $key => &$value) {
141-
$fieldName = $value['name'] ?? $key;
142-
if ($resolverMap->isResolvable($type->name, $fieldName)) {
143-
$value['value'] = $resolverMap->resolve($type->name, $fieldName);
139+
$values = $type->config['values'];
140+
141+
$decoratedValues = function () use ($type, $resolverMap, $values) {
142+
if (is_callable($values)) {
143+
$values = $values();
144144
}
145-
$fieldNames[] = $fieldName;
146-
}
147-
$unknownFields = array_diff($resolverMap->covered($type->name), $fieldNames);
148-
if (!empty($unknownFields)) {
149-
throw new InvalidArgumentException(
150-
sprintf(
151-
'"%s".{"%s"} defined in resolverMap, was defined in resolvers, but enum is not in schema.',
152-
$type->name,
153-
implode('", "', $unknownFields)
154-
)
155-
);
156-
}
145+
146+
// Convert a Generator to an array so that can modify it (by reference)
147+
// and return the new array.
148+
$values = $values instanceof Traversable ? iterator_to_array($values) : (array) $values;
149+
150+
$fieldNames = [];
151+
foreach ($values as $key => &$value) {
152+
$fieldName = $value['name'] ?? $key;
153+
if ($resolverMap->isResolvable($type->name, $fieldName)) {
154+
$value['value'] = $resolverMap->resolve($type->name, $fieldName);
155+
}
156+
$fieldNames[] = $fieldName;
157+
}
158+
$unknownFields = array_diff($resolverMap->covered($type->name), $fieldNames);
159+
if (!empty($unknownFields)) {
160+
throw new InvalidArgumentException(
161+
sprintf(
162+
'"%s".{"%s"} defined in resolverMap, was defined in resolvers, but enum is not in schema.',
163+
$type->name,
164+
implode('", "', $unknownFields)
165+
)
166+
);
167+
}
168+
169+
return $values;
170+
};
171+
172+
/** @phpstan-ignore-next-line see https://github.com/webonyx/graphql-php/issues/1041 */
173+
$type->config['values'] = is_callable($values) ? $decoratedValues : $decoratedValues();
157174
}
158175

159176
private function decorateObjectTypeFields(ObjectType $type, array $fieldsResolved, ResolverMapInterface $resolverMap): void

tests/EventListener/TypeDecoratorListenerTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,35 @@ public function testEnumTypeValuesDecoration(): void
141141
);
142142
}
143143

144+
public function testEnumTypeLazyValuesDecoration(): void
145+
{
146+
$enumType = new EnumType([
147+
'name' => 'Foo',
148+
'values' => function (): iterable {
149+
yield 'BAR' => ['name' => 'BAR', 'value' => 'BAR'];
150+
yield 'BAZ' => ['name' => 'BAZ', 'value' => 'BAZ'];
151+
yield 'TOTO' => ['name' => 'TOTO', 'value' => 'TOTO'];
152+
},
153+
]);
154+
155+
$this->decorate(
156+
[$enumType->name => $enumType],
157+
[$enumType->name => ['BAR' => 1, 'BAZ' => 2]]
158+
);
159+
160+
$values = is_callable($enumType->config['values']) ? $enumType->config['values']() : $enumType->config['values'];
161+
$values = $values instanceof Traversable ? iterator_to_array($values) : (array) $values;
162+
163+
$this->assertSame(
164+
[
165+
'BAR' => ['name' => 'BAR', 'value' => 1],
166+
'BAZ' => ['name' => 'BAZ', 'value' => 2],
167+
'TOTO' => ['name' => 'TOTO', 'value' => 'TOTO'],
168+
],
169+
$values
170+
);
171+
}
172+
144173
public function testEnumTypeUnknownField(): void
145174
{
146175
$enumType = new EnumType([

0 commit comments

Comments
 (0)