Skip to content

Commit 1e15957

Browse files
committed
Refactoring duplicated code into an "ArgumentResolver"
The ArgumentResolver role is similar to the HydratorInterface except it can actually hydrate any type (not only input types) but also lists and scalars. The code was previously duplicated between input type objects and queryfield args.
1 parent 81f17f2 commit 1e15957

13 files changed

+131
-99
lines changed

src/FieldsBuilder.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
2020
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
2121
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
22+
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
2223
use TheCodingMachine\GraphQLite\Types\CustomTypesRegistry;
2324
use TheCodingMachine\GraphQLite\Types\ID;
2425
use TheCodingMachine\GraphQLite\Types\TypeResolver;
@@ -65,9 +66,9 @@ class FieldsBuilder
6566
*/
6667
private $typeMapper;
6768
/**
68-
* @var HydratorInterface
69+
* @var ArgumentResolver
6970
*/
70-
private $hydrator;
71+
private $argumentResolver;
7172
/**
7273
* @var AuthenticationServiceInterface
7374
*/
@@ -90,13 +91,13 @@ class FieldsBuilder
9091
private $namingStrategy;
9192

9293
public function __construct(AnnotationReader $annotationReader, RecursiveTypeMapperInterface $typeMapper,
93-
HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService,
94+
ArgumentResolver $argumentResolver, AuthenticationServiceInterface $authenticationService,
9495
AuthorizationServiceInterface $authorizationService, TypeResolver $typeResolver,
9596
CachedDocBlockFactory $cachedDocBlockFactory, NamingStrategyInterface $namingStrategy)
9697
{
9798
$this->annotationReader = $annotationReader;
9899
$this->typeMapper = $typeMapper;
99-
$this->hydrator = $hydrator;
100+
$this->argumentResolver = $argumentResolver;
100101
$this->authenticationService = $authenticationService;
101102
$this->authorizationService = $authorizationService;
102103
$this->typeResolver = $typeResolver;
@@ -266,13 +267,13 @@ private function getFieldsByAnnotations($controller, string $annotationName, boo
266267
if ($failWithValue === null && $type instanceof NonNull) {
267268
$type = $type->getWrappedType();
268269
}
269-
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->hydrator, $docBlockComment, $injectSource);
270+
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
270271
} else {
271272
$callable = [$controller, $methodName];
272273
if ($sourceClassName !== null) {
273-
$queryList[] = new QueryField($name, $type, $args, null, $callable[1], $this->hydrator, $docBlockComment, $injectSource);
274+
$queryList[] = new QueryField($name, $type, $args, null, $callable[1], $this->argumentResolver, $docBlockComment, $injectSource);
274275
} else {
275-
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->hydrator, $docBlockComment, $injectSource);
276+
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
276277
}
277278
}
278279
}
@@ -392,7 +393,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
392393
}
393394

394395
if (!$unauthorized) {
395-
$queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->hydrator, $docBlockComment, false);
396+
$queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->argumentResolver, $docBlockComment, false);
396397
} else {
397398
$failWithValue = $sourceField->getFailWith();
398399
$callable = function() use ($failWithValue) {
@@ -401,7 +402,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
401402
if ($failWithValue === null && $type instanceof NonNull) {
402403
$type = $type->getWrappedType();
403404
}
404-
$queryList[] = new QueryField($sourceField->getName(), $type, $args, $callable, null, $this->hydrator, $docBlockComment, false);
405+
$queryList[] = new QueryField($sourceField->getName(), $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, false);
405406
}
406407

407408
}

src/FieldsBuilderFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
1010
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
1111
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
12+
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
1213
use TheCodingMachine\GraphQLite\Types\TypeResolver;
1314

