Skip to content

Commit a43a26e

Browse files
authored
Merge pull request #971 from ruudk/generator-callable
Support defining enum values by callable and/or generator
2 parents ebdcfe0 + 539bbbd commit a43a26e

File tree

2 files changed

+72
-23
lines changed

2 files changed

+72
-23
lines changed

src/EventListener/TypeDecoratorListener.php

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Overblog\GraphQLBundle\Event\TypeLoadedEvent;
1717
use Overblog\GraphQLBundle\Resolver\ResolverMapInterface;
1818
use Overblog\GraphQLBundle\Resolver\ResolverMaps;
19+
use Traversable;
1920
use function array_diff;
2021
use function count;
2122
use function current;
@@ -135,24 +136,41 @@ private function decorateCustomScalarType(CustomScalarType $type, ResolverMapInt
135136

136137
private function decorateEnumType(EnumType $type, ResolverMapInterface $resolverMap): void
137138
{
138-
$fieldNames = [];
139-
foreach ($type->config['values'] as $key => &$value) {
140-
$fieldName = $value['name'] ?? $key;
141-
if ($resolverMap->isResolvable($type->name, $fieldName)) {
142-
$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();
143144
}
144-
$fieldNames[] = $fieldName;
145-
}
146-
$unknownFields = array_diff($resolverMap->covered($type->name), $fieldNames);
147-
if (!empty($unknownFields)) {
148-
throw new InvalidArgumentException(
149-
sprintf(
150-
'"%s".{"%s"} defined in resolverMap, was defined in resolvers, but enum is not in schema.',
151-
$type->name,
152-
implode('", "', $unknownFields)
153-
)
154-
);
155-
}
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();
156174
}
157175

158176
private function decorateObjectTypeFields(ObjectType $type, array $fieldsResolved, ResolverMapInterface $resolverMap): void
@@ -164,6 +182,10 @@ private function decorateObjectTypeFields(ObjectType $type, array $fieldsResolve
164182
$fields = $fields();
165183
}
166184

185+
// Convert a Generator to an array so that can modify it (by reference)
186+
// and return the new array.
187+
$fields = $fields instanceof Traversable ? iterator_to_array($fields) : (array) $fields;
188+
167189
$fieldNames = [];
168190
foreach ($fields as $key => &$field) {
169191
$fieldName = $field['name'] ?? $key;

tests/EventListener/TypeDecoratorListenerTest.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,10 @@ public function testObjectTypeFieldDecoration(): ObjectType
6060
{
6161
$objectType = new ObjectType([
6262
'name' => 'Foo',
63-
'fields' => function () {
64-
return [
65-
'bar' => ['type' => Type::string()],
66-
'baz' => ['type' => Type::string()],
67-
'toto' => ['type' => Type::boolean(), 'resolve' => null],
68-
];
63+
'fields' => function (): iterable {
64+
yield 'bar' => ['type' => Type::string()];
65+
yield 'baz' => ['type' => Type::string()];
66+
yield 'toto' => ['type' => Type::boolean(), 'resolve' => null];
6967
},
7068
]);
7169
$barResolver = static fn () => 'bar';
@@ -143,6 +141,35 @@ public function testEnumTypeValuesDecoration(): void
143141
);
144142
}
145143

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+
146173
public function testEnumTypeUnknownField(): void
147174
{
148175
$enumType = new EnumType([

0 commit comments

Comments
 (0)