Passed
Push — main ( 3e26d7...787f8a )
by Tom
01:02 queued 12s
created

Entity::addFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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