AbstractFactory   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 17
dl 0
loc 107
rs 10
c 1
b 0
f 0
eloc 37
ccs 41
cts 41
cp 1

5 Methods

Rating   Name   Duplication   Size   Complexity  
A adjustNamespace() 0 16 4
A getTypeFromPhpDeclaration() 0 29 5
A __construct() 0 5 1
A isPropertyExcluded() 0 5 1
A getTypeFromRegistry() 0 17 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Doctrine\Factory;
6
7
use Doctrine\ORM\EntityManager;
8
use GraphQL\Doctrine\Attribute\Exclude;
9
use GraphQL\Doctrine\Attribute\Reader\Reader;
10
use GraphQL\Doctrine\Types;
11
use GraphQL\Type\Definition\NamedType;
12
use GraphQL\Type\Definition\Type;
13
use ReflectionClass;
14
use ReflectionProperty;
15
16
/**
17
 * Abstract factory to be aware of types and entityManager.
18
 */
19
abstract class AbstractFactory
20
{
21
    protected readonly Reader $reader;
22
23 121
    public function __construct(
24
        protected readonly Types $types,
25
        protected readonly EntityManager $entityManager,
26
    ) {
27 121
        $this->reader = new Reader();
0 ignored issues
show
Bug introduced by
The property reader is declared read-only in GraphQL\Doctrine\Factory\AbstractFactory.
Loading history...
28
    }
29
30
    /**
31
     * Returns whether the property is excluded.
32
     */
33 18
    final protected function isPropertyExcluded(ReflectionProperty $property): bool
34
    {
35 18
        $exclude = $this->reader->getAttribute($property, Exclude::class);
36
37 18
        return $exclude !== null;
38
    }
39
40
    /**
41
     * Get instance of GraphQL type from a PHP class name.
42
     *
43
     * Supported syntaxes are the following:
44
     *
45
     *  - `?MyType`
46
     *  - `null|MyType`
47
     *  - `MyType|null`
48
     *  - `MyType[]`
49
     *  - `?MyType[]`
50
     *  - `null|MyType[]`
51
     *  - `MyType[]|null`
52
     *  - `Collection<int, MyType>`
53
     */
54 35
    final protected function getTypeFromPhpDeclaration(ReflectionClass $class, null|string|Type $typeDeclaration, bool $isEntityId = false): ?Type
55
    {
56 35
        if ($typeDeclaration === null || $typeDeclaration instanceof Type) {
57 23
            return $typeDeclaration;
58
        }
59
60 30
        $isNullable = 0;
61 30
        $name = preg_replace('~(^\?|^null\||\|null$)~', '', $typeDeclaration, count: $isNullable);
62
63 30
        $isList = 0;
64 30
        $name = preg_replace_callback(
65 30
            '~^([^<]*)\[]$|^Collection<(.*),(.*)>$~',
66 30
            fn (array $m) => $m[1] . trim($m[3] ?? ''),
67 30
            $name ?? '',
68 30
            count: $isList,
69 30
        );
70 30
        $name = $this->adjustNamespace($class, $name);
71 30
        $type = $this->getTypeFromRegistry($name, $isEntityId);
72
73 30
        if ($isList) {
74 7
            $type = Type::listOf(Type::nonNull($type));
75
        }
76
77 30
        if (!$isNullable) {
78 19
            $type = Type::nonNull($type);
79
        }
80
81
        // @phpstan-ignore-next-line
82 30
        return $type;
83
    }
84
85
    /**
86
     * Prepend namespace of the method if the class actually exists.
87
     */
88 30
    private function adjustNamespace(ReflectionClass $class, string $type): string
89
    {
90 30
        if ($type === 'self') {
91 2
            $type = $class->getName();
92
        }
93
94 30
        $namespace = $class->getNamespaceName();
95 30
        if ($namespace) {
96 30
            $namespacedType = $namespace . '\\' . $type;
97
98 30
            if (class_exists($namespacedType)) {
99 2
                return $namespacedType;
100
            }
101
        }
102
103 30
        return $type;
104
    }
105
106
    /**
107
     * Returns a type from our registry.
108
     */
109 30
    final protected function getTypeFromRegistry(string $type, bool $isEntityId): NamedType
110
    {
111 30
        if ($type === 'ID') {
112 1
            return Type::id();
113
        }
114
115 30
        if ($this->types->isEntity($type) && $isEntityId) {
116
            // @phpstan-ignore-next-line
117 8
            return $this->types->getId($type);
118
        }
119
120 30
        if ($this->types->isEntity($type) && !$isEntityId) {
121
            // @phpstan-ignore-next-line
122 9
            return $this->types->getOutput($type);
123
        }
124
125 28
        return $this->types->get($type);
126
    }
127
}
128