Passed
Branch main (0cd36c)
by Tom
03:14 queued 39s
created

MetadataFactory::buildMetadataForFields()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 4
nop 2
dl 0
loc 33
rs 9.6333
c 0
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 ArrayObject;
13
use Doctrine\ORM\EntityManager;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use League\Event\EventDispatcher;
16
use ReflectionClass;
17
18
use function assert;
19
use function count;
20
21
/**
22
 * Build metadata for entities
23
 */
24
class MetadataFactory extends AbstractMetadataFactory
25
{
26
    public function __construct(
27
        protected ArrayObject $metadata,
28
        protected EntityManager $entityManager,
29
        protected Config $config,
30
        protected GlobalEnable $globalEnable,
31
        protected EventDispatcher $eventDispatcher,
32
    ) {
33
    }
34
35
    /**
36
     * Build metadata for all entities and return it
37
     */
38
    public function __invoke(): ArrayObject
39
    {
40
        if (count($this->metadata)) {
41
            return $this->metadata;
42
        }
43
44
        // Fetch all entity classes from the entity manager
45
        $entityClasses = [];
46
        foreach ($this->entityManager->getMetadataFactory()->getAllMetadata() as $metadata) {
47
            $entityClasses[] = $metadata->getName();
48
        }
49
50
        // If global enable is set, use the GlobalEnable class to build metadata
51
        if ($this->config->getGlobalEnable()) {
52
            $this->metadata = ($this->globalEnable)($entityClasses);
53
54
            return $this->metadata;
55
        }
56
57
        // Build metadata for each entity class
58
        foreach ($entityClasses as $entityClass) {
59
            $reflectionClass = new ReflectionClass($entityClass);
60
61
            $entityClassMetadata = $this->entityManager
62
                ->getMetadataFactory()
63
                ->getMetadataFor($reflectionClass->getName());
64
65
            $this->buildMetadataForEntity($reflectionClass);
66
            $this->buildMetadataForFields($entityClassMetadata, $reflectionClass);
67
            $this->buildMetadataForAssociations($reflectionClass);
68
        }
69
70
        // Fire the metadata.build event
71
        $this->eventDispatcher->dispatch(
72
            new Metadata($this->metadata, 'metadata.build'),
73
        );
74
75
        return $this->metadata;
76
    }
77
78
    /**
79
     * Using the entity class attributes, generate the metadata.
80
     * The buildmetadata* functions exist to simplify the buildMetadata
81
     * function.
82
     */
83
    private function buildMetadataForEntity(ReflectionClass $reflectionClass): void
84
    {
85
        $entityInstance = null;
86
87
        // Fetch attributes for the entity class filterd by Attribute\Entity
88
        foreach ($reflectionClass->getAttributes(Attribute\Entity::class) as $attribute) {
89
            $instance = $attribute->newInstance();
90
91
            // Only process attributes for the Config group
92
            if ($instance->getGroup() !== $this->config->getGroup()) {
93
                continue;
94
            }
95
96
            // Only one matching instance per group is allowed
97
            assert(
98
                ! $entityInstance,
99
                'Duplicate attribute found for entity '
100
                . $reflectionClass->getName() . ', group ' . $instance->getGroup(),
101
            );
102
            $entityInstance = $instance;
103
104
            // Save entity-level metadata
105
            $this->metadata[$reflectionClass->getName()] = [
106
                'entityClass' => $reflectionClass->getName(),
107
                'byValue' => $this->config->getGlobalByValue() ?? $instance->getByValue(),
108
                'limit' => $instance->getLimit(),
109
                'hydratorNamingStrategy' => $instance->getHydratorNamingStrategy(),
110
                'fields' => [],
111
                'hydratorFilters' => $instance->getHydratorFilters(),
112
                'excludeFilters' => Filters::toStringArray($instance->getExcludeFilters()),
113
                'description' => $instance->getDescription(),
114
                'typeName' => $instance->getTypeName()
115
                    ? $this->appendGroupSuffix($instance->getTypeName()) :
116
                    $this->getTypeName($reflectionClass->getName()),
117
            ];
118
        }
119
    }
120
121
    /**
122
     * Build the metadata for each field in an entity based on the Attribute\Field
123
     */
124
    private function buildMetadataForFields(
125
        ClassMetadata $entityClassMetadata,
126
        ReflectionClass $reflectionClass,
127
    ): void {
128
        foreach ($entityClassMetadata->getFieldNames() as $fieldName) {
129
            $fieldInstance   = null;
130
            $reflectionField = $reflectionClass->getProperty($fieldName);
131
132
            foreach ($reflectionField->getAttributes(Attribute\Field::class) as $attribute) {
133
                $instance = $attribute->newInstance();
134
135
                // Only process attributes for the same group
136
                if ($instance->getGroup() !== $this->config->getGroup()) {
137
                    continue;
138
                }
139
140
                // Only one matching instance per group is allowed
141
                assert(
142
                    ! $fieldInstance,
143
                    'Duplicate attribute found for field '
144
                    . $fieldName . ', group ' . $instance->getGroup(),
145
                );
146
                $fieldInstance = $instance;
147
148
                $fieldMetadata = [
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
                    'limit' => $instance->getLimit(),
195
                    'description' => $instance->getDescription(),
196
                    'excludeFilters' => Filters::toStringArray($instance->getExcludeFilters()),
197
                    'criteriaEventName' => $instance->getCriteriaEventName(),
198
                    'hydratorStrategy' => $instance->getHydratorStrategy() ??
199
                        Strategy\AssociationDefault::class,
200
                ];
201
202
                $this->metadata[$reflectionClass->getName()]['fields'][$associationName] = $associationMetadata;
203
            }
204
        }
205
    }
206
}
207