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

QueryField::__construct()   B

Complexity

Conditions 8
Paths 2

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 41
rs 8.4444
c 0
b 0
f 0
cc 8
nc 2
nop 9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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