Passed
Push — master ( fb7c7d...819165 )
by Andrey
49s queued 13s
created

AbstractObjectType::addAdditionalField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
cc 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Andi\GraphQL\Type;
6
7
use Andi\GraphQL\Definition\Field\ComplexityAwareInterface;
8
use Andi\GraphQL\Definition\Field\ObjectFieldInterface;
9
use Andi\GraphQL\Definition\Field\ResolveAwareInterface;
10
use Andi\GraphQL\Definition\Type\InterfacesAwareInterface;
11
use Andi\GraphQL\Definition\Type\ObjectTypeInterface;
12
use Andi\GraphQL\Exception\CantResolveObjectFieldException;
13
use Andi\GraphQL\Field\AbstractAnonymousObjectField;
14
use Andi\GraphQL\Field\AbstractObjectField;
15
use Andi\GraphQL\Field\AnonymousComplexityAwareTrait;
16
use Andi\GraphQL\Field\AnonymousResolveAwareTrait;
17
use GraphQL\Type\Definition as Webonyx;
18
19
abstract class AbstractObjectType extends AbstractType implements
20
    ObjectTypeInterface,
21
    InterfacesAwareInterface,
22
    DynamicObjectTypeInterface
23
{
24
    /**
25
     * @template A of array{name: string, type: string, typeMode: int, description: string, deprecationReason: string, defaultValue: mixed}
26
     * @template F of array{name: string, type:string, typeMode: int, description: string, deprecationReason: string, resolve: callable, complexity: callable, arguments: array<array-key, string|A>}
27
     *
28
     * @var iterable<array-key, string|ObjectFieldInterface|Webonyx\FieldDefinition|F>
29
     */
30
    protected iterable $fields;
31
32
    protected iterable $interfaces;
33
34
    protected iterable $additionalFields = [];
35
36 23
    public function getFields(): iterable
37
    {
38 23
        foreach ($this->fields ?? [] as $name => $field) {
39 22
            if ($field instanceof Webonyx\FieldDefinition || $field instanceof ObjectFieldInterface) {
40 1
                yield $field;
41
42 1
                continue;
43
            }
44
45 21
            if (is_string($field) || is_array($field)) {
46 20
                yield $this->getObjectField($name, $field);
47
48 14
                continue;
49
            }
50
51 1
            throw new CantResolveObjectFieldException(
52 1
                'Can\'t resolve ObjectField configuration: unknown field configuration',
53 1
            );
54
        }
55
56 16
        yield from $this->additionalFields;
57
    }
58
59 1
    public function getInterfaces(): iterable
60
    {
61 1
        yield from $this->interfaces ?? [];
62
    }
63
64 1
    public function addAdditionalField(mixed $field): static
65
    {
66 1
        $this->additionalFields[] = $field;
67
68 1
        return $this;
69
    }
70
71 20
    private function getObjectField(int|string $name, string|array $field): AbstractObjectField
72
    {
73 20
        $fieldName = $field['name'] ?? $name;
74
75 20
        if (! is_string($fieldName)) {
76 1
            throw new CantResolveObjectFieldException('Can\'t resolve ObjectField configuration: undefined name');
77
        }
78
79 19
        if (is_string($field)) {
0 ignored issues
show
introduced by
The condition is_string($field) is always false.
Loading history...
80 1
            return $this->makeObjectField($fieldName, ['type' => $field]);
81
        }
82
83 18
        if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
84 18
            if (! isset($field['type']) || ! is_string($field['type'])) {
85 1
                throw new CantResolveObjectFieldException(
86 1
                    'Can\'t resolve ObjectField configuration: undefined type',
87 1
                );
88
            }
89
90 17
            if (isset($field['resolve'], $field['complexity'])) {
91 4
                return $this->makeObjectFieldWithBoth($fieldName, $field);
92
            }
93
94 13
            if (isset($field['resolve'])) {
95 8
                return $this->makeObjectFieldWithResolve($fieldName, $field);
96
            }
97
98 5
            if (isset($field['complexity'])) {
99 4
                return $this->makeObjectFieldWithComplexity($fieldName, $field);
100
            }
101
102 1
            return $this->makeObjectField($fieldName, $field);
103
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 83 is false. This is incompatible with the type-hinted return Andi\GraphQL\Field\AbstractObjectField. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
104
    }
105
106 2
    private function makeObjectField(string $name, array $field): AbstractObjectField
107
    {
108 2
        return new class($name, $field) extends AbstractAnonymousObjectField {};
109
    }
110
111 8
    private function makeObjectFieldWithResolve(string $name, array $field): AbstractObjectField
112
    {
113 8
        $resolve = $this->makeClosure($field['resolve']);
114
115 4
        return new class($name, $field, $resolve) extends AbstractAnonymousObjectField implements
116 4
            ResolveAwareInterface
117 4
        {
118
            use AnonymousResolveAwareTrait;
119 4
        };
120
    }
121
122 4
    private function makeObjectFieldWithComplexity(string $name, array $field): AbstractObjectField
123
    {
124 4
        $complexity = $this->makeClosure($field['complexity']);
125
126 4
        return new class($name, $field, null, $complexity) extends AbstractAnonymousObjectField implements
127 4
            ComplexityAwareInterface
128 4
        {
129
            use AnonymousComplexityAwareTrait;
130 4
        };
131
    }
132
133 4
    private function makeObjectFieldWithBoth(string $name, array $field): AbstractObjectField
134
    {
135 4
        $resolve = $this->makeClosure($field['resolve']);
136 4
        $complexity = $this->makeClosure($field['complexity']);
137
138 4
        return new class($name, $field, $resolve, $complexity) extends AbstractAnonymousObjectField implements
139 4
            ResolveAwareInterface,
140 4
            ComplexityAwareInterface
141 4
        {
142
            use AnonymousResolveAwareTrait;
143
            use AnonymousComplexityAwareTrait;
144 4
        };
145
    }
146
147 16
    private function makeClosure($callable): \Closure
148
    {
149 16
        if ($callable instanceof \Closure) {
150 3
            return $callable;
151
        }
152
153 13
        if (is_array($callable)) {
154 8
            if (is_callable($callable)) {
155 3
                return \Closure::fromCallable($callable);
156
            }
157
158 5
            if (! isset($callable[0], $callable[1])) {
159 1
                throw new CantResolveObjectFieldException(
160 1
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
161 1
                );
162
            }
163
164
            try {
165 4
                $method = new \ReflectionMethod($callable[0], $callable[1]);
166 1
            } catch (\ReflectionException $exception) {
167 1
                throw new CantResolveObjectFieldException(
168 1
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
169 1
                    $exception->getCode(),
170 1
                    $exception,
171 1
                );
172
            }
173
174 3
            return $method->getClosure($this);
175
        }
176
177 5
        if (is_string($callable)) {
178
            try {
179 4
                $method = str_contains($callable, '::')
180 1
                    ? new \ReflectionMethod(...explode('::', $callable, 2))
0 ignored issues
show
Bug introduced by
explode('::', $callable, 2) is expanded, but the parameter $objectOrMethod of ReflectionMethod::__construct() does not expect variable arguments. ( Ignorable by Annotation )

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

180
                    ? new \ReflectionMethod(/** @scrutinizer ignore-type */ ...explode('::', $callable, 2))
Loading history...
181 3
                    : new \ReflectionMethod($this, $callable);
182 1
            } catch (\ReflectionException $exception) {
183 1
                throw new CantResolveObjectFieldException(
184 1
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
185 1
                    $exception->getCode(),
186 1
                    $exception,
187 1
                );
188
            }
189
190 3
            return $method->getClosure($this);
191
        }
192
193 1
        throw new CantResolveObjectFieldException(
194 1
            'Can\'t resolve ObjectField configuration: resolve must be callable',
195 1
        );
196
    }
197
}
198