Passed
Push — main ( d0cb76...ea9e69 )
by Tom
01:11 queued 12s
created

MetadataFactory::appendGroupSuffix()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\GraphQL\Metadata;
6
7
use ApiSkeletons\Doctrine\GraphQL\Attribute;
8
use ApiSkeletons\Doctrine\GraphQL\Config;
9
use ApiSkeletons\Doctrine\GraphQL\Hydrator\Strategy;
10
use Doctrine\ORM\EntityManager;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Doctrine\ORM\Mapping\ClassMetadataInfo;
13
use Psr\Container\ContainerInterface;
14
use ReflectionClass;
15
16
use function assert;
17
18
class MetadataFactory extends AbstractMetadataFactory
19
{
20
    protected Metadata|null $metadata = null;
21
    protected EntityManager $entityManager;
22
    protected Config $config;
23
24
    /** @param mixed|null $metadataConfig */
25
    public function __construct(protected ContainerInterface $container, protected array|null $metadataConfig)
26
    {
27
        $this->entityManager = $container->get(EntityManager::class);
28
        $this->config        = $container->get(Config::class);
29
30
        if (empty($metadataConfig)) {
31
            return;
32
        }
33
34
        $this->metadata = new Metadata($this->container, $metadataConfig);
35
    }
36
37
    public function getMetadata(): Metadata
38
    {
39
        if ($this->metadata) {
40
            return $this->metadata;
41
        }
42
43
        return $this->buildMetadata();
44
    }
45
46
    protected function buildMetadata(): Metadata
47
    {
48
        $entityClasses = [];
49
        foreach ($this->entityManager->getMetadataFactory()->getAllMetadata() as $metadata) {
50
            $entityClasses[] = $metadata->getName();
51
        }
52
53
        if ($this->config->getGlobalEnable()) {
54
            $globalEnable = $this->container->get(GlobalEnable::class);
55
56
            return new Metadata($this->container, $globalEnable($entityClasses));
57
        }
58
59
        foreach ($entityClasses as $entityClass) {
60
            $reflectionClass     = new ReflectionClass($entityClass);
61
            $entityClassMetadata = $this->entityManager
62
                ->getMetadataFactory()->getMetadataFor($reflectionClass->getName());
63
64
            $this->buildMetadataConfigForEntity($reflectionClass);
65
            $this->buildMetadataConfigForFields($entityClassMetadata, $reflectionClass);
66
            $this->buildMetadataConfigForAssociations($entityClassMetadata, $reflectionClass);
67
        }
68
69
        $this->metadata = new Metadata($this->container, $this->metadataConfig);
70
71
        return $this->metadata;
72
    }
73
74
    /**
75
     * Using the entity class attributes, generate the metadataConfig.
76
     * The buildMetadataConfig* functions exist to simplify the buildMetadata
77
     * function.
78
     */
79
    private function buildMetadataConfigForEntity(ReflectionClass $reflectionClass): void
80
    {
81
        $entityInstance = null;
82
83
        // Fetch attributes for the entity class filterd by Attribute\Entity
84
        foreach ($reflectionClass->getAttributes(Attribute\Entity::class) as $attribute) {
85
            $instance = $attribute->newInstance();
86
87
            // Only process attributes for the Config group
88
            if ($instance->getGroup() !== $this->config->getGroup()) {
89
                continue;
90
            }
91
92
            // Only one matching instance per group is allowed
93
            assert(
94
                ! $entityInstance,
95
                'Duplicate attribute found for entity '
96
                . $reflectionClass->getName() . ', group ' . $instance->getGroup(),
97
            );
98
            $entityInstance = $instance;
99
100
            // Save entity-level metadata
101
            $this->metadataConfig[$reflectionClass->getName()] = [
102
                'entityClass' => $reflectionClass->getName(),
103
                'byValue' => $this->config->getGlobalByValue() ?? $instance->getByValue(),
104
                'namingStrategy' => $instance->getNamingStrategy(),
105
                'fields' => [],
106
                'filters' => $instance->getFilters(),
107
                'excludeCriteria' => $instance->getExcludeCriteria(),
108
                'description' => $instance->getDescription(),
109
                'typeName' => $instance->getTypeName()
110
                    ? $this->appendGroupSuffix($instance->getTypeName()) :
111
                    $this->getTypeName($reflectionClass->getName()),
112
            ];
113
        }
114
    }
115
116
    private function buildMetadataConfigForFields(
117
        ClassMetadata $entityClassMetadata,
118
        ReflectionClass $reflectionClass,
119
    ): void {
120
        foreach ($entityClassMetadata->getFieldNames() as $fieldName) {
121
            $fieldInstance   = null;
122
            $reflectionField = $reflectionClass->getProperty($fieldName);
123
124
            foreach ($reflectionField->getAttributes(Attribute\Field::class) as $attribute) {
125
                $instance = $attribute->newInstance();
126
127
                // Only process attributes for the same group
128
                if ($instance->getGroup() !== $this->config->getGroup()) {
129
                    continue;
130
                }
131
132
                // Only one matching instance per group is allowed
133
                assert(
134
                    ! $fieldInstance,
135
                    'Duplicate attribute found for field '
136
                    . $fieldName . ', group ' . $instance->getGroup(),
137
                );
138
                $fieldInstance = $instance;
139
140
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$fieldName]['description'] =
141
                    $instance->getDescription();
142
143
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$fieldName]['type'] =
144
                    $instance->getType() ?? $entityClassMetadata->getTypeOfField($fieldName);
145
146
                if ($instance->getStrategy()) {
147
                    $this->metadataConfig[$reflectionClass->getName()]['fields'][$fieldName]['strategy'] =
148
                        $instance->getStrategy();
149
150
                    continue;
151
                }
152
153
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$fieldName]['excludeCriteria'] =
154
                    $instance->getExcludeCriteria();
155
156
                // Set default strategy based on field type
157
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$fieldName]['strategy'] =
158
                    $this->getDefaultStrategy($entityClassMetadata->getTypeOfField($fieldName));
