Skip to content

Commit 06f817f

Browse files
committed
Don't loop infinitely on self-referencing types
1 parent a0b2155 commit 06f817f

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

src/GraphQL/CorrespondanceRule.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ private function listFieldResolvedValueErrors(
154154
$objectType,
155155
$fieldName,
156156
$resolverClassType,
157+
[],
157158
);
158159

159160
foreach ($errors as $error) {
@@ -186,6 +187,7 @@ private function listFieldResolvedValueErrors(
186187

187188

188189
/**
190+
* @param list<string> $alreadyVisitedFields
189191
* @return array{list<PHPStan\Type\Type>, list<string>}
190192
*/
191193
private function listFieldResolvedValueTypes(
@@ -194,15 +196,23 @@ private function listFieldResolvedValueTypes(
194196
string $objectType,
195197
string $fieldName,
196198
PHPStan\Type\ObjectType $resolverClassType,
199+
array $alreadyVisitedFields,
197200
): array
198201
{
199202
$errors = [];
200203
$types = [];
201204

205+
$parentTypes = $this->listObjectTypeResolvedValueTypes(
206+
$scope,
207+
$schemaServiceOraculum,
208+
$objectType,
209+
[...$alreadyVisitedFields, "$objectType.$fieldName"],
210+
);
211+
202212
if ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\ArrayFieldResolver::class) {
203213
$offsetType = new PHPStan\Type\Constant\ConstantStringType($fieldName);
204214

205-
foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
215+
foreach ($parentTypes as $parentType) {
206216
if ($parentType->isOffsetAccessible()->yes() === false) {
207217
$errors[] = sprintf(
208218
"Resolver %s of field %s expects parent to have array access, but parent is resolved to %s",
@@ -225,7 +235,7 @@ private function listFieldResolvedValueTypes(
225235
} elseif ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\GetterFieldResolver::class) {
226236
$methodName = 'get' . ucfirst($fieldName);
227237

228-
foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
238+
foreach ($parentTypes as $parentType) {
229239
if ($parentType->isObject()->yes() === false) {
230240
$errors[] = sprintf(
231241
"Resolver %s of field %s expects parent to be an object, but parent is resolved to %s",
@@ -250,7 +260,7 @@ private function listFieldResolvedValueTypes(
250260
}
251261
}
252262
} elseif ($resolverClassType->getClassName() === Vojtechdobes\GraphQL\PropertyFieldResolver::class) {
253-
foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
263+
foreach ($parentTypes as $parentType) {
254264
if ($parentType->isObject()->yes() === false) {
255265
$errors[] = sprintf(
256266
"Resolver %s of field %s expects parent to be an object, but parent is resolved to %s",
@@ -274,7 +284,7 @@ private function listFieldResolvedValueTypes(
274284
} else {
275285
$expectedParentType = $resolverClassType->getTemplateType(Vojtechdobes\GraphQL\FieldResolver::class, 'TObjectValue');
276286

277-
foreach ($this->listObjectTypeResolvedValueTypes($scope, $schemaServiceOraculum, $objectType) as $parentType) {
287+
foreach ($parentTypes as $parentType) {
278288
if ($expectedParentType->isSuperTypeOf($parentType)->yes() === false) {
279289
$errors[] = sprintf(
280290
"Resolver %s of field %s expects parent to be %s, but parent is resolved to %s",
@@ -303,12 +313,14 @@ private function listFieldResolvedValueTypes(
303313

304314

305315
/**
316+
* @param list<string> $alreadyVisitedFields
306317
* @return list<PHPStan\Type\Type>
307318
*/
308319
private function listObjectTypeResolvedValueTypes(
309320
PHPStan\Analyser\Scope $scope,
310321
SchemaServiceOraculum $schemaServiceOraculum,
311322
string $objectType,
323+
array $alreadyVisitedFields,
312324
): array
313325
{
314326
$result = [];
@@ -318,12 +330,19 @@ private function listObjectTypeResolvedValueTypes(
318330
}
319331

320332
foreach ($schemaServiceOraculum->listFieldsResolvedToObjectType($objectType) as [$parentObjectType, $parentFieldName]) {
333+
$parentField = "{$parentObjectType}.{$parentFieldName}";
334+
335+
if (in_array($parentField, $alreadyVisitedFields, true)) {
336+
continue;
337+
}
338+
321339
[$parentTypes] = $this->listFieldResolvedValueTypes(
322340
$scope,
323341
$schemaServiceOraculum,
324342
$parentObjectType,
325343
$parentFieldName,
326344
$schemaServiceOraculum->getFieldResolverType($parentObjectType, $parentFieldName),
345+
[...$alreadyVisitedFields, $parentField],
327346
);
328347

329348
foreach ($parentTypes as $parentType) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Vojtechdobes\TestsShared\Resolvers;
4+
5+
use Vojtechdobes;
6+
7+
8+
/**
9+
* @implements Vojtechdobes\GraphQL\FieldResolver<null, SelfReference>
10+
*/
11+
final class QueryValidSelfReferenceFieldResolver implements Vojtechdobes\GraphQL\FieldResolver
12+
{
13+
14+
public function resolveField(mixed $objectValue, Vojtechdobes\GraphQL\FieldSelection $field): mixed
15+
{
16+
return new SelfReference(internalSelfReference: null);
17+
}
18+
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Vojtechdobes\TestsShared\Resolvers;
4+
5+
6+
final class SelfReference
7+
{
8+
9+
public function __construct(
10+
public readonly ?SelfReference $internalSelfReference,
11+
) {}
12+
13+
}

tests-shared/schema.graphqls

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type Query {
2020
providerOfValidEntityParentTypeThing: EntityParentType!
2121

2222
rootFieldWithParentBasedResolver: String # should be eg. PropertyFieldResolver
23+
24+
validSelfReference: SelfReference
2325
}
2426

2527
type ArrayType {
@@ -38,3 +40,7 @@ type PersonParentType {
3840
type EntityParentType {
3941
name: String!
4042
}
43+
44+
type SelfReference {
45+
internalSelfReference: SelfReference
46+
}

tests/CustomAdapter.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ public function getFieldResolverProvider(string $schemaName): Vojtechdobes\Graph
5656

5757
'PersonParentType.name' => new Vojtechdobes\TestsShared\Resolvers\PersonParentTypeNameFieldResolver(),
5858
'EntityParentType.name' => new Vojtechdobes\TestsShared\Resolvers\EntityParentTypeNameFieldResolver(),
59+
60+
'Query.validSelfReference' => new Vojtechdobes\TestsShared\Resolvers\QueryValidSelfReferenceFieldResolver(),
61+
'SelfReference.internalSelfReference' => new Vojtechdobes\GraphQL\PropertyFieldResolver(),
5962
]);
6063
}
6164

0 commit comments

Comments
 (0)