QueryFilterManager   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 62
c 2
b 0
f 0
dl 0
loc 148
rs 10
wmc 20

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A applySort() 0 22 4
A getQueryBuilder() 0 7 2
A createFilter() 0 9 2
A applyFilter() 0 27 4
A createMetadataProxy() 0 9 2
A filter() 0 17 3
A createSort() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineQueryFilter;
6
7
use Arp\DoctrineQueryFilter\Exception\QueryFilterManagerException;
8
use Arp\DoctrineQueryFilter\Filter\Exception\FilterException;
9
use Arp\DoctrineQueryFilter\Filter\Exception\FilterFactoryException;
10
use Arp\DoctrineQueryFilter\Filter\FilterFactoryInterface;
11
use Arp\DoctrineQueryFilter\Filter\FilterInterface;
12
use Arp\DoctrineQueryFilter\Metadata\Metadata;
13
use Arp\DoctrineQueryFilter\Metadata\MetadataInterface;
14
use Arp\DoctrineQueryFilter\Sort\Exception\SortException;
15
use Arp\DoctrineQueryFilter\Sort\Exception\SortFactoryException;
16
use Arp\DoctrineQueryFilter\Sort\Field;
17
use Arp\DoctrineQueryFilter\Sort\SortFactoryInterface;
18
use Arp\DoctrineQueryFilter\Sort\SortInterface;
19
use Doctrine\ORM\EntityManagerInterface;
20
use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder;
21
22
class QueryFilterManager implements QueryFilterManagerInterface
23
{
24
    public function __construct(
25
        private readonly FilterFactoryInterface $filterFactory,
26
        private readonly SortFactoryInterface $sortFactory
27
    ) {
28
    }
29
30
    /**
31
     * Apply the query filters to the provided query builder instance
32
     *
33
     * @throws QueryFilterManagerException
34
     */
35
    public function filter(
36
        DoctrineQueryBuilder|QueryBuilderInterface $queryBuilder,
37
        string $entityName,
38
        array $criteria
39
    ): DoctrineQueryBuilder {
40
        $queryBuilder = $this->getQueryBuilder($queryBuilder);
41
        $metadata = $this->createMetadataProxy($queryBuilder->getEntityManager(), $entityName);
42
43
        foreach ($criteria['filters'] ?? [] as $filterCriteria) {
44
            $this->applyFilter($queryBuilder, $metadata, $filterCriteria);
45
        }
46
47
        foreach ($criteria['sort'] ?? [] as $sortCriteria) {
48
            $this->applySort($queryBuilder, $metadata, $sortCriteria);
49
        }
50
51
        return $queryBuilder->getWrappedQueryBuilder();
52
    }
53
54
    /**
55
     * @throws QueryFilterManagerException
56
     */
57
    public function applyFilter(
58
        QueryBuilderInterface $queryBuilder,
59
        MetadataInterface $metadata,
60
        array|FilterInterface $data
61
    ): void {
62
        if ($data instanceof FilterInterface) {
0 ignored issues
show
introduced by
$data is never a sub-type of Arp\DoctrineQueryFilter\Filter\FilterInterface.
Loading history...
63
            $filter = $data;
64
            $data = [];
65
        } else {
66
            $filterName = $data['name'] ?? null;
67
68
            if (empty($filterName)) {
69
                throw new QueryFilterManagerException(
70
                    sprintf('The required \'name\' configuration option is missing in \'%s\'', static::class)
71
                );
72
            }
73
74
            $filter = $this->createFilter($filterName, $data['options'] ?? []);
75
        }
76
77
        try {
78
            $filter->filter($queryBuilder, $metadata, $data);
79
        } catch (FilterException $e) {
80
            throw new QueryFilterManagerException(
81
                sprintf('Failed to apply query filter for entity \'%s\'', $metadata->getName()),
82
                $e->getCode(),
83
                $e
84
            );
85
        }
86
    }
87
88
    /**
89
     * @throws QueryFilterManagerException
90
     */
91
    public function createFilter(string $name, array $options = []): FilterInterface
92
    {
93
        try {
94
            return $this->filterFactory->create($this, $name, $options);
95
        } catch (FilterFactoryException $e) {
96
            throw new QueryFilterManagerException(
97
                sprintf('Failed to create filter \'%s\'', $name),
98
                $e->getCode(),
99
                $e
100
            );
101
        }
102
    }
103
104
    /**
105
     * @throws QueryFilterManagerException
106
     */
107
    public function applySort(
108
        QueryBuilderInterface $queryBuilder,
109
        MetadataInterface $metadata,
110
        array|SortInterface $data
111
    ): void {
112
        if ($data instanceof SortInterface) {
0 ignored issues
show
introduced by
$data is never a sub-type of Arp\DoctrineQueryFilter\Sort\SortInterface.
Loading history...
113
            $sort = $data;
114
            $data = [];
115
        } else {
116
            $sort = $this->createSort(
117
                empty($data['name']) ? Field::class : $data['name'],
118
                $data['options'] ?? []
119
            );
120
        }
121
122
        try {
123
            $sort->sort($queryBuilder, $metadata, $data);
124
        } catch (SortException $e) {
125
            throw new QueryFilterManagerException(
126
                sprintf('Failed to apply query sorting for entity \'%s\'', $metadata->getName()),
127
                $e->getCode(),
128
                $e
129
            );
130
        }
131
    }
132
133
    /**
134
     * @throws QueryFilterManagerException
135
     */
136
    public function createSort(string $name, array $options = []): SortInterface
137
    {
138
        try {
139
            return $this->sortFactory->create($this, $name, $options);
140
        } catch (SortFactoryException $e) {
141
            throw new QueryFilterManagerException(
142
                sprintf('Failed to create filter \'%s\'', $name),
143
                $e->getCode(),
144
                $e
145
            );
146
        }
147
    }
148
149
    private function getQueryBuilder(QueryBuilderInterface|DoctrineQueryBuilder $queryBuilder): QueryBuilderInterface
150
    {
151
        if ($queryBuilder instanceof DoctrineQueryBuilder) {
152
            $queryBuilder = new QueryBuilder($queryBuilder, $queryBuilder->getRootAliases()[0] ?? '');
153
        }
154
155
        return $queryBuilder;
156
    }
157
158
    /**
159
     * @throws QueryFilterManagerException
160
     */
161
    private function createMetadataProxy(EntityManagerInterface $entityManager, string $entityName): MetadataInterface
162
    {
163
        try {
164
            return new Metadata($entityManager->getClassMetadata($entityName));
165
        } catch (\Exception $e) {
166
            throw new QueryFilterManagerException(
167
                sprintf('Failed to fetch entity metadata for class \'%s\'', $entityName),
168
                $e->getCode(),
169
                $e
170
            );
171
        }
172
    }
173
}
174