Completed
Push — master ( 924012...c1a650 )
by David
16s queued 10s
created

QueryField   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 92
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 46
dl 0
loc 92
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
B castVal() 0 20 7
B __construct() 0 41 8
A stripNonNullType() 0 6 2
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
It seems like $type->getWrappedType() can also be of type GraphQL\Type\Definition\InterfaceType and GraphQL\Type\Definition\ObjectType and GraphQL\Type\Definition\UnionType; however, parameter $type of TheCodingMachine\GraphQL...s\QueryField::castVal() does only seem to accept GraphQL\Type\Definition\InputType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
                return $this->castVal($item, /** @scrutinizer ignore-type */ $type->getWrappedType(), $hydrator);
Loading history...
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