1415
class FieldsBuilderFactory
@@ -65,7 +66,7 @@ public function buildFieldsBuilder(RecursiveTypeMapperInterface $typeMapper): Fi
6566
return new FieldsBuilder(
6667
$this->annotationReader,
6768
$typeMapper,
68-
$this->hydrator,
69+
new ArgumentResolver($this->hydrator),
6970
$this->authenticationService,
7071
$this->authorizationService,
7172
$this->typeResolver,

src/Hydrators/HydratorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
interface HydratorInterface
1212
{
1313
/**
14-
* Whether this hydrate can hydrate the passed data.
14+
* Whether this hydrator can hydrate the passed data.
1515
*
1616
* @param mixed[] $data
1717
* @param InputObjectType $type

src/InputTypeGenerator.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use TheCodingMachine\GraphQLite\Annotations\Type;
1616
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
1717
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
18+
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
1819
use TheCodingMachine\GraphQLite\Types\ResolvableInputObjectType;
1920

2021
/**
@@ -31,21 +32,21 @@ class InputTypeGenerator
3132
*/
3233
private $cache = [];
3334
/**
34-
* @var HydratorInterface
35+
* @var ArgumentResolver
3536
*/
36-
private $hydrator;
37+
private $argumentResolver;
3738
/**
3839
* @var InputTypeUtils
3940
*/
4041
private $inputTypeUtils;
4142

4243
public function __construct(InputTypeUtils $inputTypeUtils,
4344
FieldsBuilderFactory $fieldsBuilderFactory,
44-
HydratorInterface $hydrator)
45+
ArgumentResolver $argumentResolver)
4546
{
4647
$this->inputTypeUtils = $inputTypeUtils;
4748
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
48-
$this->hydrator = $hydrator;
49+
$this->argumentResolver = $argumentResolver;
4950
}
5051

5152
/**
@@ -68,7 +69,7 @@ public function mapFactoryMethod(string $factory, string $methodName, RecursiveT
6869

6970
if (!isset($this->cache[$inputName])) {
7071
// TODO: add comment argument.
71-
$this->cache[$inputName] = new ResolvableInputObjectType($inputName, $this->fieldsBuilderFactory, $recursiveTypeMapper, $object, $methodName, $this->hydrator, null);
72+
$this->cache[$inputName] = new ResolvableInputObjectType($inputName, $this->fieldsBuilderFactory, $recursiveTypeMapper, $object, $methodName, $this->argumentResolver, null);
7273
}
7374

7475
return $this->cache[$inputName];

src/QueryField.php

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use GraphQL\Type\Definition\IDType;
99
use GraphQL\Type\Definition\InputObjectType;
1010
use GraphQL\Type\Definition\InputType;
11+
use GraphQL\Type\Definition\LeafType;
1112
use GraphQL\Type\Definition\ListOfType;
1213
use GraphQL\Type\Definition\NonNull;
1314
use GraphQL\Type\Definition\OutputType;
@@ -16,11 +17,14 @@
1617
use InvalidArgumentException;
1718
use function is_array;
1819
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
20+
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
1921
use TheCodingMachine\GraphQLite\Types\DateTimeType;
2022
use TheCodingMachine\GraphQLite\Types\ID;
2123

2224
/**
2325
* A GraphQL field that maps to a PHP method automatically.
26+
*
27+
* @internal
2428
*/
2529
class QueryField extends FieldDefinition
2630
{
@@ -31,12 +35,12 @@ class QueryField extends FieldDefinition
3135
* @param array[] $arguments Indexed by argument name, value: ['type'=>InputType, 'defaultValue'=>val].
3236
* @param callable|null $resolve The method to execute
3337
* @param string|null $targetMethodOnSource The name of the method to execute on the source object. Mutually exclusive with $resolve parameter.
34-
* @param HydratorInterface $hydrator
38+
* @param ArgumentResolver $argumentResolver
3539
* @param null|string $comment
3640
* @param bool $injectSource Whether to inject the source object (for Fields), or null for Query and Mutations
3741
* @param array $additionalConfig
3842
*/
39-
public function __construct(string $name, OutputType $type, array $arguments, ?callable $resolve, ?string $targetMethodOnSource, HydratorInterface $hydrator, ?string $comment, bool $injectSource, array $additionalConfig = [])
43+
public function __construct(string $name, OutputType $type, array $arguments, ?callable $resolve, ?string $targetMethodOnSource, ArgumentResolver $argumentResolver, ?string $comment, bool $injectSource, array $additionalConfig = [])
4044
{
4145
$config = [
4246
'name' => $name,
@@ -47,15 +51,15 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
4751
$config['description'] = $comment;
4852
}
4953

50-
$config['resolve'] = function ($source, array $args) use ($resolve, $targetMethodOnSource, $arguments, $injectSource, $hydrator) {
54+
$config['resolve'] = function ($source, array $args) use ($resolve, $targetMethodOnSource, $arguments, $injectSource, $argumentResolver) {
5155
$toPassArgs = [];
5256
if ($injectSource) {
5357
$toPassArgs[] = $source;
5458
}
5559
foreach ($arguments as $name => $arr) {
5660
$type = $arr['type'];
5761
if (isset($args[$name])) {
58-
$val = $this->castVal($args[$name], $type, $hydrator);
62+
$val = $argumentResolver->resolve($args[$name], $type);
5963
} elseif (array_key_exists('defaultValue', $arr)) {
6064
$val = $arr['defaultValue'];
6165
} else {
@@ -78,41 +82,4 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
7882
$config += $additionalConfig;
7983
parent::__construct($config);
8084
}
81-
82-
private function stripNonNullType(Type $type): Type
83-
{
84-
if ($type instanceof NonNull) {
85-
return $this->stripNonNullType($type->getWrappedType());
86-
}
87-
return $type;
88-
}
89-
90-
/**
91-
* Casts a value received from GraphQL into an argument passed to a method.
92-
*
93-
* @param mixed $val
94-
* @param InputType $type
95-
* @return mixed
96-
*/
97-
private function castVal($val, InputType $type, HydratorInterface $hydrator)
98-
{
99-
$type = $this->stripNonNullType($type);
100-
if ($type instanceof ListOfType) {
101-
if (!is_array($val)) {
102-
throw new InvalidArgumentException('Expected GraphQL List but value passed is not an array.');
103-
}
104-
return array_map(function($item) use ($type, $hydrator) {
105-
return $this->castVal($item, $type->getWrappedType(), $hydrator);
106-
}, $val);
107-
} elseif ($type instanceof DateTimeType) {
108-
return new \DateTimeImmutable($val);
109-
} elseif ($type instanceof IDType) {
110-
return new ID($val);
111-
} elseif ($type instanceof InputObjectType) {
112-
return $hydrator->hydrate($val, $type);
113-
} elseif (!$type instanceof ScalarType) {
114-
throw new \RuntimeException('Unexpected type: '.get_class($type));
115-
}
116-
return $val;
117-
}
11885
}

src/SchemaFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use TheCodingMachine\GraphQLite\Security\FailAuthorizationService;
3131
use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService;
3232
use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService;
33+
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
3334
use TheCodingMachine\GraphQLite\Types\TypeResolver;
3435

3536
/**
@@ -183,6 +184,7 @@ public function createSchema(): Schema
183184
{
184185
$annotationReader = new AnnotationReader($this->getDoctrineAnnotationReader(), AnnotationReader::LAX_MODE);
185186
$hydrator = $this->hydrator ?: new FactoryHydrator();
187+
$argumentResolver = new ArgumentResolver($hydrator);
186188
$authenticationService = $this->authenticationService ?: new FailAuthenticationService();
187189
$authorizationService = $this->authorizationService ?: new FailAuthorizationService();
188190
$typeResolver = new TypeResolver();
@@ -202,7 +204,7 @@ public function createSchema(): Schema
202204

203205
$typeGenerator = new TypeGenerator($annotationReader, $fieldsBuilderFactory, $namingStrategy, $typeRegistry, $this->container);
204206
$inputTypeUtils = new InputTypeUtils($annotationReader, $namingStrategy);
205-
$inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilderFactory, $hydrator);
207+
$inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilderFactory, $argumentResolver);
206208

207209
$typeMappers = [];
208210

src/Types/ArgumentResolver.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\GraphQLite\Types;
5+
6+
use function array_map;
7+
use function get_class;
8+
use GraphQL\Type\Definition\IDType;
9+
use GraphQL\Type\Definition\InputObjectType;
10+
use GraphQL\Type\Definition\InputType;
11+
use GraphQL\Type\Definition\LeafType;
12+
use GraphQL\Type\Definition\ListOfType;
13+
use GraphQL\Type\Definition\NonNull;
14+
use GraphQL\Type\Definition\Type;
15+
use InvalidArgumentException;
16+
use function is_array;
17+
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
18+
19+
/**
20+
* Resolves arguments based on input value and InputType
21+
*/
22+
class ArgumentResolver
23+
{
24+
/**
25+
* @var HydratorInterface
26+
*/
27+
private $hydrator;
28+
29+
public function __construct(HydratorInterface $hydrator)
30+
{
31+
$this->hydrator = $hydrator;
32+
}
33+
34+
/**
35+
* Casts a value received from GraphQL into an argument passed to a method.
36+
*
37+
* @param mixed $val
38+
* @param InputType $type
39+
* @return mixed
40+
*/
41+
public function resolve($val, InputType $type)
42+
{
43+
$type = $this->stripNonNullType($type);
44+
if ($type instanceof ListOfType) {
45+
if (!is_array($val)) {
46+
throw new InvalidArgumentException('Expected GraphQL List but value passed is not an array.');
47+
}
48+
return array_map(function($item) use ($type) {
49+
return $this->resolve($item, $type->getWrappedType());
50+
}, $val);
51+
} elseif ($type instanceof IDType) {
52+
return new ID($val);
53+
} elseif ($type instanceof LeafType) {
54+
return $type->parseValue($val);
55+
} elseif ($type instanceof InputObjectType) {
56+
return $this->hydrator->hydrate($val, $type);
57+
} else {
58+
throw new \RuntimeException('Unexpected type: '.get_class($type));
59+
}
60+
}
61+
62+
private function stripNonNullType(Type $type): Type
63+
{
64+
if ($type instanceof NonNull) {
65+
return $this->stripNonNullType($type->getWrappedType());
66+
}
67+
return $type;
68+
}
69+
}

src/Types/DateTimeType.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ public function serialize($value): string
5353
*/
5454
public function parseValue($value): ?DateTimeImmutable
5555
{
56-
return DateTimeImmutable::createFromFormat(DateTime::ATOM, $value) ?: null;
56+
if ($value === null) {
57+
return null;
58+
}
59+
return new DateTimeImmutable($value);
5760
}
5861

5962
/**

0 commit comments

Comments
 (0)