Passed
Pull Request — main (#102)
by Tom
03:19
created

src/Criteria/CriteriaFactory.php (7 issues)

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\FiltersInputType;
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 assert;
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
        if ($owningEntity) {
40
            $typeName = $owningEntity->getTypeName() . '_' . $associationName . '_filter';
41
        } else {
42
            $typeName = $targetEntity->getTypeName() . '_filter';
43
        }
44
45
        if ($this->typeManager->has($typeName)) {
46
            return $this->typeManager->get($typeName);
47
        }
48
49
        $fields         = [];
50
        $classMetadata  = $this->entityManager->getClassMetadata($targetEntity->getEntityClass());
51
        $entityMetadata = $targetEntity->getMetadataConfig();
52
53
        $allowedFilters = Filters::toArray();
54
55
        // Limit entity filters
56
        if ($entityMetadata['excludeCriteria']) {
57
            $excludeCriteria = $entityMetadata['excludeCriteria'];
58
            $allowedFilters  = array_filter($allowedFilters, static function ($value) use ($excludeCriteria) {
59
                return ! in_array($value, $excludeCriteria);
60
            });
61
        }
62
63
        // Limit association filters
64
        if ($associationName) {
65
            $excludeCriteria = $associationMetadata['excludeCriteria'];
66
            $allowedFilters  = array_filter($allowedFilters, static function ($value) use ($excludeCriteria) {
67
                return ! in_array($value, $excludeCriteria);
68
            });
69
        }
70
71
        foreach ($classMetadata->getFieldNames() as $fieldName) {
72
            $graphQLType = null;
73
74
            // Only process fields which are in the graphql metadata
75
            if (! in_array($fieldName, array_keys($entityMetadata['fields']))) {
76
                continue;
77
            }
78
79
            $fieldMetadata = $classMetadata->getFieldMapping($fieldName);
80
81
            $graphQLType = $this->typeManager
82
                ->get($entityMetadata['fields'][$fieldName]['type']);
83
84
            if ($graphQLType && $classMetadata->isIdentifier($fieldName)) {
85
                $graphQLType = Type::id();
86
            }
87
88
            assert($graphQLType, 'GraphQL type not found for ' . $fieldMetadata['type']);
89
90
            // Limit field filters
91
            if (isset($entityMetadata['fields'][$fieldName]['excludeCriteria'])
0 ignored issues
show
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
92
                && count($entityMetadata['fields'][$fieldName]['excludeCriteria'])) {
0 ignored issues
show
Function count() should not be referenced via a fallback global name, but via a use statement.
Loading history...
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
Blank line found at start of control structure
Loading history...
93
94
                // Compute the difference of arrays
95
                $fieldExcludeCriteria  = $entityMetadata['fields'][$fieldName]['excludeCriteria'];
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
96
                $allowedFilters        = array_filter($allowedFilters,
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 8 spaces

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
97
                    static function ($value) use ($fieldExcludeCriteria) {
98
                        return ! in_array($value, $fieldExcludeCriteria);
99
                    }
0 ignored issues
show
Multi-line function calls must have a trailing comma after the last parameter.
Loading history...
100
                );
101
            }
102
103
            $fields[$fieldName] = [
104
                'name' => $fieldName,
105
                'type' => new FiltersInputType($typeName, $fieldName, $graphQLType, $allowedFilters),
106
                'description' => 'Filters for ' . $fieldName,
107
            ];
108
        }
109
110
        // Add eq filter for to-one associations
111
        foreach ($classMetadata->getAssociationNames() as $associationName) {
112
            // Only process fields which are in the graphql metadata
113
            if (! in_array($associationName, array_keys($entityMetadata['fields']))) {
114
                continue;
115
            }
116
117
            $associationMetadata = $classMetadata->getAssociationMapping($associationName);
118
            $graphQLType         = Type::id();
119
            switch ($associationMetadata['type']) {
120
                case ClassMetadataInfo::ONE_TO_ONE:
121
                case ClassMetadataInfo::MANY_TO_ONE:
122
                case ClassMetadataInfo::TO_ONE:
123
                    // eq filter is for association:value
124
                    if (in_array(Filters::EQ, $allowedFilters)) {
125
                        $fields[$associationName] = [
126
                            'name' => $associationName,
127
                            'type' => new FiltersInputType($typeName, $associationName, $graphQLType, ['eq']),
128
                            'description' => 'Filters for ' . $associationName,
129
                        ];
130
                    }
131
            }
132
        }
133
134
        $inputObject = new InputObjectType([
135
            'name' => $typeName,
136
            'fields' => static function () use ($fields) {
137
                return $fields;
138
            },
139
        ]);
140
141
        $this->typeManager->set($typeName, $inputObject);
142
143
        return $inputObject;
144
    }
145
}
146