1 | <?php |
||
2 | |||
3 | |||
4 | namespace TheCodingMachine\GraphQL\Controllers; |
||
5 | |||
6 | use function get_class; |
||
7 | use GraphQL\Type\Definition\FieldDefinition; |
||
8 | use GraphQL\Type\Definition\IDType; |
||
9 | use GraphQL\Type\Definition\InputObjectType; |
||
10 | use GraphQL\Type\Definition\InputType; |
||
11 | use GraphQL\Type\Definition\ListOfType; |
||
12 | use GraphQL\Type\Definition\NonNull; |
||
13 | use GraphQL\Type\Definition\OutputType; |
||
14 | use GraphQL\Type\Definition\ScalarType; |
||
15 | use GraphQL\Type\Definition\Type; |
||
16 | use InvalidArgumentException; |
||
17 | use function is_array; |
||
18 | use TheCodingMachine\GraphQL\Controllers\Hydrators\HydratorInterface; |
||
19 | use TheCodingMachine\GraphQL\Controllers\Types\DateTimeType; |
||
20 | use TheCodingMachine\GraphQL\Controllers\Types\ID; |
||
21 | |||
22 | /** |
||
23 | * A GraphQL field that maps to a PHP method automatically. |
||
24 | */ |
||
25 | class QueryField extends FieldDefinition |
||
26 | { |
||
27 | /** |
||
28 | * QueryField constructor. |
||
29 | * @param string $name |
||
30 | * @param OutputType&Type $type |
||
31 | * @param array[] $arguments Indexed by argument name, value: ['type'=>InputType, 'defaultValue'=>val]. |
||
32 | * @param callable|null $resolve The method to execute |
||
33 | * @param string|null $targetMethodOnSource The name of the method to execute on the source object. Mutually exclusive with $resolve parameter. |
||
34 | * @param HydratorInterface $hydrator |
||
35 | * @param null|string $comment |
||
36 | * @param bool $injectSource Whether to inject the source object (for Fields), or null for Query and Mutations |
||
37 | * @param array $additionalConfig |
||
38 | */ |
||
39 | public function __construct(string $name, OutputType $type, array $arguments, ?callable $resolve, ?string $targetMethodOnSource, HydratorInterface $hydrator, ?string $comment, bool $injectSource, array $additionalConfig = []) |
||
40 | { |
||
41 | $config = [ |
||
42 | 'name' => $name, |
||
43 | 'type' => $type, |
||
44 | 'args' => array_map(function(array $item) { return $item['type']; }, $arguments) |
||
45 | ]; |
||
46 | if ($comment) { |
||
47 | $config['description'] = $comment; |
||
48 | } |
||
49 | |||
50 | $config['resolve'] = function ($source, array $args) use ($resolve, $targetMethodOnSource, $arguments, $injectSource, $hydrator) { |
||
51 | $toPassArgs = []; |
||
52 | if ($injectSource) { |
||
53 | $toPassArgs[] = $source; |
||
54 | } |
||
55 | foreach ($arguments as $name => $arr) { |
||
56 | $type = $arr['type']; |
||
57 | if (isset($args[$name])) { |
||
58 | $val = $this->castVal($args[$name], $type, $hydrator); |
||
59 | } elseif (array_key_exists('defaultValue', $arr)) { |
||
60 | $val = $arr['defaultValue']; |
||
61 | } else { |
||
62 | throw new GraphQLException("Expected argument '$name' was not provided."); |
||
63 | } |
||
64 | |||
65 | $toPassArgs[] = $val; |
||
66 | } |
||
67 | |||
68 | if ($resolve !== null) { |
||
69 | return $resolve(...$toPassArgs); |
||
70 | } |
||
71 | if ($targetMethodOnSource !== null) { |
||
72 | $method = [$source, $targetMethodOnSource]; |
||
73 | return $method(...$toPassArgs); |
||
74 | } |
||
75 | throw new \InvalidArgumentException('The QueryField constructor should be passed either a resolve method or a target method on source object.'); |
||
76 | }; |
||
77 | |||
78 | $config += $additionalConfig; |
||
79 | parent::__construct($config); |
||
80 | } |
||
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); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
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 | } |
||
118 | } |
||
119 |