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

WIP - Add wildcard to inject directive #2280

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eedb6ea
WIP - Add wildcard to inject directive
andershagbard Jan 25, 2023
78a92ce
Insert lang doc
andershagbard Jan 29, 2023
7226a09
Finish test
andershagbard Jan 29, 2023
acb91bb
Add type
andershagbard Jan 29, 2023
5f1c74b
Rename method
andershagbard Jan 29, 2023
b607c28
Improve docs
andershagbard Jan 29, 2023
8ff0340
Update def
andershagbard Jan 29, 2023
8b4ffec
Update CHANGELOG.md
andershagbard Jan 29, 2023
eea6145
Update src/Schema/Directives/InjectDirective.php
andershagbard Jan 29, 2023
d20a332
Throw error if not array
andershagbard Jan 29, 2023
60f8834
Update docs
andershagbard Jan 29, 2023
e163ae7
Change typo
andershagbard Jan 29, 2023
4a49468
Update example
andershagbard Jan 29, 2023
4fc5b9c
Update docs/master/api-reference/directives.md
andershagbard Jan 30, 2023
95299b0
Add test case with non array target
andershagbard Feb 1, 2023
86ee821
Implement ArrayAccess and use data_set
andershagbard Feb 1, 2023
9f17d55
Merge branch 'inject-wildcard' of https://github.com/andershagbard/li…
andershagbard Feb 1, 2023
503347c
Implement ArrayAccess
andershagbard Feb 1, 2023
3ae756e
Set new self if empty value
andershagbard Feb 1, 2023
7bb4ffb
Add IteratorAggregate
andershagbard Feb 2, 2023
36e3e0e
Remove check
andershagbard Feb 2, 2023
d0a1cd7
Remove method
andershagbard Feb 2, 2023
829172f
Add failing test case
andershagbard Feb 2, 2023
a42c22e
Revert "Add failing test case"
andershagbard Feb 3, 2023
42b907c
Fix test
andershagbard Feb 3, 2023
a0e55d8
Update
andershagbard Feb 3, 2023
ead7514
Fix
andershagbard Feb 3, 2023
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

- Allow to set value using asterisks as a wildcard in `@inject` https://github.com/nuwave/lighthouse/pull/2280

### Changed

- Pass resolver arguments to `FieldBuilderDirective::handleFieldBuilder()` https://github.com/nuwave/lighthouse/pull/2234
Expand Down
24 changes: 24 additions & 0 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,7 @@ directive @inject(
The target name of the argument into which the value is injected.
You can use dot notation to set the value at arbitrary depth
within the incoming argument.
Use an asterisk `*` as a path segment where the value of the argument is a list to be traversed.
"""
name: String!
) repeatable on FIELD_DEFINITION
Expand Down Expand Up @@ -1756,6 +1757,29 @@ type Mutation {
}
```

If you have an array you need to inject a value into, you can use an asterisk `*`.

```graphql
type Mutation {
updateUser(input: UpdateUserInput!): Task
@update
@inject(context: "user.id", name: "input.tasks.create.*.user_id")
}

input UpdateUserInput {
id: ID!
tasks: UpdateTasksHasManyInput
}

input UpdateTasksHasManyInput {
create: [CreateTaskInput!]
}

input CreateTaskInput {
title: String!
}
```

## @interface

```graphql
Expand Down
40 changes: 39 additions & 1 deletion src/Execution/Arguments/Argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Support\Collection;

class Argument
class Argument implements \ArrayAccess, \IteratorAggregate
{
/**
* The value given by the client.
Expand Down Expand Up @@ -85,4 +85,42 @@ protected static function toPlainRecursive($value)

return $value;
}

public function offsetExists(mixed $offset): bool
{
$argument = $this;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not get why you keep aliasing $this - why not just it directly?


return isset($argument->value[$offset]);
}

public function &offsetGet(mixed $offset): mixed
{
$argument = $this;

return $argument->value[$offset];
}

public function offsetSet(mixed $offset, mixed $value): void
{
$argument = $this;

$argumentSet = new ArgumentSet();
$argumentSet[(string) $offset] = $value;

$argument->value = $argumentSet;
}

public function offsetUnset(mixed $offset): void
{
$argument = $this;

unset($argument[$offset]);
}

public function getIterator(): \ArrayIterator
{
$value = $this->value;

return new \ArrayIterator($value);
Comment on lines +122 to +124
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$value = $this->value;
return new \ArrayIterator($value);
return new \ArrayIterator($this->value);

}
}
64 changes: 41 additions & 23 deletions src/Execution/Arguments/ArgumentSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Nuwave\Lighthouse\Execution\Arguments;

class ArgumentSet
class ArgumentSet implements \ArrayAccess, \IteratorAggregate
{
/**
* An associative array from argument names to arguments.
Expand Down Expand Up @@ -61,34 +61,15 @@ public function has(string $key): bool

/**
* Add a value at the dot-separated path.
*
* Works just like @see \Illuminate\Support\Arr::add().
* Asterisks may be used to indicate wildcards.
*
* @param mixed $value any value to inject
*/
public function addValue(string $path, $value): self
public function addValue(string $path, mixed $value): self
{
$argumentSet = $this;
$keys = explode('.', $path);

while (count($keys) > 1) {
$key = array_shift($keys);

// If the key doesn't exist at this depth, we will just create an empty ArgumentSet
// to hold the next value, allowing us to create the ArgumentSet to hold a final
// value at the correct depth. Then we'll keep digging into the ArgumentSet.
if (! isset($argumentSet->arguments[$key])) {
$argument = new Argument();
$argument->value = new self();
$argumentSet->arguments[$key] = $argument;
}

$argumentSet = $argumentSet->arguments[$key]->value;
}

$argument = new Argument();
$argument->value = $value;
$argumentSet->arguments[array_shift($keys)] = $argument;
data_set($argumentSet, $path, $value);

return $this;
}
Expand All @@ -102,4 +83,41 @@ public function argumentsWithUndefined(): array
{
return array_merge($this->arguments, $this->undefined);
}

public function offsetExists(mixed $offset): bool
{
$argumentSet = $this;

return isset($argumentSet->arguments[$offset]);
}

public function &offsetGet(mixed $offset): Argument
{
$argumentSet = $this;

return $argumentSet->arguments[$offset];
}

public function offsetSet(mixed $offset, mixed $value): void
{
$argumentSet = $this;

$argument = new Argument();
$argument->value = $value;
$argumentSet->arguments[(string) $offset] = $argument;
}

public function offsetUnset(mixed $offset): void
{
$argumentSet = $this;

unset($argumentSet->arguments[$offset]);
}

public function getIterator(): \ArrayIterator
{
$arguments = $this->arguments;

return new \ArrayIterator($arguments);
}
}
1 change: 1 addition & 0 deletions src/Schema/Directives/InjectDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static function definition(): string
The target name of the argument into which the value is injected.
You can use dot notation to set the value at arbitrary depth
within the incoming argument.
Use an asterisk `*` as a path segment where the value of the argument is a list to be traversed.
"""
name: String!
) repeatable on FIELD_DEFINITION
Expand Down
150 changes: 149 additions & 1 deletion tests/Integration/Schema/Directives/InjectDirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public function testCreateFromInputObjectWithDeepInjection(): void
$user = factory(User::class)->create();
$this->be($user);

