CriteriaFactory   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 61
c 5
b 0
f 0
dl 0
loc 131
rs 10
wmc 18

4 Methods

Rating   Name   Duplication   Size   Complexity  
B addFields() 0 36 6
A get() 0 46 4
A __construct() 0 6 1
B addAssociations() 0 24 7
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_diff;
18
use function array_filter;
19
use function array_keys;
20
use function array_merge;
21
use function array_unique;
22
use function count;
23
use function in_array;
24
25
use const SORT_REGULAR;
26
27
class CriteriaFactory
28
{
29
    public function __construct(
30
        protected Config $config,
31
        protected EntityManager $entityManager,
32
        protected TypeManager $typeManager,
33
        protected EventDispatcher $eventDispatcher,
34
    ) {
35
    }
36
37
    /** @param mixed[]|null $associationMetadata */
38
    public function get(
39
        Entity $targetEntity,
40
        Entity|null $owningEntity = null,
41
        string|null $associationName = null,
42
        array|null $associationMetadata = null,
43
    ): InputObjectType {
44
        $typeName = $owningEntity ?
45
            $owningEntity->getTypeName() . '_' . $associationName . '_filter'
46
            : $targetEntity->getTypeName() . '_filter';
47
48
        if ($this->typeManager->has($typeName)) {
49
            return $this->typeManager->get($typeName);
50
        }
51
52
        $fields          = [];
53
        $entityMetadata  = $targetEntity->getMetadata();
54
        $excludedFilters = array_unique(
55
            array_merge(
56
                $entityMetadata['excludeCriteria'],
57
                $this->config->getExcludeCriteria(),
58
            ),
59
            SORT_REGULAR,
60
        );
61
62
        // Limit filters
63
        $allowedFilters = array_diff(Filters::toArray(), $excludedFilters);
64
65
        // Limit association filters
66
        if ($associationName) {
67
            $excludeCriteria = $associationMetadata['excludeCriteria'];
68
            $allowedFilters  = array_filter($allowedFilters, static function ($value) use ($excludeCriteria) {
69
                return ! in_array($value, $excludeCriteria);
70
            });
71
        }
72
73
        $this->addFields($targetEntity, $typeName, $allowedFilters, $fields);
74
        $this->addAssociations($targetEntity, $typeName, $allowedFilters, $fields);
75
76
        $inputObject = new InputObjectType([
77
            'name' => $typeName,
78
            'fields' => static fn () => $fields,
79
        ]);
80
81
        $this->typeManager->set($typeName, $inputObject);
82
83
        return $inputObject;
84
    }
85
86
    /**
87
     * @param string[]                           $allowedFilters
88
     * @param array<int, FiltersInputObjectType> $fields
89
     */
90
    protected function addFields(Entity $targetEntity, string $typeName, array $allowedFilters, array &$fields): void
91
    {
92
        $classMetadata  = $this->entityManager->getClassMetadata($targetEntity->getEntityClass());
93
        $entityMetadata = $targetEntity->getMetadata();
94
95
        foreach ($classMetadata->getFieldNames() as $fieldName) {
96
            // Only process fields that are in the graphql metadata
97
            if (! in_array($fieldName, array_keys($entityMetadata['fields']))) {
98
                continue;
99
            }
100
101
            $graphQLType = $this->typeManager
102
                ->get($entityMetadata['fields'][$fieldName]['type']);
103
104
            if ($classMetadata->isIdentifier($fieldName)) {
105
                $graphQLType = Type::id();
106
            }
107
108
            // Limit field filters
109
            if (
110
                isset($entityMetadata['fields'][$fieldName]['excludeCriteria'])
111
                && count($entityMetadata['fields'][$fieldName]['excludeCriteria'])
112
            ) {
113
                $fieldExcludeCriteria = $entityMetadata['fields'][$fieldName]['excludeCriteria'];
114
                $allowedFilters       = array_filter(
115
                    $allowedFilters,
116
                    static function ($value) use ($fieldExcludeCriteria) {
117
                        return ! in_array($value, $fieldExcludeCriteria);
118
                    },
119
                );
120
            }
121
122
            $fields[$fieldName] = [
123
                'name'        => $fieldName,
124
                'type'        => new FiltersInputObjectType($typeName, $fieldName, $graphQLType, $allowedFilters),
125
                'description' => 'Filters for ' . $fieldName,
126
            ];
127
        }
128
    }
129
130
    /**
131
     * @param string[]                           $allowedFilters
132
     * @param array<int, FiltersInputObjectType> $fields
133
     */
134
    protected function addAssociations(Entity $targetEntity, string $typeName, array $allowedFilters, array &$fields): void
135
    {
136
        $classMetadata  = $this->entityManager->getClassMetadata($targetEntity->getEntityClass());
137
        $entityMetadata = $targetEntity->getMetadata();
138
139
        // Add eq filter for to-one associations
140
        foreach ($classMetadata->getAssociationNames() as $associationName) {
141
            // Only process fields which are in the graphql metadata
142
            if (! in_array($associationName, array_keys($entityMetadata['fields']))) {
143
                continue;
144
            }
145
146
            $associationMetadata = $classMetadata->getAssociationMapping($associationName);
147
            $graphQLType         = Type::id();
148
            switch ($associationMetadata['type']) {
149
                case ClassMetadataInfo::ONE_TO_ONE:
150
                case ClassMetadataInfo::MANY_TO_ONE:
151
                case ClassMetadataInfo::TO_ONE:
152
                    // eq filter is for association:value
153
                    if (in_array(Filters::EQ, $allowedFilters)) {
154
                        $fields[$associationName] = [
155
                            'name' => $associationName,
156
                            'type' => new FiltersInputObjectType($typeName, $associationName, $graphQLType, ['eq']),
157
                            'description' => 'Filters for ' . $associationName,
158
                        ];
159
                    }
160
            }
161
        }
162
    }
163
}
164