159
            }
160
        }
161
    }
162
163
    private function buildMetadataConfigForAssociations(
164
        ClassMetadata $entityClassMetadata,
165
        ReflectionClass $reflectionClass,
166
    ): void {
167
        // Fetch attributes for associations
168
        $associationNames = $this->entityManager->getMetadataFactory()
169
            ->getMetadataFor($reflectionClass->getName())->getAssociationNames();
170
171
        foreach ($associationNames as $associationName) {
172
            $associationInstance   = null;
173
            $reflectionAssociation = $reflectionClass->getProperty($associationName);
174
175
            foreach ($reflectionAssociation->getAttributes(Attribute\Association::class) as $attribute) {
176
                $instance = $attribute->newInstance();
177
178
                // Only process attributes for the same group
179
                if ($instance->getGroup() !== $this->config->getGroup()) {
180
                    continue;
181
                }
182
183
                // Only one matching instance per group is allowed
184
                assert(
185
                    ! $associationInstance,
186
                    'Duplicate attribute found for association '
187
                    . $associationName . ', group ' . $instance->getGroup(),
188
                );
189
                $associationInstance = $instance;
190
191
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['description']             =
192
                    $instance->getDescription();
193
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['excludeCriteria']         =
194
                    $instance->getExcludeCriteria();
195
                $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['filterCriteriaEventName'] =
196
                    $instance->getFilterCriteriaEventName();
197
198
                if ($instance->getStrategy()) {
199
                    $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['strategy']
200
                        = $instance->getStrategy();
201
202
                    continue;
203
                }
204
205
                $mapping = $entityClassMetadata->getAssociationMapping($associationName);
206
207
                // See comment on NullifyOwningAssociation for details of why this is done
208
                if ($mapping['type'] === ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
209
                    $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['strategy'] =
210
                        Strategy\NullifyOwningAssociation::class;
211
                } else {
212
                    $this->metadataConfig[$reflectionClass->getName()]['fields'][$associationName]['strategy'] =
213
                        Strategy\AssociationDefault::class;
214
                }
215
            }
216
        }
217
    }
218
}
219