$this->schema .= '
$this->schema .= /* @lang GraphQL */ '
type Task {
id: ID!
name: String!
Expand Down Expand Up @@ -56,4 +56,152 @@ public function testCreateFromInputObjectWithDeepInjection(): void
],
]);
}

public function testCreateFromInputObjectWithWildcardInjection(): void
{
$user = factory(User::class)->create();
$this->be($user);

$this->schema .= /* @lang GraphQL */ '
type Task {
id: ID!
name: String!
user: User @belongsTo
}

type User {
id: ID
tasks: [Task!] @hasmany
}

type Mutation {
updateUser(input: UpdateUserInput @spread): User
@update
@inject(context: "user.id", name: "tasks.create.*.user_id")
}

input UpdateUserInput {
id: ID!
tasks: CreateTaskInputMany
}

input CreateTaskInputMany {
create: [CreateTaskInput!]
}

input CreateTaskInput {
name: String
}
';

$this->graphQL('
mutation ($input: UpdateUserInput!) {
updateUser(input: $input) {
tasks {
id
name
user {
id
}
}
}
}
', [
'input' => [
'id' => $user->getKey(),
'tasks' => [
'create' => [
[ 'name' => 'foo' ],
[ 'name' => 'bar' ],
],
],
],
])->assertJson([
'data' => [
'updateUser' => [
'tasks' => [
[
'id' => '1',
'name' => 'foo',
'user' => [
'id' => '1',
],
],
[
'id' => '2',
'name' => 'bar',
'user' => [
'id' => '1',
],
],
],
],
],
]);
}


public function testWillRejectValuesNotPlacedAtArrayWithWildcardInjection(): void
{
$user = factory(User::class)->create();
$this->be($user);

$this->schema .= /* @lang GraphQL */ '
type Task {
id: ID!
name: String!
user: User @belongsTo
}

type User {
id: ID
tasks: [Task!] @hasmany
}

type Mutation {
updateUser(input: UpdateUserInput @spread): User
@update
@inject(context: "user.id", name: "tasks.create.*.user_id")
}

input UpdateUserInput {
id: ID!
tasks: CreateTaskInputMany
}

input CreateTaskInputMany {
create: [CreateTaskInput!]
}

input CreateTaskInput {
name: String
}
';

$this->graphQL('
mutation ($input: UpdateUserInput!) {
updateUser(input: $input) {
id
tasks {
id
name
user {
id
}
}
}
}
', [
'input' => [
'id' => $user->getKey(),
],
])->assertJson([
'data' => [
'updateUser' => [
'id' => $user->getKey(),
'tasks' => [],
],
],
]);
}
}