Passed
Push — 12.2.x ( b01f09...547718 )
by Tom
02:24
created

MetadataFactory::getConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\ORM\GraphQL\Metadata;
6
7
use ApiSkeletons\Doctrine\ORM\GraphQL\Attribute;
8
use ApiSkeletons\Doctrine\ORM\GraphQL\Config;
9
use ApiSkeletons\Doctrine\ORM\GraphQL\Event\Metadata;
10
use ApiSkeletons\Doctrine\ORM\GraphQL\Filter\Filters;
11
use ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy;
12
use ApiSkeletons\Doctrine\ORM\GraphQL\Metadata\Common\MetadataFactory as CommonMetadataFactory;
13
use ArrayObject;
14
use Doctrine\ORM\EntityManager;
15
use Doctrine\ORM\Mapping\ClassMetadata;
16
use League\Event\EventDispatcher;
17
use ReflectionClass;
18
19
use function assert;
20
use function count;
21
22
/**
23
 * Build metadata for entities
24
 */
25
class MetadataFactory extends CommonMetadataFactory
26
{
27
    public function __construct(
28
        protected ArrayObject $metadata,
29
        protected readonly EntityManager $entityManager,
30
        protected readonly Config $config,
31
        protected readonly GlobalEnable $globalEnable,
32
        protected readonly EventDispatcher $eventDispatcher,
33
    ) {
34
    }
35
36
    /**
37
     * Build metadata for all entities and return it
38
     */
39
    public function __invoke(): ArrayObject
40
    {
41
        if (count($this->metadata)) {
42
            return $this->metadata;
43
        }
44
45
        // Fetch all entity classes from the entity manager
46
        $entityClasses = [];
47
        foreach ($this->entityManager->getMetadataFactory()->getAllMetadata() as $metadata) {
48
            $entityClasses[] = $metadata->getName();
49
        }
50
51
        // If global enable is set, use the GlobalEnable class to build metadata
52
        if ($this->config->getGlobalEnable()) {
53
            $this->metadata = ($this->globalEnable)($entityClasses);
54
55
            return $this->metadata;
56
        }
57
58
        // Build metadata for each entity class
59
        foreach ($entityClasses as $entityClass) {
60
            $reflectionClass = new ReflectionClass($entityClass);
61
62
            $entityClassMetadata = $this->entityManager
63
                ->getMetadataFactory()
64
                ->getMetadataFor($reflectionClass->getName());
65
66
            $this->buildMetadataForEntity($reflectionClass);
67
            $this->buildMetadataForFields($entityClassMetadata, $reflectionClass);
68
            $this->buildMetadataForAssociations($reflectionClass);
69
        }
70
71
        // Fire the metadata.build event
72
        $this->eventDispatcher->dispatch(
73
            new Metadata($this->metadata, 'metadata.build'),
74
        );
75
76
        return $this->metadata;
77
    }
78
79
    /**
80
     * Using the entity class attributes, generate the metadata.
81
     * The buildmetadata* functions exist to simplify the buildMetadata
82
     * function.
83
     */
84
    private function buildMetadataForEntity(ReflectionClass $reflectionClass): void
85
    {
86
        $entityInstance = null;
87
88
        // Fetch attributes for the entity class filterd by Attribute\Entity
89
        foreach ($reflectionClass->getAttributes(Attribute\Entity::class) as $attribute) {
90
            $instance = $attribute->newInstance();
91
92
            // Only process attributes for the Config group
93
            if ($instance->getGroup() !== $this->config->getGroup()) {
94
                continue;
95
            }
96
97
            // Only one matching instance per group is allowed
98
            assert(
99
                ! $entityInstance,
100
                'Duplicate attribute found for entity '
101
                . $reflectionClass->getName() . ', group ' . $instance->getGroup(),
102
            );
103
            $entityInstance = $instance;
104
105
            // Save entity-level metadata
106
            $this->metadata[$reflectionClass->getName()] = [
107
                'entityClass' => $reflectionClass->getName(),
108
                'byValue' => $this->config->getGlobalByValue() ?? $instance->getByValue(),
109
                'limit' => $instance->getLimit(),
110
                'fields' => [],
111
                'excludeFilters' => Filters::toStringArray($instance->getExcludeFilters()),
112
                'description' => $instance->getDescription(),
113
                'typeName' => $instance->getTypeName()
114
                    ? $this->appendGroupSuffix($instance->getTypeName()) :
115
                    $this->getTypeName($reflectionClass->getName()),
116
            ];
117
        }
118
    }
119
120
    /**
121
     * Build the metadata for each field in an entity based on the Attribute\Field
122
     */
123
    private function buildMetadataForFields(
124
        ClassMetadata $entityClassMetadata,
125
        ReflectionClass $reflectionClass,
126
    ): void {
127
        foreach ($entityClassMetadata->getFieldNames() as $fieldName) {
128
            $fieldInstance   = null;
129
            $reflectionField = $reflectionClass->getProperty($fieldName);
130
131
            foreach ($reflectionField->getAttributes(Attribute\Field::class) as $attribute) {
132
                $instance = $attribute->newInstance();
133
134
                // Only process attributes for the same group
135
                if ($instance->getGroup() !== $this->config->getGroup()) {
136
                    continue;
137
                }
138
139
                // Only one matching instance per group is allowed
140
                assert(
141
                    ! $fieldInstance,
142
                    'Duplicate attribute found for field '
143
                    . $fieldName . ', group ' . $instance->getGroup(),
144
                );
145
                $fieldInstance = $instance;
146
147
                $fieldMetadata = [
148
                    'alias' => $instance->getAlias(),
149
                    'description' => $instance->getDescription(),
150
                    'type' => $instance->getType() ?? $entityClassMetadata->getTypeOfField($fieldName),
151
                    'hydratorStrategy' => $instance->getHydratorStrategy() ??
152
                        $this->getDefaultStrategy($entityClassMetadata->getTypeOfField($fieldName)),
153
                    'excludeFilters' => Filters::toStringArray($instance->getExcludeFilters()),
154
                ];
155
156
                $this->metadata[$reflectionClass->getName()]['fields'][$fieldName] = $fieldMetadata;
157
            }
158
        }
159
    }
160
161
    /**
162
     * Build the metadata for each field in an entity based on the Attribute\Association
163
     */
164
    private function buildMetadataForAssociations(
165
        ReflectionClass $reflectionClass,
166
    ): void {
167
        // Fetch attributes for associations
168
        $associationNames = $this->entityManager->getMetadataFactory()
169
            ->getMetadataFor($reflectionClass->getName())
170
            ->getAssociationNames();
171
172
        foreach ($associationNames as $associationName) {
173
            $associationInstance   = null;
174
            $reflectionAssociation = $reflectionClass->getProperty($associationName);
175
176
            foreach ($reflectionAssociation->getAttributes(Attribute\Association::class) as $attribute) {
177
                $instance = $attribute->newInstance();
178
179
                // Only process attributes for the same group
180
                if ($instance->getGroup() !== $this->config->getGroup()) {
181
                    continue;
182
                }
183
184
                // Only one matching instance per group is allowed
185
                assert(
186
                    ! $associationInstance,
187
                    'Duplicate attribute found for association '
188
                    . $associationName . ', group ' . $instance->getGroup(),
189
                );
190
191
                $associationInstance = $instance;
192
193
                $associationMetadata = [
194
                    'alias' => $instance->getAlias(),
195
                    'limit' => $instance->getLimit(),
196
                    'description' => $instance->getDescription(),
197
                    'excludeFilters' => Filters::toStringArray($instance->getExcludeFilters()),
198
                    'criteriaEventName' => $instance->getCriteriaEventName(),
199
                    'hydratorStrategy' => $instance->getHydratorStrategy() ??
200
                        Strategy\AssociationDefault::class,
201
                ];
202
203
                $this->metadata[$reflectionClass->getName()]['fields'][$associationName] = $associationMetadata;
204
            }
205
        }
206
    }
207
208
    protected function getConfig(): Config
209
    {
210
        return $this->config;
211
    }
212
}
213