Skip to content

Commit

Permalink
Relationship Resolvers (#132)
Browse files Browse the repository at this point in the history
* wip

* Apply fixes from StyleCI

[ci skip] [skip ci]

* Ignore phpunit cache

* Tests

* Apply fixes from StyleCI

[ci skip] [skip ci]
  • Loading branch information
robertvansteen authored Dec 19, 2019
1 parent f77c455 commit c91f880
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ composer.lock
.DS_Store
.idea/
.vscode/
.phpunit.result.cache
4 changes: 4 additions & 0 deletions src/Eloquent/ModelSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ public function getRelations(): Collection

return $field;
})->map(function (Field $field) {
if ($field instanceof EloquentField) {
return $field->getRelation($this->getModel());
}

$accessor = $field->getAccessor();

Utils::invariant(
Expand Down
4 changes: 2 additions & 2 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public static function float(): Fields\Field
return self::getRegistry()->field(self::getRegistry()->float());
}

public static function model(string $class): Fields\Field
public static function model(string $class): Fields\EloquentField
{
return self::getRegistry()->eloquent($class);
}

public static function collection(string $class): Fields\Field
public static function collection(string $class): Fields\EloquentField
{
return self::model($class)->list();
}
Expand Down
62 changes: 62 additions & 0 deletions src/Fields/EloquentField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

namespace Bakery\Fields;

use Bakery\Utils\Utils;
use Bakery\Eloquent\ModelSchema;
use Bakery\Support\TypeRegistry;
use Bakery\Types\Definitions\RootType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;

class EloquentField extends Field
{
Expand All @@ -18,6 +21,16 @@ class EloquentField extends Field
*/
protected $inverseRelationName;

/**
* @var callable|null
*/
protected $relationResolver;

/**
* @var callable|null
*/
protected $collectionResolver;

/**
* EloquentField constructor.
*
Expand Down Expand Up @@ -79,4 +92,53 @@ public function name(): string
{
return $this->getModelClass()->getTypename();
}

/**
* Set a custom relation resolver.
*/
public function relation(callable $resolver): self
{
$this->relationResolver = $resolver;

return $this;
}

/**
* Return the Eloquent relation.
*/
public function getRelation(Model $model): Relation
{
if ($resolver = $this->relationResolver) {
return $resolver($model);
}

$accessor = $this->getAccessor();

Utils::invariant(
method_exists($model, $accessor),
'Relation "'.$accessor.'" is not defined on "'.get_class($model).'".'
);

return $model->{$accessor}();
}

/**
* Determine if the field has a relation resolver.
*/
public function hasRelationResolver(): bool
{
return isset($this->relationResolver);
}

/**
* Get the result of the field.
*/
public function getResult(Model $model)
{
if ($resolver = $this->relationResolver) {
return $resolver($model)->get();
}

return $model->{$this->getAccessor()};
}
}
10 changes: 9 additions & 1 deletion src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public function description(string $description): self
}

/**
* Return the name of the database column associated with this field.
* Return the name of the database column or method associated with this field.
*
* @return string|null
*/
Expand Down Expand Up @@ -524,6 +524,14 @@ public function resolve(callable $resolver): self
return $this;
}

/**
* Get the resolver.
*/
public function getResolver(): ?callable
{
return $this->resolver;
}

