Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @bind directive as a GraphQL analogue for Laravel's Route Model Binding #2645

Merged
merged 34 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a7a0952
Setup validation for `@bind` directive schema definitions
remipelhate Dec 21, 2024
b41f64b
Implement simple model binding
remipelhate Dec 22, 2024
145a247
Restructure to separate Nuwave\Lighthouse\Bind namespace
remipelhate Dec 22, 2024
d407329
Test optional model binding
remipelhate Dec 22, 2024
3018a72
Handle optional model bindings
remipelhate Dec 23, 2024
518df4c
Add missing tests for binding models by column
remipelhate Dec 24, 2024
ce51095
Add missing tests for binding models with eager loading
remipelhate Dec 24, 2024
8f981f7
Add missing tests for too many results when resolving a binding
remipelhate Dec 24, 2024
2f46f5e
Handle missing required bindings a validation errors
remipelhate Dec 25, 2024
371749f
Rename `optional` argument to `required` on BindDirective
remipelhate Dec 25, 2024
a03666e
Cleanup exception assetions in BindDirectiveTest
remipelhate Dec 25, 2024
6c6e4e4
Fail schema validation when `@bind` directive is defined on unsupport…
remipelhate Dec 26, 2024
a291ef2
Ensure `@bind` directive can be used multiple times in the same request
remipelhate Dec 27, 2024
aa54257
Add missing tests for binding instances using callable classes
remipelhate Dec 27, 2024
8080404
Add docs
remipelhate Dec 27, 2024
f0ddbeb
Fix PHPStan errors
remipelhate Dec 27, 2024
23dd711
Fix backwards compatibility with Laravel 9
remipelhate Dec 27, 2024
6030bd4
Update changelog
remipelhate Dec 29, 2024
17597f9
review | Remove Laravel version in docs reference
remipelhate Jan 3, 2025
dd87182
review | Update examples in docs
remipelhate Jan 3, 2025
e64fff9
review | Remove function imports
remipelhate Jan 3, 2025
baa7b21
review | Adhere to coding standards
remipelhate Jan 3, 2025
b685580
review | Use placeholder query in BindDirectiveTest
remipelhate Jan 3, 2025
67d3d6d
review | Remove SpyResolver in favour of inspecting resolver args by …
remipelhate Jan 3, 2025
2ef000c
Fix BindDefinition constructor docblock
remipelhate Jan 3, 2025
c543ee9
Fix BindDirective schema validation tests
remipelhate Jan 3, 2025
b52dc07
review | Reuse existing underlying type name resolution
remipelhate Jan 3, 2025
87c0a72
review | Don’t validate value types on which `@bind` is defined
remipelhate Jan 3, 2025
10a7580
review | Add comment for context on handling “too few” vs “too many” …
remipelhate Jan 3, 2025
28cdfb3
review | Update examples in directives docs
remipelhate Jan 3, 2025
541e796
review | Remove redundant docblock from BindDefinition
remipelhate Jan 3, 2025
c6df8c8
review | Cleanup internals in accordance to coding standard
remipelhate Jan 3, 2025
fd530f6
review | Use protected methods and properties instead of private
remipelhate Jan 3, 2025
2ba7be4
Merge branch 'master' into feature/bind-directive
spawnia Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
review | Remove SpyResolver in favour of inspecting resolver args by …
…reference
  • Loading branch information
remipelhate committed Jan 3, 2025
commit 67d3d6da00262a3160f5c6998c7113bdef4710d7
114 changes: 60 additions & 54 deletions tests/Integration/Bind/BindDirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Tests\Utils\Bind\SpyCallableClassBinding;
use Tests\Utils\Models\Company;
use Tests\Utils\Models\User;
use Tests\Utils\Resolvers\SpyResolver;

final class BindDirectiveTest extends DBTestCase
{
Expand Down Expand Up @@ -344,8 +343,11 @@ public function testModelBindingByColumnOnFieldArgument(): void
public function testModelBindingWithEagerLoadingOnFieldArgument(): void
{
$user = factory(User::class)->create();
$resolver = new SpyResolver(fn (mixed $root, array $args) => $args['user']);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return $args['user'];
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -376,10 +378,8 @@ public function testModelBindingWithEagerLoadingOnFieldArgument(): void
],
],
]);
$resolver->assertArgs(function (array $args): void {
$this->assertInstanceOf(User::class, $args['user']);
$this->assertTrue($args['user']->relationLoaded('company'));
});
$this->assertInstanceOf(User::class, $resolverArgs['user']);
$this->assertTrue($resolverArgs['user']->relationLoaded('company'));
}

public function testModelBindingWithTooManyResultsOnFieldArgument(): void
Expand Down Expand Up @@ -414,8 +414,11 @@ public function testModelBindingWithTooManyResultsOnFieldArgument(): void
public function testModelCollectionBindingOnFieldArgument(): void
{
$users = factory(User::class, 2)->create();
$resolver = new SpyResolver(return: true);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return true;
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand All @@ -442,12 +445,10 @@ public function testModelCollectionBindingOnFieldArgument(): void
'removeUsers' => true,
],
]);
$resolver->assertArgs(function (array $args) use ($users): void {
$this->assertArrayHasKey('users', $args);
$this->assertCount($users->count(), $args['users']);
$users->each(function (User $user) use ($args): void {
$this->assertTrue($user->is($args['users'][$user->getKey()]));
});
$this->assertArrayHasKey('users', $resolverArgs);
$this->assertCount($users->count(), $resolverArgs['users']);
$users->each(function (User $user) use ($resolverArgs): void {
$this->assertTrue($user->is($resolverArgs['users'][$user->getKey()]));
});
}

Expand Down Expand Up @@ -483,8 +484,11 @@ public function testMissingModelCollectionBindingOnFieldArgument(): void
public function testMissingOptionalModelCollectionBindingOnFieldArgument(): void
{
$user = factory(User::class)->create();
$resolver = new SpyResolver(return: true);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return true;
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -513,11 +517,9 @@ public function testMissingOptionalModelCollectionBindingOnFieldArgument(): void
'removeUsers' => true,
],
]);
$resolver->assertArgs(function (array $args) use ($user): void {
$this->assertArrayHasKey('users', $args);
$this->assertCount(1, $args['users']);
$this->assertTrue($user->is($args['users'][$user->getKey()]));
});
$this->assertArrayHasKey('users', $resolverArgs);
$this->assertCount(1, $resolverArgs['users']);
$this->assertTrue($user->is($resolverArgs['users'][$user->getKey()]));
}

