Passed
Push — main ( 5c74ff...777132 )
by Tom
59s queued 15s
created

Entity::getMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\GraphQL\Type;
6
7
use ApiSkeletons\Doctrine\GraphQL\AbstractContainer;
8
use ApiSkeletons\Doctrine\GraphQL\Buildable;
9
use ApiSkeletons\Doctrine\GraphQL\Criteria\CriteriaFactory;
10
use ApiSkeletons\Doctrine\GraphQL\Event\EntityDefinition;
11
use ApiSkeletons\Doctrine\GraphQL\Hydrator\HydratorFactory;
12
use ApiSkeletons\Doctrine\GraphQL\Resolve\FieldResolver;
13
use ApiSkeletons\Doctrine\GraphQL\Resolve\ResolveCollectionFactory;
14
use ArrayObject;
15
use Doctrine\ORM\EntityManager;
16
use Doctrine\ORM\Mapping\ClassMetadataInfo;
17
use Doctrine\ORM\Mapping\MappingException;
18
use GraphQL\Error\Error;
19
use GraphQL\Type\Definition\ObjectType;
20
use Laminas\Hydrator\HydratorInterface;
21
use League\Event\EventDispatcher;
22
23
use function array_keys;
24
use function assert;
25
use function in_array;
26
27
class Entity implements Buildable
28
{
29
    /** @var mixed[]  */
30
    protected array $metadata;
31
32
    protected CriteriaFactory $criteriaFactory;
33
34
    protected EntityManager $entityManager;
35
36
    protected EventDispatcher $eventDispatcher;
37
38
    protected FieldResolver $fieldResolver;
39
40
    protected HydratorFactory $hydratorFactory;
41
42
    protected ResolveCollectionFactory $collectionFactory;
43
44
    protected TypeManager $typeManager;
45
46
    /** @param mixed[] $params */
47
    public function __construct(AbstractContainer $container, string $typeName, array $params)
48
    {
49
        assert($container instanceof TypeManager);
50
        $container = $container->getContainer();
51
52
        $this->collectionFactory = $container->get(ResolveCollectionFactory::class);
53
        $this->criteriaFactory   = $container->get(CriteriaFactory::class);
54
        $this->entityManager     = $container->get(EntityManager::class);
55
        $this->eventDispatcher   = $container->get(EventDispatcher::class);
56
        $this->fieldResolver     = $container->get(FieldResolver::class);
57
        $this->hydratorFactory   = $container->get(HydratorFactory::class);
58
        $this->typeManager       = $container->get(TypeManager::class);
59
60
        if (! isset($container->get('metadata')[$typeName])) {
61
            throw new Error(
62
                'Entity ' . $typeName . ' is not mapped in the metadata',
63
            );
64
        }
65
66
        $this->metadata = $container->get('metadata')[$typeName];
67
    }
68
69
    public function __invoke(): ObjectType
70
    {
71
        return $this->getGraphQLType();
72
    }
73
74
    public function getHydrator(): HydratorInterface
75
    {
76
        return $this->hydratorFactory->get($this->getEntityClass());
77
    }
78
79
    public function getTypeName(): string
80
    {
81
        return $this->metadata['typeName'];
82
    }
83
84
    public function getDescription(): string|null
85
    {
86
        return $this->metadata['description'];
87
    }
88
89
    /** @return mixed[] */
90
    public function getMetadata(): array
91
    {
92
        return $this->metadata;
93
    }
94
95
    public function getEntityClass(): string
96
    {
97
        return $this->metadata['entityClass'];
98
    }
99
100
    /**
101
     * Build the type for the current entity
102
     *
103
     * @throws MappingException
104
     */
105
    protected function getGraphQLType(): ObjectType
106
    {
107
        if ($this->typeManager->has($this->getTypeName())) {
108
            return $this->typeManager->get($this->getTypeName());
109
        }
110
111
        $fields = [];
112
113
        $this->addFields($fields);
114
        $this->addAssociations($fields);
115
116
        $arrayObject = new ArrayObject([
117
            'name' => $this->getTypeName(),
118
            'description' => $this->getDescription(),
119
            'fields' => static fn () => $fields,
120
            'resolveField' => $this->fieldResolver,
121
        ]);
122
123
        /**
124
         * Dispatch event to allow modifications to the ObjectType definition
125
         */
126
        $this->eventDispatcher->dispatch(
127
            new EntityDefinition($arrayObject, $this->getEntityClass() . '.definition'),
128
        );
129
130
        /** @psalm-suppress InvalidArgument */
131
        $objectType = new ObjectType($arrayObject->getArrayCopy());
132
        $this->typeManager->set($this->getTypeName(), $objectType);
133
134
        return $objectType;
135
    }
136
137
    /** @param array<int, mixed[]> $fields */
138
    protected function addFields(array &$fields): void
139
    {
140
        $classMetadata = $this->entityManager->getClassMetadata($this->getEntityClass());
141
142
        foreach ($classMetadata->getFieldNames() as $fieldName) {
143
            if (! in_array($fieldName, array_keys($this->metadata['fields']))) {
144
                continue;
145
            }
146
147
            $fields[$fieldName] = [
148
                'type' => $this->typeManager
149
                    ->get($this->getmetadata()['fields'][$fieldName]['type']),
150
                'description' => $this->metadata['fields'][$fieldName]['description'],
151
            ];
152
        }
153
    }
154
155
    /** @param array<int, mixed[]> $fields */
156
    protected function addAssociations(array &$fields): void
157
    {
158
        $classMetadata = $this->entityManager->getClassMetadata($this->getEntityClass());
159
160
        foreach ($classMetadata->getAssociationNames() as $associationName) {
161
            if (! in_array($associationName, array_keys($this->metadata['fields']))) {
162
                continue;
163
            }
164
165
            $associationMetadata = $classMetadata->getAssociationMapping($associationName);
166
167
            switch ($associationMetadata['type']) {
168
                case ClassMetadataInfo::ONE_TO_ONE:
169
                case ClassMetadataInfo::MANY_TO_ONE:
170
                case ClassMetadataInfo::TO_ONE:
171
                    $targetEntity             = $associationMetadata['targetEntity'];
172
                    $fields[$associationName] = function () use ($targetEntity) {
173
                        $entity = $this->typeManager->build(self::class, $targetEntity);
174
175
                        return [
176
                            'type' => $entity->getGraphQLType(),
177
                            'description' => $entity->getDescription(),
178
                        ];
179
                    };
180
                    break;
181
                case ClassMetadataInfo::ONE_TO_MANY:
182
                case ClassMetadataInfo::MANY_TO_MANY:
183
                case ClassMetadataInfo::TO_MANY:
184
                    $targetEntity             = $associationMetadata['targetEntity'];
185
                    $fields[$associationName] = function () use ($targetEntity, $associationName) {
186
                        $entity    = $this->typeManager->build(self::class, $targetEntity);
187
                        $shortName = $this->getTypeName() . '_' . $associationName;
188
189
                        return [
190
                            'type' => $this->typeManager->build(
191
                                Connection::class,
192
                                $shortName . '_Connection',
193
                                $entity->getGraphQLType(),
194
                            ),
195
                            'args' => [
196
                                'filter' => $this->criteriaFactory->get(
197
                                    $entity,
198
                                    $this,
199
                                    $associationName,
200
                                    $this->metadata['fields'][$associationName],
201
                                ),
202
                                'pagination' => $this->typeManager->get('pagination'),
203
                            ],
204
                            'description' => $this->metadata['fields'][$associationName]['description'],
205
                            'resolve' => $this->collectionFactory->get($entity),
206
                        ];
207
                    };
208
                    break;
209
            }
210
        }
211
    }
212
}
213