Passed
Push — master ( 3b66e1...fb7c7d )
by Andrey
58s queued 12s
created

AbstractObjectType::getInterfaces()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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
    public function getFields(): iterable
37
    {
38
        foreach ($this->fields as $name => $field) {
39
            if ($field instanceof Webonyx\FieldDefinition || $field instanceof ObjectFieldInterface) {
40
                yield $field;
41
42
                continue;
43
            }
44
45
            $fieldName = $field['name'] ?? $name;
46
47
            if (! is_string($fieldName)) {
48
                throw new CantResolveObjectFieldException('Can\'t resolve ObjectField configuration: undefined name');
49
            }
50
51
            if (is_string($field)) {
52
                yield $this->makeObjectField($fieldName, ['type' => $field]);
53
54
                continue;
55
            }
56
57
            if (is_array($field)) {
58
                if (! isset($field['type']) || ! is_string($field['type'])) {
59
                    throw new CantResolveObjectFieldException(
60
                        'Can\'t resolve ObjectField configuration: undefined type',
61
                    );
62
                }
63
64
                if (isset($field['resolve'], $field['complexity'])) {
65
                    yield $this->makeObjectFieldWithBoth($fieldName, $field);
66
                } elseif (isset($field['resolve'])) {
67
                    yield $this->makeObjectFieldWithResolve($fieldName, $field);
68
                } elseif (isset($field['complexity'])) {
69
                    yield $this->makeObjectFieldWithComplexity($fieldName, $field);
70
                } else {
71
                    yield $this->makeObjectField($fieldName, $field);
72
                }
73
74
                continue;
75
            }
76
77
            throw new CantResolveObjectFieldException(
78
                'Can\'t resolve ObjectField configuration: unknown field configuration',
79
            );
80
        }
81
82
        yield from $this->additionalFields;
83
    }
84
85
    public function getInterfaces(): iterable
86
    {
87
        yield from $this->interfaces ?? [];
88
    }
89
90
    public function addAdditionalField(mixed $field): static
91
    {
92
        $this->additionalFields[] = $field;
93
94
        return $this;
95
    }
96
97
    private function makeObjectField(string $name, array $field): AbstractObjectField
98
    {
99
        return new class($name, $field) extends AbstractAnonymousObjectField {};
100
    }
101
102
    private function makeObjectFieldWithResolve(string $name, array $field): AbstractObjectField
103
    {
104
        $resolve = $this->makeClosure($field['resolve']);
105
106
        return new class($name, $field, $resolve) extends AbstractAnonymousObjectField implements
107
            ResolveAwareInterface
108
        {
109
            use AnonymousResolveAwareTrait;
110
        };
111
    }
112
113
    private function makeObjectFieldWithComplexity(string $name, array $field): AbstractObjectField
114
    {
115
        $complexity = $this->makeClosure($field['complexity']);
116
117
        return new class($name, $field, null, $complexity) extends AbstractAnonymousObjectField implements
118
            ComplexityAwareInterface
119
        {
120
            use AnonymousComplexityAwareTrait;
121
        };
122
    }
123
124
    private function makeObjectFieldWithBoth(string $name, array $field): AbstractObjectField
125
    {
126
        $resolve = $this->makeClosure($field['resolve']);
127
        $complexity = $this->makeClosure($field['complexity']);
128
129
        return new class($name, $field, $resolve, $complexity) extends AbstractAnonymousObjectField implements
130
            ResolveAwareInterface,
131
            ComplexityAwareInterface
132
        {
133
            use AnonymousResolveAwareTrait;
134
            use AnonymousComplexityAwareTrait;
135
        };
136
    }
137
138
    private function makeClosure($callable): \Closure
139
    {
140
        if ($callable instanceof \Closure) {
141
            return $callable;
142
        }
143
144
        if (is_array($callable)) {
145
            if (is_callable($callable)) {
146
                return \Closure::fromCallable($callable);
147
            }
148
149
            if (! isset($callable[0], $callable[1])) {
150
                throw new CantResolveObjectFieldException(
151
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
152
                );
153
            }
154
155
            try {
156
                $method = new \ReflectionMethod($callable[0], $callable[1]);
157
            } catch (\ReflectionException $exception) {
158
                throw new CantResolveObjectFieldException(
159
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
160
                    $exception->getCode(),
161
                    $exception,
162
                );
163
            }
164
165
            return $method->getClosure($this);
166
        }
167
168
        if (is_string($callable)) {
169
            try {
170
                $method = str_contains($callable, '::')
171
                    ? new \ReflectionMethod($callable)
172
                    : new \ReflectionMethod($this, $callable);
173
            } catch (\ReflectionException $exception) {
174
                throw new CantResolveObjectFieldException(
175
                    'Can\'t resolve ObjectField configuration: resolve must be callable',
176
                    $exception->getCode(),
177
                    $exception,
178
                );
179
            }
180
181
            return $method->getClosure($this);
182
        }
183
184
        throw new CantResolveObjectFieldException(
185
            'Can\'t resolve ObjectField configuration: resolve must be callable',
186
        );
187
    }
188
}
189