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

CriteriaFactory::get()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 5
eloc 23
c 5
b 0
f 0
nc 10
nop 4
dl 0
loc 45
rs 9.2408
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\GraphQL\Criteria;
6
7
use ApiSkeletons\Doctrine\GraphQL\Config;
8
use ApiSkeletons\Doctrine\GraphQL\Criteria\Type\FiltersInputObjectType;
9
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
10
use ApiSkeletons\Doctrine\GraphQL\Type\TypeManager;
11
use Doctrine\ORM\EntityManager;
12
use Doctrine\ORM\Mapping\ClassMetadataInfo;
13
use GraphQL\Type\Definition\InputObjectType;
14
use GraphQL\Type\Definition\Type;
15
use League\Event\EventDispatcher;
16
17
use function array_filter;
18
use function array_keys;
19
use function count;
20
use function in_array;
21
22
class CriteriaFactory
23
{
24
    public function __construct(
25
        protected Config $config,
26
        protected EntityManager $entityManager,
27
        protected TypeManager $typeManager,
28
        protected EventDispatcher $eventDispatcher,
29
    ) {
30
    }
31
32
    /** @param mixed[]|null $associationMetadata */
33
    public function get(
34
        Entity $targetEntity,
35
        Entity|null $owningEntity = null,
36
        string|null $associationName = null,
37
        array|null $associationMetadata = null,
38
    ): InputObjectType {
39
        $typeName = $owningEntity ?
40
            $owningEntity->getTypeName() . '_' . $associationName . '_filter'
41
            : $targetEntity->getTypeName() . '_filter';
42
43
        if ($this->typeManager->has($typeName)) {
44
            return $this->typeManager->get($typeName);
45
        }
46
47
        $fields         = [];
48
        $entityMetadata = $targetEntity->getMetadataConfig();
49
        $allowedFilters = Filters::toArray();
50
51
        // Limit entity filters
52
        if ($entityMetadata['excludeCriteria']) {
53
            $excludeCriteria = $entityMetadata['excludeCriteria'];
54
            $allowedFilters  = array_filter($allowedFilters, static function ($value) use ($excludeCriteria) {
55
                return ! in_array($value, $excludeCriteria);
56
            });
57
        }
58
59
        // Limit association filters
60
        if ($associationName) {
61
            $excludeCriteria = $associationMetadata['excludeCriteria'];
62
            $allowedFilters  = array_filter($allowedFilters, static function ($value) use ($excludeCriteria) {
63
                return ! in_array($value, $excludeCriteria);
64
            });
65
        }
66
67
        $this->addFields($targetEntity, $typeName, $allowedFilters, $fields);
68
        $this->addAssociations($targetEntity, $typeName, $allowedFilters, $fields);
69
70
        $inputObject = new InputObjectType([
71
            'name' => $typeName,
72
            'fields' => static fn () => $fields,
73
        ]);
74
75
        $this->typeManager->set($typeName, $inputObject);
76
77
        return $inputObject;
78
    }
79
80
    /**
81
     * @param string[]                           $allowedFilters
82
     * @param array<int, FiltersInputObjectType> $fields
83
     */
84
    protected function addFields(Entity $targetEntity, string $typeName, array $allowedFilters, array &$fields): void
85
    {
86
        $classMetadata  = $this->entityManager->getClassMetadata($targetEntity->getEntityClass());
87
        $entityMetadata = $targetEntity->getMetadataConfig();
88
89
        foreach ($classMetadata->getFieldNames() as $fieldName) {
90
            // Only process fields that are in the graphql metadata
91
            if (! in_array($fieldName, array_keys($entityMetadata['fields']))) {
92
                continue;
93
            }
94
95
            $graphQLType = $this->typeManager
96
                ->get($entityMetadata['fields'][$fieldName]['type']);
97
98
            if ($classMetadata->isIdentifier($fieldName)) {
99
                $graphQLType = Type::id();
100
            }
101
102
            // Limit field filters
103
            if (
104
                isset($entityMetadata['fields'][$fieldName]['excludeCriteria'])
105
                && count($entityMetadata['fields'][$fieldName]['excludeCriteria'])
106
            ) {
107
                $fieldExcludeCriteria = $entityMetadata['fields'][$fieldName]['excludeCriteria'];
108
                $allowedFilters       = array_filter(
109
                    $allowedFilters,
110
                    static function ($value) use ($fieldExcludeCriteria) {
111
                        return ! in_array($value, $fieldExcludeCriteria);
112
                    },
113
                );
114
            }
115
116
            $fields[$fieldName] = [
117
                'name'        => $fieldName,
118
                'type'        => new FiltersInputObjectType($typeName, $fieldName, $graphQLType, $allowedFilters),
119
                'description' => 'Filters for ' . $fieldName,
120
            ];
121
        }
122
    }
123
124
    /**
125
     * @param string[]                           $allowedFilters
126
     * @param array<int, FiltersInputObjectType> $fields
127
     */
128
    protected function addAssociations(Entity $targetEntity, string $typeName, array $allowedFilters, array &$fields): void
129
    {
130
        $classMetadata  = $this->entityManager->getClassMetadata($targetEntity->getEntityClass());
131
        $entityMetadata = $targetEntity->getMetadataConfig();
132
133
        // Add eq filter for to-one associations
134
        foreach ($classMetadata->getAssociationNames() as $associationName) {
135
            // Only process fields which are in the graphql metadata
136
            if (! in_array($associationName, array_keys($entityMetadata['fields']))) {
137
                continue;
138
            }
139
140
            $associationMetadata = $classMetadata->getAssociationMapping($associationName);
141
            $graphQLType         = Type::id();
142
            switch ($associationMetadata['type']) {
143
                case ClassMetadataInfo::ONE_TO_ONE:
144
                case ClassMetadataInfo::MANY_TO_ONE:
145
                case ClassMetadataInfo::TO_ONE:
146
                    // eq filter is for association:value
147
                    if (in_array(Filters::EQ, $allowedFilters)) {
148
                        $fields[$associationName] = [
149
                            'name' => $associationName,
150
                            'type' => new FiltersInputObjectType($typeName, $associationName, $graphQLType, ['eq']),
151
                            'description' => 'Filters for ' . $associationName,
152
                        ];
153
                    }
154
            }
155
        }
156
    }
157
}
158