/**
* Resolve the field.
*
Expand Down
11 changes: 8 additions & 3 deletions src/Queries/Concerns/EagerLoadRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Bakery\Queries\Concerns;

use Bakery\Eloquent\ModelSchema;
use Bakery\Fields\EloquentField;
use Bakery\Support\TypeRegistry;
use Bakery\Fields\PolymorphicField;
use Illuminate\Database\Eloquent\Builder;

trait EagerLoadRelationships
Expand Down Expand Up @@ -32,15 +32,20 @@ protected function eagerLoadRelations(Builder $query, array $fields, ModelSchema
continue;
}

// If a custom relation resolver is provided we cannot eager load.
if ($field instanceof EloquentField && $field->hasRelationResolver()) {
continue;
}

$with = array_map(function ($with) use ($path) {
return $path ? "{$path}.{$with}" : $with;
}, $field->getWith() ?? []);

$query->with($with);

if ($field->isRelationship() && ! $field instanceof PolymorphicField) {
if ($field instanceof EloquentField) {
$accessor = $field->getAccessor();
$related = $schema->getModel()->{$accessor}()->getRelated();
$related = $field->getRelation($schema->getModel())->getRelated();
$relatedSchema = $this->registry->getSchemaForModel($related);
$this->eagerLoadRelations($query, $subFields, $relatedSchema, $path ? "{$path}.{$accessor}" : $accessor);
}
Expand Down
16 changes: 0 additions & 16 deletions src/Queries/SingleEntityQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ class SingleEntityQuery extends EloquentQuery
{
/**
* Get the name of the query.
*
* @return string
*/
public function name(): string
{
Expand All @@ -29,8 +27,6 @@ public function name(): string

/**
* The return type of the query.
*
* @return \Bakery\Types\Definitions\RootType
*/
public function type(): RootType
{
Expand All @@ -39,8 +35,6 @@ public function type(): RootType

/**
* The arguments for the Query.
*
* @return array
*/
public function args(): array
{
Expand All @@ -49,12 +43,6 @@ public function args(): array

/**
* Resolve the EloquentQuery.
*
* @param Arguments $args
* @param mixed $root
* @param mixed $context
* @param \GraphQL\Type\Definition\ResolveInfo $info
* @return \Illuminate\Database\Eloquent\Model|null ?Model
*/
public function resolve(Arguments $args, $root, $context, ResolveInfo $info): ?Model
{
Expand Down Expand Up @@ -83,10 +71,6 @@ public function resolve(Arguments $args, $root, $context, ResolveInfo $info): ?M

/**
* Query by the arguments supplied to the query.
*
* @param Builder $query
* @param Arguments $args
* @return Builder
*/
protected function queryByArgs(Builder $query, Arguments $args): Builder
{
Expand Down
2 changes: 1 addition & 1 deletion src/Types/EloquentMutationInputType.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected function getFieldsForRelation(string $relation, EloquentField $root):
$fields = collect();
$root->setRegistry($this->registry);
$inputType = 'Create'.$root->getName().'Input';
$relationship = $this->model->{$root->getAccessor()}();
$relationship = $root->getRelation($this->model);

if ($root->isList()) {
$name = Str::singular($relation).'Ids';
Expand Down
22 changes: 10 additions & 12 deletions src/Types/EntityType.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ protected function getRelationFields(): Collection
protected function getFieldsForRelation(string $key, EloquentField $field): Collection
{
$fields = collect();
$relationship = $this->model->{$field->getAccessor()}();
$relationship = $field->getRelation($this->model);

if ($field->isList()) {
$fields = $fields->merge($this->getPluralRelationFields($key, $field));
Expand Down Expand Up @@ -126,12 +126,10 @@ protected function getPluralRelationFields(string $key, EloquentField $field): C

$field = $field->args([
'filter' => $this->registry->type($field->getName().'Filter')->nullable(),
])->resolve(function (Model $model, string $accessor, Arguments $args) {
$relation = $model->{$accessor}();
])->resolve(function (Model $model, string $accessor, Arguments $args) use ($field) {
$relation = $field->getRelation($model);

$result = $args->isEmpty() ? $model->{$accessor} : $this->getRelationQuery($relation, $args)->get();

return $result;
return $args->isEmpty() ? $field->getResult($model) : $this->getRelationQuery($relation, $args)->get();
});

$fields->put($key, $field);
Expand All @@ -142,10 +140,10 @@ protected function getPluralRelationFields(string $key, EloquentField $field): C
->args($field->getArgs())
->nullable($field->isNullable())
->viewPolicy($field->getViewPolicy())
->resolve(function (Model $model, string $accessor, Arguments $args) {
$relation = $model->{$accessor}();
->resolve(function (Model $model, string $accessor, Arguments $args) use ($field) {
$relation = $field->getRelation($model);

$result = $args->isEmpty() ? $model->{$accessor} : $this->getRelationQuery($relation, $args)->get();
$result = $args->isEmpty() ? $field->getResult($model) : $this->getRelationQuery($relation, $args)->get();

return $result->pluck($relation->getRelated()->getKeyName());
})
Expand All @@ -155,10 +153,10 @@ protected function getPluralRelationFields(string $key, EloquentField $field): C
->accessor($field->getAccessor())
->nullable($field->isNullable())
->viewPolicy($field->getViewPolicy())
->resolve(function (Model $model, string $accessor, Arguments $args) {
$relation = $model->{$accessor};
->resolve(function (Model $model, string $accessor, Arguments $args) use ($field) {
$relation = $field->getRelation($model);

$result = $args->isEmpty() ? $model->{$accessor} : $this->getRelationQuery($relation, $args);
$result = $args->isEmpty() ? $field->getResult($model) : $this->getRelationQuery($relation, $args)->get();

return $result->count();
})
Expand Down
28 changes: 28 additions & 0 deletions tests/Feature/EntityQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,34 @@ public function it_can_filter_a_relation()
$response->assertJsonFragment(['body' => 'Cool story'])->assertJsonMissing(['body' => 'Boo!']);
}

/** @test */
public function it_uses_custom_resolver_for_relationships()
{
$user = factory(User::class)->create();
$article = factory(Article::class)->create(['user_id' => $user->id]);

factory(Comment::class)->create(['commentable_id' => $article->id, 'author_id' => $user->id, 'body' => 'This is my comment.']);
factory(Comment::class)->create(['commentable_id' => $article->id, 'body' => 'This is a comment from someone else.']);

$query = '
query {
article {
id
myComments {
id
body
}
myCommentsCount
myCommentIds
}
}
';

$this->graphql($query)
->assertJsonFragment(['body' => 'This is my comment.'])
->assertJsonMissing(['body' => 'This is a comment from someone else.']);
}

/** @test */
public function it_shows_the_count_for_many_relationships()
{
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/Schemas/ArticleSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public function relations(): array
'tags' => Field::collection(TagSchema::class),
'comments' => Field::collection(CommentSchema::class),
'remarks' => Field::collection(CommentSchema::class)->accessor('comments'),
'myComments' => Field::collection(CommentSchema::class)->relation(function (Article $article) {
return $article->comments()->where('author_id', optional(auth()->user())->getAuthIdentifier());
}),
];
}
}

0 comments on commit c91f880

Please sign in to comment.