AbstractObjectType::getObjectField()   B
last analyzed

Complexity

Conditions 8
Paths 7

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 16
c 0
b 0
f 0
nc 7
nop 2
dl 0
loc 33
ccs 19
cts 19
cp 1
crap 8
rs 8.4444
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
/**
20
 * @phpstan-type ArgumentConfig array{
21
 *     name: string,
22
 *     type: string,
23
 *     mode: int,
24
 *     description?: string|null,
25
 *     deprecationReason?: string|null,
26
 *     defaultValue: mixed
27
 * }
28
 * @phpstan-type FieldConfig array{
29
 *     name: string,
30
 *     type:string,
31
 *     mode: int,
32
 *     description?: string|null,
33
 *     deprecationReason?: string|null,
34
 *     resolve?: callable|null,
35
 *     complexity?: callable|null,
36
 *     arguments: array<array-key, string|ArgumentConfig>
37
 * }
38
 */
39
abstract class AbstractObjectType extends AbstractType implements
40
    ObjectTypeInterface,
41
    InterfacesAwareInterface,
42
    DynamicObjectTypeInterface
43
{
44
    /**
45
     * @var iterable<array-key, string|ObjectFieldInterface|Webonyx\FieldDefinition|FieldConfig>
46
     */
47
    protected iterable $fields;
48
49
    protected iterable $interfaces;
50
51
    protected array $additionalFields = [];
52
53 23
    public function getFields(): iterable
54
    {
55
        /**
56
         * @psalm-suppress RedundantPropertyInitializationCheck
57
         * @psalm-suppress RedundantCondition
58
         * @psalm-suppress TypeDoesNotContainType
59
         */
60 23
        foreach ($this->fields ?? [] as $name => $field) {
61 22
            if ($field instanceof Webonyx\FieldDefinition || $field instanceof ObjectFieldInterface) {
62 1
                yield $field;
63
64 1
                continue;
65
            }
66
67 21
            if (\is_string($field) || \is_array($field)) {
68 20
                yield $this->getObjectField($name, $field);
69
70 14
                continue;
71
            }
72
73 1
            throw new CantResolveObjectFieldException('Can\'t resolve ObjectField: wrong field configuration');
74
        }
75
76 16
        yield from $this->additionalFields;
77
    }
78
79 1
    public function getInterfaces(): iterable
80
    {
81
        /** @psalm-suppress RedundantPropertyInitializationCheck */
82 1
        yield from $this->interfaces ?? [];
83
    }
84
85 1
    public function addAdditionalField(mixed $field): static
86
    {
87 1
        $this->additionalFields[] = $field;
88
89 1
        return $this;
90
    }
91
92 20
    private function getObjectField(int|string $name, string|array $field): AbstractObjectField
93
    {
94 20
        $fieldName = $field['name'] ?? $name;
95
96 20
        if (! \is_string($fieldName)) {
97 1
            throw new CantResolveObjectFieldException(
98 1
                'Can\'t resolve ObjectField: wrong configuration - undefined name',
99 1
            );
100
        }
101
102 19
        if (\is_string($field)) {
0 ignored issues
show
introduced by
The condition is_string($field) is always false.
Loading history...
103 1
            return $this->makeObjectField($fieldName, ['type' => $field]);
104
        }
105
106 18
        if (! isset($field['type']) || ! \is_string($field['type'])) {
107 1
            throw new CantResolveObjectFieldException(
108 1
                'Can\'t resolve ObjectField: wrong configuration - undefined type',
109 1
            );
110
        }
111
112 17
        if (isset($field['resolve'], $field['complexity'])) {
113 4
            return $this->makeObjectFieldWithBoth($fieldName, $field);
114
        }
115
116 13
        if (isset($field['resolve'])) {
117 8
            return $this->makeObjectFieldWithResolve($fieldName, $field);
118
        }
119
120 5
        if (isset($field['complexity'])) {
121 4
            return $this->makeObjectFieldWithComplexity($fieldName, $field);
122
        }
123
124 1
        return $this->makeObjectField($fieldName, $field);
125
    }
126
127 2
    private function makeObjectField(string $name, array $field): AbstractObjectField
128
    {
129
        /** @psalm-suppress InternalClass */
130 2
        return new class($name, $field) extends AbstractAnonymousObjectField {};
131
    }
132
133 8
    private function makeObjectFieldWithResolve(string $name, array $field): AbstractObjectField
134
    {
135 8
        $resolve = $this->makeClosure($field['resolve'], 'resolve');
136
        /** @psalm-suppress InternalClass */
137 4
        return new class($name, $field, $resolve) extends AbstractAnonymousObjectField implements
138 4
            ResolveAwareInterface
139 4
        {
140
            use AnonymousResolveAwareTrait;
141 4
        };
142
    }
143
144 4
    private function makeObjectFieldWithComplexity(string $name, array $field): AbstractObjectField
145
    {
146 4
        $complexity = $this->makeClosure($field['complexity'], 'complexity');
147
        /** @psalm-suppress InternalClass */
148 4
        return new class($name, $field, null, $complexity) extends AbstractAnonymousObjectField implements
149 4
            ComplexityAwareInterface
150 4
        {
151
            use AnonymousComplexityAwareTrait;
152 4
        };
153
    }
154
155 4
    private function makeObjectFieldWithBoth(string $name, array $field): AbstractObjectField
156
    {
157 4
        $resolve = $this->makeClosure($field['resolve'], 'resolve');
158 4
        $complexity = $this->makeClosure($field['complexity'], 'complexity');
159
        /** @psalm-suppress InternalClass */
160 4
        return new class($name, $field, $resolve, $complexity) extends AbstractAnonymousObjectField implements
161 4
            ResolveAwareInterface,
162 4
            ComplexityAwareInterface
163 4
        {
164
            use AnonymousResolveAwareTrait;
165
            use AnonymousComplexityAwareTrait;
166 4
        };
167
    }
168
169 16
    private function makeClosure($callable, string $option): \Closure
170
    {
171 16
        if ($callable instanceof \Closure) {
172 3
            return $callable;
173
        }
174
175 13
        if (\is_callable($callable)) {
176 3
            return \Closure::fromCallable($callable);
177
        }
178
179 10
        if (\is_array($callable)) {
180 5
            if (! isset($callable[0], $callable[1])) {
181 1
                throw new CantResolveObjectFieldException(\sprintf(
182 1
                    'Can\'t resolve ObjectField: wrong configuration - %s must be callable',
183 1
                    $option
184 1
                ));
185
            }
186
187
            try {
188 4
                $method = new \ReflectionMethod($callable[0], $callable[1]);
189 1
            } catch (\ReflectionException $exception) {
190 1
                throw new CantResolveObjectFieldException(
191 1
                    \sprintf('Can\'t resolve ObjectField: wrong configuration - %s must be callable', $option),
192 1
                    $exception->getCode(),
193 1
                    $exception,
194 1
                );
195
            }
196
197 3
            return $method->getClosure($this);
198
        }
199
200 5
        if (\is_string($callable)) {
201
            try {
202 4
                $method = \str_contains($callable, '::')
203 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

203
                    ? new \ReflectionMethod(/** @scrutinizer ignore-type */ ...\explode('::', $callable, 2))
Loading history...
204 3
                    : new \ReflectionMethod($this, $callable);
205 1
            } catch (\ReflectionException $exception) {
206 1
                throw new CantResolveObjectFieldException(
207 1
                    \sprintf('Can\'t resolve ObjectField: wrong configuration - %s must be callable', $option),
208 1
                    $exception->getCode(),
209 1
                    $exception,
210 1
                );
211
            }
212
213 3
            return $method->getClosure($this);
214
        }
215
216 1
        throw new CantResolveObjectFieldException(
217 1
            'Can\'t resolve ObjectField: wrong configuration - resolve must be callable',
218 1
        );
219
    }
220
}
221