Passed
Pull Request — main (#39)
by Tom
12:32
created

MetadataFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 5
dl 0
loc 7
rs 10
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\Hydrator\Strategy;
11
use ArrayObject;
12
use Doctrine\ORM\EntityManager;
13
use Doctrine\ORM\Mapping\ClassMetadata;
14
use League\Event\EventDispatcher;
15
use ReflectionClass;
16
17
use function assert;
18
use function count;
19
20
/**
21
 * Build metadata for entities
22
 */
23
class MetadataFactory extends AbstractMetadataFactory
24
{
25
    public function __construct(
26
        protected ArrayObject $metadata,
27
        protected EntityManager $entityManager,
28
        protected Config $config,
29
        protected GlobalEnable $globalEnable,
30
        protected EventDispatcher $eventDispatcher,
31
    ) {
32
    }
33
34
    /**
35
     * Build metadata for all entities and return it
36
     */
37
    public function __invoke(): ArrayObject
38
    {
39
        if (count($this->metadata)) {
40
            return $this->metadata;
41
        }
42
43
        // Fetch all entity classes from the entity manager
44
        $entityClasses = [];
45
        foreach ($this->entityManager->getMetadataFactory()->getAllMetadata() as $metadata) {
46
            $entityClasses[] = $metadata->getName();
47
        }
48
49
        // If global enable is set, use the GlobalEnable class to build metadata
50
        if ($this->config->getGlobalEnable()) {
51
            $this->metadata = ($this->globalEnable)($entityClasses);
52
53
            return $this->metadata;
54
        }
55
56
        // Build metadata for each entity class
57
        foreach ($entityClasses as $entityClass) {
58
            $reflectionClass = new ReflectionClass($entityClass);
59
60
            $entityClassMetadata = $this->entityManager
61
                ->getMetadataFactory()
62
                ->getMetadataFor($reflectionClass->getName());
63
64
            $this->buildMetadataForEntity($reflectionClass);
65
            $this->buildMetadataForFields($entityClassMetadata, $reflectionClass);
66
            $this->buildMetadataForAssociations($entityClassMetadata, $reflectionClass);
67
        }
68
69
        // Fire the metadata.build event
70
        $this->eventDispatcher->dispatch(
71
            new Metadata($this->metadata, 'metadata.build'),
72
        );
73
74
        return $this->metadata;
75
    }
76
77
    /**
78
     * Using the entity class attributes, generate the metadata.
79
     * The buildmetadata* functions exist to simplify the buildMetadata
80
     * function.
81
     */
82
    private function buildMetadataForEntity(ReflectionClass $reflectionClass): void
83
    {
84
        $entityInstance = null;
85
86
        // Fetch attributes for the entity class filterd by Attribute\Entity
87
        foreach ($reflectionClass->getAttributes(Attribute\Entity::class) as $attribute) {
88
            $instance = $attribute->newInstance();
89
90
            // Only process attributes for the Config group
91
            if ($instance->getGroup() !== $this->config->getGroup()) {
92
                continue;
93
            }
94
95
            // Only one matching instance per group is allowed
96
            assert(
97
                ! $entityInstance,
98
                'Duplicate attribute found for entity '
99
                . $reflectionClass->getName() . ', group ' . $instance->getGroup(),
100
            );
101
            $entityInstance = $instance;
102
103
            // Save entity-level metadata
104
            $this->metadata[$reflectionClass->getName()] = [
105
                'entityClass' => $reflectionClass->getName(),
106
                'byValue' => $this->config->getGlobalByValue() ?? $instance->getByValue(),
107
                'limit' => $instance->getLimit(),
108
                'hydratorNamingStrategy' => $instance->getHydratorNamingStrategy(),
109
                'fields' => [],
110
                'hydratorFilters' => $instance->getHydratorFilters(),
111
                'excludeFilters' => $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
                    'description' => $instance->getDescription(),
149
                    'type' => $instance->getType() ?? $entityClassMetadata->getTypeOfField($fieldName),
150
                    'hydratorStrategy' => $instance->getHydratorStrategy() ??
151
                        $this->getDefaultStrategy($entityClassMetadata->getTypeOfField($fieldName)),
152
                    'excludeFilters' => $instance->getExcludeFilters(),
153
                ];
154
155
                $this->metadata[$reflectionClass->getName()]['fields'][$fieldName] = $fieldMetadata;
156
            }
157
        }
158
    }
159
160
    /**
161
     * Build the metadata for each field in an entity based on the Attribute\Association
162
     */
163
    private function buildMetadataForAssociations(
164
        ClassMetadata $entityClassMetadata,
0 ignored issues
show
Unused Code introduced by
The parameter $entityClassMetadata is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

164
        /** @scrutinizer ignore-unused */ ClassMetadata $entityClassMetadata,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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' => $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