Entity   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Importance

Changes 12
Bugs 0 Features 0
Metric Value
eloc 94
c 12
b 0
f 0
dl 0
loc 196
rs 10
wmc 24

10 Methods

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