Skip to content

Commit 6af5c28

Browse files
authored
Merge pull request #36 from moufmouf/argument_resolver
Refactoring duplicated code into an "ArgumentResolver"
2 parents 81f17f2 + 0bca91f commit 6af5c28

14 files changed

+176
-101
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: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
use DateTime;
88
use DateTimeImmutable;
9+
use Exception;
910
use GraphQL\Error\InvariantViolation;
1011
use GraphQL\Language\AST\Node;
1112
use GraphQL\Language\AST\StringValueNode;
12-
use GraphQL\Language\AST\ValueNode;
1313
use GraphQL\Type\Definition\ScalarType;
1414
use GraphQL\Utils\Utils;
1515

@@ -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
/**
@@ -72,6 +75,7 @@ public function parseLiteral($valueNode, array $variables = null)
7275
return $valueNode->value;
7376
}
7477

75-
return null;
78+
// Intentionally without message, as all information already in wrapped Exception
79+
throw new Exception();
7680
}
7781
}

0 commit comments

Comments
 (0)