buildInterfaceType()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Andi\GraphQL\TypeResolver\Middleware;
6
7
use Andi\GraphQL\Attribute;
8
use Andi\GraphQL\Common\DefinitionAwareTrait;
9
use Andi\GraphQL\Common\InputObjectFactory;
10
use Andi\GraphQL\Common\LazyInputObjectFields;
11
use Andi\GraphQL\Common\LazyObjectFields;
12
use Andi\GraphQL\Common\LazyTypeIterator;
13
use Andi\GraphQL\Common\LazyTypeResolver;
14
use Andi\GraphQL\Common\ReflectionMethodWithAttribute;
15
use Andi\GraphQL\Common\ResolveType;
16
use Andi\GraphQL\Definition\Type\FieldsAwareInterface;
17
use Andi\GraphQL\Definition\Type\InterfacesAwareInterface;
18
use Andi\GraphQL\Definition\Type\IsTypeOfAwareInterface;
19
use Andi\GraphQL\Definition\Type\ParseValueAwareInterface;
20
use Andi\GraphQL\Definition\Type\ResolveFieldAwareInterface;
21
use Andi\GraphQL\Definition\Type\ResolveTypeAwareInterface;
22
use Andi\GraphQL\InputObjectFieldResolver\InputObjectFieldResolverInterface;
23
use Andi\GraphQL\ObjectFieldResolver\ObjectFieldResolverInterface;
24
use Andi\GraphQL\TypeRegistryInterface;
25
use Andi\GraphQL\TypeResolver\TypeResolverInterface;
26
use Andi\GraphQL\Type\DynamicObjectTypeInterface;
27
use Andi\GraphQL\WebonyxType\InputObjectType;
28
use Andi\GraphQL\WebonyxType\InterfaceType;
29
use Andi\GraphQL\WebonyxType\ObjectType;
30
use GraphQL\Type\Definition as Webonyx;
31
use Psr\Container\ContainerInterface;
32
use Spiral\Attributes\ReaderInterface;
33
use Spiral\Core\ResolverInterface;
34
35
final class AttributedGraphQLTypeMiddleware implements MiddlewareInterface
36
{
37
    use DefinitionAwareTrait;
0 ignored issues
show
introduced by
The trait Andi\GraphQL\Common\DefinitionAwareTrait requires some properties which are not provided by Andi\GraphQL\TypeResolve...edGraphQLTypeMiddleware: $name, $description
Loading history...
38
39
    public const PRIORITY = 1024;
40
41 12
    public function __construct(
42
        private readonly ContainerInterface $container,
43
        private readonly ReaderInterface $reader,
44
        private readonly TypeRegistryInterface $typeRegistry,
45
        private readonly ObjectFieldResolverInterface $objectFieldResolver,
46
        private readonly InputObjectFieldResolverInterface $inputObjectFieldResolver,
47
        private readonly ResolverInterface $resolver,
48
    ) {
49 12
    }
50
51 11
    public function process(mixed $type, TypeResolverInterface $typeResolver): Webonyx\Type
52
    {
53 11
        $class = \is_string($type) && \class_exists($type)
54 1
            ? new \ReflectionClass($type)
55 10
            : $type;
56
57 11
        if (! $class instanceof \ReflectionClass) {
58 1
            return $typeResolver->resolve($type);
59
        }
60
61 10
        $attributes = $class->getAttributes(Attribute\AbstractType::class, \ReflectionAttribute::IS_INSTANCEOF);
62 10
        foreach ($attributes as $attribute) {
63 9
            $webonyxType = match ($attribute->getName()) {
64 9
                Attribute\ObjectType::class => $this->buildObjectType($class),
65 9
                Attribute\InputObjectType::class => $this->buildInputObjectType($class),
66 9
                Attribute\InterfaceType::class => $this->buildInterfaceType($class),
67 9
                default => null,
68 9
            };
69
70 9
            if (null !== $webonyxType) {
71 9
                return $webonyxType;
72
            }
73
        }
74
75 1
        return $typeResolver->resolve($type);
76
    }
77
78 2
    private function buildObjectType(\ReflectionClass $class): Webonyx\ObjectType
79
    {
80 2
        $attribute = $this->reader->firstClassMetadata($class, Attribute\ObjectType::class);
81
82 2
        $config = [
83 2
            'name' => $this->getTypeName($class, $attribute),
84 2
            'description' => $this->getTypeDescription($class, $attribute),
85 2
        ];
86
87 2
        $instance = null;
88 2
        if ($class->isSubclassOf(FieldsAwareInterface::class)) {
89 1
            $instance = $class->newInstanceWithoutConstructor();
90
91 1
            $config['fields'] = new LazyObjectFields($instance, $this->objectFieldResolver);
92
        }
93
94 2
        if ($class->isSubclassOf(InterfacesAwareInterface::class)) {
95 1
            $instance ??= $class->newInstanceWithoutConstructor();
96
            /** @psalm-suppress UndefinedMethod */
97 1
            $config['interfaces'] = new LazyTypeIterator($instance->getInterfaces(...), $this->typeRegistry);
98
        }
99
100 2
        if ($class->isSubclassOf(IsTypeOfAwareInterface::class)) {
101 1
            $instance ??= $class->newInstanceWithoutConstructor();
102
            /** @psalm-suppress UndefinedMethod */
103 1
            $config['isTypeOf'] = $instance->isTypeOf(...);
104
        }
105
106 2
        if ($class->isSubclassOf(ResolveFieldAwareInterface::class)) {
107 1
            $instance ??= $class->newInstanceWithoutConstructor();
108
            /** @psalm-suppress UndefinedMethod */
109 1
            $config['resolveField'] = $instance->resolveField(...);
110
        }
111
112 2
        $type = new ObjectType($config, $this->objectFieldResolver);
113
114 2
        $this->registerAdditionalFieldByMethods($type, $class, Attribute\ObjectField::class);
115 2
        $this->registerAdditionalFieldByProperties($type, $class, Attribute\ObjectField::class);
116
117 2
        return $type;
118
    }
119
120 4
    private function buildInputObjectType(\ReflectionClass $class): Webonyx\InputObjectType
121
    {
122 4
        $attribute = $this->reader->firstClassMetadata($class, Attribute\InputObjectType::class);
123
124 4
        $config = [
125 4
            'name' => $this->getTypeName($class, $attribute),
126 4
            'description' => $this->getTypeDescription($class, $attribute),
127 4
            'parseValue' => $this->getTypeParseValue($class, $attribute),
128 4
        ];
129
130 4
        if ($class->isSubclassOf(FieldsAwareInterface::class)) {
131 1
            $instance = $class->newInstanceWithoutConstructor();
132
133 1
            $config['fields'] = new LazyInputObjectFields($instance, $this->inputObjectFieldResolver);
134
        }
135
136 4
        $type = new InputObjectType($config, $this->inputObjectFieldResolver);
137
138 4
        $this->registerAdditionalFieldByMethods($type, $class, Attribute\InputObjectField::class);
139 4
        $this->registerAdditionalFieldByProperties($type, $class, Attribute\InputObjectField::class);
140
141 4
        return $type;
142
    }
143
144 3
    private function buildInterfaceType(\ReflectionClass $class): Webonyx\InterfaceType
145
    {
146 3
        $attribute = $this->reader->firstClassMetadata($class, Attribute\InterfaceType::class);
147
148 3
        $config = [
149 3
            'name' => $this->getTypeName($class, $attribute),
150 3
            'description' => $this->getTypeDescription($class, $attribute),
151 3
            'resolveType' => $this->getResolveTypeFn($class, $attribute),
152 3
        ];
153
154 3
        $type = new InterfaceType($config, $this->objectFieldResolver);
155
156 3
        $this->registerAdditionalFieldByMethods($type, $class, Attribute\InterfaceField::class);
157
158 3
        return $type;
159
    }
160
161 3
    private function getResolveTypeFn(\ReflectionClass $class, ?Attribute\InterfaceType $attribute): callable
162
    {
163 3
        if ($attribute?->resolveType) {
164 1
            return new LazyTypeResolver($this->container->get($attribute->resolveType), $this->typeRegistry);
165
        }
166
167 2
        if (! $class->isInterface() && $class->isSubclassOf(ResolveTypeAwareInterface::class)) {
168 1
            return new LazyTypeResolver($class->getMethod('resolveType')->getClosure(), $this->typeRegistry);
169
        }
170
171 1
        return $this->container->get(ResolveType::class);
172
    }
173
174 4
    private function getTypeParseValue(\ReflectionClass $class, ?Attribute\InputObjectType $attribute): callable
175
    {
176 4
        if ($attribute?->factory) {
177 1
            return $this->container->get($attribute->factory);
178
        }
179
180 3
        if ($class->isSubclassOf(ParseValueAwareInterface::class)) {
181 1
            return $class->getMethod('parseValue')->getClosure();
182
        }
183
184 2
        return new InputObjectFactory($class, $this->resolver);
185
    }
186
187
    /**
188
     * @param DynamicObjectTypeInterface $type
189
     * @param \ReflectionClass $class
190
     * @param class-string $targetAttribute
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
191
     */
192 9
    private function registerAdditionalFieldByMethods(
193
        DynamicObjectTypeInterface $type,
194
        \ReflectionClass $class,
195
        string $targetAttribute,
196
    ): void {
197 9
        foreach ($class->getMethods() as $method) {
198 8
            if ($attribute = $this->reader->firstFunctionMetadata($method, $targetAttribute)) {
199 6
                $type->addAdditionalField(new ReflectionMethodWithAttribute($method, $attribute));
200
            }
201
        }
202
    }
203
    /**
204
     * @param DynamicObjectTypeInterface $type
205
     * @param \ReflectionClass $class
206
     * @param class-string $targetAttribute
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
207
     */
208 6
    private function registerAdditionalFieldByProperties(
209
        DynamicObjectTypeInterface $type,
210
        \ReflectionClass $class,
211
        string $targetAttribute,
212
    ): void {
213 6
        foreach ($class->getProperties() as $property) {
214 4
            if (null !== $this->reader->firstPropertyMetadata($property, $targetAttribute)) {
215 4
                $type->addAdditionalField($property);
216
            }
217
        }
218
    }
219
}
220