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

Injection of ResolveInfo into auto-guessed & arguments transformer #728

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions docs/annotations/arguments-transformer.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,47 @@ class RootMutation {
```

So, the resolver (the `createUser` method) will receive an instance of the class `UserRegisterInput` instead of an array of data.

### Special arguments

The arguments transformer is also able to transform some `special arguments` (see auto-guessing) :

- If the type `@info` is specified, it will be replaced by the current `ResolveInfo`

You can use it this way :

```php
/**
* @GQL\Type
*/
class RootQuery {
/**
* @GQL\Field(
* type="[User]",
* args={
* @GQL\Arg(name="ids", type="[Int]"),
* @GQL\Arg(name="info", type="@info")
* },
* resolve="@=call(service('UserRepository').getUser, arguments({ids: '[Int]', info: '@info'}, arg))"
* )
*/
public $getUsers;
}
```

or with auto-guessing

```php
/**
* @GQL\Provider
*/
class UsersResolver {
/**
* @GQL\Query(type="[User]")
*/
public function getUser(int $id, ResolveInfo $info):array
{
...
}
}
```
28 changes: 28 additions & 0 deletions docs/annotations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,34 @@ The GraphQL arguments will be auto-guessed as:
- `@Arg(name="input", type="MyInput!")` (The input type corresponding to the `MyInputClass` will be used).
- `@Arg(name="limit", type="Int", default = 10)`


#### Special arguments

In conjonction with the `Arguments transformer`, some arguments with type-hinted classes can also be auto-guessed.
The class `GraphQL\Type\Definition\ResolveInfo` will be auto-guessed as the `ResolveInfo` for the resolver.
Special arguments are not exposed as GraphQL argument as there are only used internally by the arguments transformer.

You can inject it as follow:
```php
/**
* @GQL\Type
*/
class MyType {
/**
* @GQL\Field(type="[String]!")
*/
public function getSomething(int $amount, ResolveInfo $info) {
...
}
}
```

The GraphQL arguments will only be:

- `@Arg(name="amount", type="Int!")`



### Limitation of auto-guessing:

When trying to auto-guess a type or args based on PHP Reflection (from type hinted method parameters or type hinted return value), there is a limitation.
Expand Down
35 changes: 23 additions & 12 deletions src/Config/Parser/AnnotationParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\OneToOne;
use Exception;
use GraphQL\Type\Definition\ResolveInfo;
use Overblog\GraphQLBundle\Annotation as GQL;
use Overblog\GraphQLBundle\Config\Parser\Annotation\GraphClass;
use Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface;
Expand Down Expand Up @@ -64,6 +65,11 @@ class AnnotationParser implements PreParserInterface
private const VALID_INPUT_TYPES = [self::GQL_SCALAR, self::GQL_ENUM, self::GQL_INPUT];
private const VALID_OUTPUT_TYPES = [self::GQL_SCALAR, self::GQL_TYPE, self::GQL_INTERFACE, self::GQL_UNION, self::GQL_ENUM];

/** Allow injection of special arguments in arguments transformer */
private const SPECIAL_ARGUMENTS = [
ResolveInfo::class => '@info',
];

/**
* {@inheritdoc}
*
Expand Down Expand Up @@ -519,7 +525,8 @@ private static function getTypeFieldConfigurationFromReflector(GraphClass $graph
}

if (!empty($args)) {
$fieldConfiguration['args'] = $args;
/** Remove special arguments **/
$fieldConfiguration['args'] = array_filter($args, fn ($arg) => !in_array($arg['type'], self::SPECIAL_ARGUMENTS));
}

$fieldName = $fieldAnnotation->name ?: $fieldName;
Expand Down Expand Up @@ -906,19 +913,23 @@ private static function guessArgs(ReflectionMethod $method): array
throw new InvalidArgumentException(sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed as there is not type hint.', $index + 1, $parameter->getName(), $method->getName()));
}

try {
// @phpstan-ignore-next-line
$gqlType = self::resolveGraphQLTypeFromReflectionType($parameter->getType(), self::VALID_INPUT_TYPES, $parameter->isDefaultValueAvailable());
} catch (Exception $e) {
throw new InvalidArgumentException(sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed : %s".', $index + 1, $parameter->getName(), $method->getName(), $e->getMessage()));
}
if ($parameter->getClass() && isset(self::SPECIAL_ARGUMENTS[$parameter->getClass()->getName()])) {
$argumentConfig = ['type' => self::SPECIAL_ARGUMENTS[$parameter->getClass()->getName()]];
} else {
try {
// @phpstan-ignore-next-line
$gqlType = self::resolveGraphQLTypeFromReflectionType($parameter->getType(), self::VALID_INPUT_TYPES, $parameter->isDefaultValueAvailable());
} catch (Exception $e) {
throw new InvalidArgumentException(sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed : %s".', $index + 1, $parameter->getName(), $method->getName(), $e->getMessage()));
}

$argumentConfig = [];
if ($parameter->isDefaultValueAvailable()) {
$argumentConfig['defaultValue'] = $parameter->getDefaultValue();
}
$argumentConfig = [];
if ($parameter->isDefaultValueAvailable()) {
$argumentConfig['defaultValue'] = $parameter->getDefaultValue();
}

$argumentConfig['type'] = $gqlType;
$argumentConfig['type'] = $gqlType;
}

$arguments[$parameter->getName()] = $argumentConfig;
}
Expand Down
9 changes: 8 additions & 1 deletion src/Transformer/ArgumentsTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,14 @@ public function getArguments(array $mapping, $data, ResolveInfo $info)

foreach ($mapping as $name => $type) {
try {
$value = $this->getInstanceAndValidate($type, $data[$name], $info, $name);
switch ($type) {
case '@info':
$value = $info;
break;
default:
$value = $this->getInstanceAndValidate($type, $data[$name], $info, $name);
break;
}
$args[] = $value;
} catch (InvalidArgumentError $exception) {
$exceptions[] = $exception;
Expand Down
7 changes: 6 additions & 1 deletion tests/Config/Parser/AnnotationParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ public function testTypes(): void
'args' => ['jediOnly' => ['type' => 'Boolean', 'description' => 'Only Jedi victims']],
'resolve' => '@=call(value.getVictims, arguments({jediOnly: "Boolean"}, args))',
],
'oldMasters' => [
'type' => '[Character]',
'args' => ['jediOnly' => ['type' => 'Boolean', 'description' => 'Only Jedi victims']],
'resolve' => '@=call(value.getOldMasters, arguments({info: "@info", jediOnly: "Boolean"}, args))',
],
],
]);

Expand Down Expand Up @@ -333,7 +338,7 @@ public function testArgsAndReturnGuessing(): void
'away' => ['type' => 'Boolean', 'defaultValue' => false],
'maxDistance' => ['type' => 'Float', 'defaultValue' => null],
],
'resolve' => '@=call(value.getCasualties, arguments({areaId: "Int!", raceId: "String!", dayStart: "Int", dayEnd: "Int", nameStartingWith: "String", planet: "PlanetInput", away: "Boolean", maxDistance: "Float"}, args))',
'resolve' => '@=call(value.getCasualties, arguments({areaId: "Int!", raceId: "String!", dayStart: "Int", dayEnd: "Int", nameStartingWith: "String", planet: "PlanetInput", info: "@info", away: "Boolean", maxDistance: "Float"}, args))',
'complexity' => '@=childrenComplexity * 5',
],
],
Expand Down
2 changes: 2 additions & 0 deletions tests/Config/Parser/fixtures/annotations/Type/Battle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Overblog\GraphQLBundle\Tests\Config\Parser\fixtures\annotations\Type;

use GraphQL\Type\Definition\ResolveInfo;
use Overblog\GraphQLBundle\Annotation as GQL;
use Overblog\GraphQLBundle\Tests\Config\Parser\fixtures\annotations\Input\Planet;

Expand All @@ -27,6 +28,7 @@ public function getCasualties(
int $dayEnd = null,
string $nameStartingWith = '',
Planet $planet = null,
ResolveInfo $info = null,
bool $away = false,
float $maxDistance = null
): ?int {
Expand Down
16 changes: 16 additions & 0 deletions tests/Config/Parser/fixtures/annotations/Type/Sith.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Overblog\GraphQLBundle\Tests\Config\Parser\fixtures\annotations\Type;

use GraphQL\Type\Definition\ResolveInfo;
use Overblog\GraphQLBundle\Annotation as GQL;
use Overblog\GraphQLBundle\Tests\Config\Parser\fixtures\annotations\Union\Killable;

Expand Down Expand Up @@ -45,4 +46,19 @@ public function getVictims(bool $jediOnly = false): array
{
return [];
}

/**
* @GQL\Field(
* type="[Character]",
* name="oldMasters",
* args={
* @GQl\Arg(name="info", type="@info"),
* @GQL\Arg(name="jediOnly", type="Boolean", description="Only Jedi victims", default=false)
* }
* )
*/
public function getOldMasters(ResolveInfo $info, bool $jediOnly = false): array
{
return [];
}
}
10 changes: 10 additions & 0 deletions tests/Transformer/ArgumentsTransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ public function testPopulating(): void
$this->assertEquals('enum1', $res->field3->value);
}

public function testSpecialArguments(): void
{
$transformer = $this->getTransformer([]);
$info = $this->getResolveInfo(self::getTypes());
$res = $transformer->getArguments(['p1' => 'Int!', 'p2' => '@info'], ['p1' => 12], $info);

$this->assertEquals($res[0], 12);
$this->assertEquals($res[1], $info);
}

public function testRaisedErrors(): void
{
$violation = new ConstraintViolation('validation_error', 'validation_error', [], 'invalid', 'field2', 'invalid');
Expand Down