public function testModelCollectionBindingWithTooManyResultsOnFieldArgument(): void
Expand Down Expand Up @@ -711,8 +713,11 @@ public function testModelBindingByColumnOnInputField(): void
public function testModelBindingWithEagerLoadingOnInputField(): void
{
$user = factory(User::class)->create();
$resolver = new SpyResolver(fn (mixed $root, array $args) => $args['input']['user']);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return $args['input']['user'];
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -745,14 +750,12 @@ public function testModelBindingWithEagerLoadingOnInputField(): void
$response->assertJson([
'data' => [
'user' => [
'id' => $user->id,
'id' => $user->getKey(),
],
],
]);
$resolver->assertArgs(function (array $args): void {
$this->assertInstanceOf(User::class, $args['input']['user']);
$this->assertTrue($args['input']['user']->relationLoaded('company'));
});
$this->assertInstanceOf(User::class, $resolverArgs['input']['user']);
$this->assertTrue($resolverArgs['input']['user']->relationLoaded('company'));
}

public function testModelBindingWithTooManyResultsOnInputField(): void
Expand Down Expand Up @@ -793,8 +796,11 @@ public function testModelBindingWithTooManyResultsOnInputField(): void
public function testModelCollectionBindingOnInputField(): void
{
$users = factory(User::class, 2)->create();
$resolver = new SpyResolver(return: true);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return true;
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -827,13 +833,11 @@ public function testModelCollectionBindingOnInputField(): void
'removeUsers' => true,
],
]);
$resolver->assertArgs(function (array $args) use ($users): void {
$this->assertArrayHasKey('input', $args);
$this->assertArrayHasKey('users', $args['input']);
$this->assertCount($users->count(), $args['input']['users']);
$users->each(function (User $user) use ($args): void {
$this->assertTrue($user->is($args['input']['users'][$user->getKey()]));
});
$this->assertArrayHasKey('input', $resolverArgs);
$this->assertArrayHasKey('users', $resolverArgs['input']);
$this->assertCount($users->count(), $resolverArgs['input']['users']);
$users->each(function (User $user) use ($resolverArgs): void {
$this->assertTrue($user->is($resolverArgs['input']['users'][$user->getKey()]));
});
}

Expand Down Expand Up @@ -875,8 +879,11 @@ public function testMissingModelCollectionBindingOnInputField(): void
public function testMissingOptionalModelCollectionBindingOnInputField(): void
{
$user = factory(User::class)->create();
$resolver = new SpyResolver(return: true);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return true;
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -909,12 +916,10 @@ public function testMissingOptionalModelCollectionBindingOnInputField(): void
'removeUsers' => true,
],
]);
$resolver->assertArgs(function (array $args) use ($user): void {
$this->assertArrayHasKey('input', $args);
$this->assertArrayHasKey('users', $args['input']);
$this->assertCount(1, $args['input']['users']);
$this->assertTrue($user->is($args['input']['users'][$user->getKey()]));
});
$this->assertArrayHasKey('input', $resolverArgs);
$this->assertArrayHasKey('users', $resolverArgs['input']);
$this->assertCount(1, $resolverArgs['input']['users']);
$this->assertTrue($user->is($resolverArgs['input']['users'][$user->getKey()]));
}

public function testModelCollectionBindingWithTooManyResultsOnInputField(): void
Expand Down Expand Up @@ -1264,8 +1269,11 @@ public function testMultipleBindingsInSameRequest(): void
{
$user = factory(User::class)->create();
$company = factory(Company::class)->create();
$resolver = new SpyResolver(return: true);
$this->mockResolver($resolver);
$resolverArgs = [];
$this->mockResolver(function ($_, array $args) use (&$resolverArgs) {
$resolverArgs = $args;
return true;
});
$this->schema = /* @lang GraphQL */ <<<'GRAPHQL'
type User {
id: ID!
Expand Down Expand Up @@ -1300,12 +1308,10 @@ public function testMultipleBindingsInSameRequest(): void
'addUserToCompany' => true,
],
]);
$resolver->assertArgs(function (array $args) use ($user, $company): void {
$this->assertArrayHasKey('user', $args);
$this->assertTrue($user->is($args['user']));
$this->assertArrayHasKey('company', $args);
$this->assertTrue($company->is($args['company']));
});
$this->assertArrayHasKey('user', $resolverArgs);
$this->assertTrue($user->is($resolverArgs['user']));
$this->assertArrayHasKey('company', $resolverArgs);
$this->assertTrue($company->is($resolverArgs['company']));
}

private function assertThrowsMultipleRecordsFoundException(Closure $makeRequest, int $count): void
Expand Down
43 changes: 0 additions & 43 deletions tests/Utils/Resolvers/SpyResolver.php

This file was deleted.