diff --git a/src/Schema/Services/NestedInputBuilder.php b/src/Schema/Services/NestedInputBuilder.php index 6a4f989e8..cd8dc2d99 100644 --- a/src/Schema/Services/NestedInputBuilder.php +++ b/src/Schema/Services/NestedInputBuilder.php @@ -26,12 +26,6 @@ class NestedInputBuilder const SELF_REFERENTIAL = '--self--'; - /** - * @var int - * @config - */ - private static $max_nesting = 3; - /** * @var string * @config @@ -119,17 +113,15 @@ public function populateSchema() /** * @param Type $type - * @param int $level * @return array * @throws SchemaBuilderException */ - protected function buildAllFieldsConfig(Type $type, int $level = 0): array + protected function buildAllFieldsConfig(Type $type): array { $existing = $this->fetch($type->getName()); if ($existing) { return $existing; } - $level++; $map = []; foreach ($type->getFields() as $fieldObj) { if (!$this->shouldAddField($type, $fieldObj)) { @@ -138,21 +130,31 @@ protected function buildAllFieldsConfig(Type $type, int $level = 0): array $namedType = $fieldObj->getNamedType(); $nestedType = $this->schema->getTypeOrModel($namedType); if ($nestedType) { - if ($level > $this->config()->get('max_nesting')) { - continue; - } + $seen = $this->schema->getState()->get([ + static::class, + 'seenConnections', + $type->getName(), + $fieldObj->getName() + ]); // Prevent stupid recursion in self-referential relationships, e.g. Parent if ($namedType === $type->getName()) { $map[$fieldObj->getName()] = self::SELF_REFERENTIAL; + } elseif ($seen) { + continue; } else { - $map[$fieldObj->getName()] = $this->buildAllFieldsConfig($nestedType, $level); + $this->schema->getState()->set([ + static::class, + 'seenConnections', + $type->getName(), + $fieldObj->getName() + ], true); + $map[$fieldObj->getName()] = $this->buildAllFieldsConfig($nestedType); } } else { $map[$fieldObj->getName()] = true; } } $this->persist($type->getName(), $map); - return $map; } diff --git a/tests/Schema/Services/NestedInputBuilderTest.php b/tests/Schema/Services/NestedInputBuilderTest.php index 88d1540bf..ed7bb0408 100644 --- a/tests/Schema/Services/NestedInputBuilderTest.php +++ b/tests/Schema/Services/NestedInputBuilderTest.php @@ -76,6 +76,38 @@ public function testNestedInputBuilder() ], $schema); } + /** + * @throws SchemaBuilderException + */ + public function testNestedInputBuilderBuildsCyclicFilterFields() + { + $schema = (new TestSchemaBuilder())->boot('filterfieldbuilder'); + $schema + ->addModelbyClassName(FakeProductPage::class, function (ModelType $model) { + $model->addField('title'); + $model->addField('products', ['plugins' => ['filter' => true]]); + }) + ->addModelbyClassName(FakeProduct::class, function (ModelType $model) { + $model->addField('title'); + $model->addField('parent'); + $model->addField('reviews'); + $model->addField('relatedProducts'); + }) + ->addModelbyClassName(FakeReview::class, function (ModelType $model) { + $model->addField('content'); + $model->addField('author'); + }) + ->addModelbyClassName(Member::class, function (ModelType $model) { + $model->addField('firstName'); + }); + $schema->createStoreableSchema(); + $filterType = $schema->getType('FakeReviewFilterFields'); + $this->assertNotNull($filterType, "Type FakeReviewFilterFields not found in schema"); + $filterFieldObj = $filterType->getFieldByName('author'); + $this->assertNotNull($filterFieldObj, "Field author not found on {$filterType->getName()}"); + $this->assertEquals('MemberFilterFields', $filterFieldObj->getType()); + } + private function assertSchema(array $graph, Schema $schema) { foreach ($graph as $typeName => $fields) {