diff --git a/src/Eloquent/ModelSchema.php b/src/Eloquent/ModelSchema.php index b1571631..30d3c339 100644 --- a/src/Eloquent/ModelSchema.php +++ b/src/Eloquent/ModelSchema.php @@ -315,6 +315,10 @@ public function getRelationFields(): Collection $field->accessor($key); } + if (! $field->getWith()) { + $field->with($field->getAccessor()); + } + return $field; }); } diff --git a/src/Fields/Field.php b/src/Fields/Field.php index f7b3723c..60c219d9 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -63,6 +63,11 @@ class Field */ protected $fillable = true; + /** + * @var array + */ + protected $with; + /** * @var bool */ @@ -361,6 +366,27 @@ public function isFillable(): bool return $this->fillable; } + /** + * Set the relations that should be eager loaded. + * + * @param string[]|string $relations + * @return Field + */ + public function with($relations): self + { + $this->with = Arr::wrap($relations); + + return $this; + } + + /** + * Get the relations that should be eager loaded. + */ + public function getWith(): ?array + { + return $this->with; + } + /** * Set if the field is searchable. * diff --git a/src/Queries/Concerns/EagerLoadRelationships.php b/src/Queries/Concerns/EagerLoadRelationships.php index 0583ab9b..7430f0bc 100644 --- a/src/Queries/Concerns/EagerLoadRelationships.php +++ b/src/Queries/Concerns/EagerLoadRelationships.php @@ -2,9 +2,9 @@ namespace Bakery\Queries\Concerns; -use Bakery\Fields\Field; use Bakery\Eloquent\ModelSchema; use Bakery\Support\TypeRegistry; +use Bakery\Fields\PolymorphicField; use Illuminate\Database\Eloquent\Builder; trait EagerLoadRelationships @@ -17,39 +17,33 @@ trait EagerLoadRelationships /** * Eager load the relations. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $fields - * @param ModelSchema $schema - * @param string $path + * @param Builder $query + * @param bool[]|array $fields + * @param ModelSchema $schema + * @param string $path */ protected function eagerLoadRelations(Builder $query, array $fields, ModelSchema $schema, $path = '') { - $relations = $schema->getRelations()->keys()->toArray(); - - foreach ($fields as $key => $field) { - if (in_array($key, $relations)) { - $relationField = $this->getRelationFieldByKey($key, $schema); - $column = $relationField->getAccessor(); - $eagerLoadPath = $path ? $path.'.'.$column : $column; - $query->with($eagerLoadPath); - $related = $schema->getModel()->{$column}()->getRelated(); + foreach ($fields as $key => $subFields) { + $field = $schema->getFieldByKey($key); + + // Skip if this is not a defined field. + if (! $field) { + continue; + } + + $with = array_map(function ($with) use ($path) { + return $path ? "{$path}.{$with}" : $with; + }, $field->getWith() ?? []); + + $query->with($with); + + if ($field->isRelationship() && ! $field instanceof PolymorphicField) { + $accessor = $field->getAccessor(); + $related = $schema->getModel()->{$accessor}()->getRelated(); $relatedSchema = $this->registry->getSchemaForModel($related); - $this->eagerLoadRelations($query, $field, $relatedSchema, $eagerLoadPath); + $this->eagerLoadRelations($query, $subFields, $relatedSchema, $path ? "{$path}.{$accessor}" : $accessor); } } } - - /** - * Get a relation field by it's key. - * - * @param string $key - * @param ModelSchema $schema - * @return Field - */ - protected function getRelationFieldByKey(string $key, ModelSchema $schema): Field - { - return $schema->getRelationFields()->first(function (Field $field, $relation) use ($key) { - return $relation === $key; - }); - } } diff --git a/tests/Feature/CollectionQueryTest.php b/tests/Feature/CollectionQueryTest.php index 7ab55ca0..fb0a3916 100644 --- a/tests/Feature/CollectionQueryTest.php +++ b/tests/Feature/CollectionQueryTest.php @@ -810,6 +810,29 @@ public function it_eager_loads_sibling_relationships() $this->assertCount(5, DB::getQueryLog()); } + /** @test */ + public function it_eager_loads_fields_that_explicitly_declare_with() + { + factory(Article::class, 25)->create(); + + $query = ' + query { + articles { + items { + authorName + } + } + } + '; + + DB::enableQueryLog(); + $response = $this->graphql($query); + DB::disableQueryLog(); + + $response->assertJsonStructure(['data' => ['articles' => ['items' => [['authorName']]]]]); + $this->assertCount(3, DB::getQueryLog()); + } + /** @test */ public function it_cannot_query_models_that_are_not_indexable() { diff --git a/tests/Fixtures/Schemas/ArticleSchema.php b/tests/Fixtures/Schemas/ArticleSchema.php index c7acbcde..b58fae04 100644 --- a/tests/Fixtures/Schemas/ArticleSchema.php +++ b/tests/Fixtures/Schemas/ArticleSchema.php @@ -16,6 +16,9 @@ public function fields(): array 'slug' => Field::string()->unique(), 'title' => Field::string()->searchable(), 'name' => Field::string()->accessor('title')->searchable()->readOnly(), + 'authorName' => Field::string()->with('user')->readOnly()->resolve(function (Article $article) { + return $article->user->name; + }), 'created_at' => Field::type('Timestamp')->readOnly(), 'createdAt' => Field::type('Timestamp')->accessor('created_at')->readOnly(), ];