Passed
Push — main ( 7ac0ad...a216bf )
by Milad
02:30
created

QueryFilter::addWhereClauses()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 54
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 32
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 54
rs 8.7857

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Bugloos\QueryFilterBundle\Service;
6
7
use Bugloos\QueryFilterBundle\Enum\ColumnType;
8
use Closure;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\QueryBuilder;
12
use JsonException;
13
use Bugloos\QueryFilterBundle\FilterHandler\Contract\WithRelationInterface;
14
use Bugloos\QueryFilterBundle\FilterHandler\Factory\FilterFactory;
15
use Bugloos\QueryFilterBundle\Traits\QueryFilterTrait;
16
use Psr\Cache\CacheItemInterface;
17
use Psr\Cache\InvalidArgumentException;
18
use Symfony\Contracts\Cache\CacheInterface;
19
20
/**
21
 * @author Milad Ghofrani <[email protected]>
22
 */
23
class QueryFilter
24
{
25
    use QueryFilterTrait;
26
27
    private const DEFAULT_CACHE_TIME = 3600;
28
29
    private const SEPARATOR = '.';
30
31
    private EntityManagerInterface $entityManager;
32
33
    private FilterFactory $filterFactory;
34
35
    private CacheInterface $cache;
36
37
    private string $rootAlias;
38
39
    private string $rootEntity;
40
41
    private ClassMetadata $rootClass;
42
43
    private QueryBuilder $query;
44
45
    private string $cacheKey;
46
47
    private array $filters = [];
48
49
    private array $mapper = [];
50
51
    private array $strategies = [];
52
53
    private array $types = [];
54
55
    private ?int $cacheTime = null;
56
57
    private bool $withOr = false;
58
59
    private int $defaultCacheTime;
60
61
    private string $separator;
62
63
    public function __construct(
64
        EntityManagerInterface $entityManager,
65
        CacheInterface $cache,
66
        FilterFactory $filterFactory,
67
        $defaultCacheTime = self::DEFAULT_CACHE_TIME,
68
        $separator = self::SEPARATOR
69
    ) {
70
        $this->entityManager = $entityManager;
71
        $this->cache = $cache;
72
        $this->filterFactory = $filterFactory;
73
        $this->defaultCacheTime = $defaultCacheTime;
74
        $this->separator = $separator;
75
    }
76
77
    public function for(QueryBuilder $query): self
78
    {
79
        $this->initializeRootQueryConfig($query);
80
81
        return $this;
82
    }
83
84
    /**
85
     * @param $filters
86
     *
87
     * @return self
88
     *
89
     * @throws JsonException
90
     */
91
    public function parameters($filters): self
92
    {
93
        if (empty($filters)) {
94
            return $this;
95
        }
96
97
        if (!\is_array($filters)) {
98
            throw new \InvalidArgumentException(
99
                'Filter parameters should be an array type'
100
            );
101
        }
102
103
        // Remove empty value from array
104
        $this->filters = array_filter($filters, static function ($var) {
105
            return null !== $var && '' !== $var;
106
        });
107
108
        // Create cache key by request
109
        $this->createCacheKey($this->filters);
110
111
        return $this;
112
    }
113
114
    /**
115
     * @throws InvalidArgumentException
116
     */
117
    public function filter(): QueryBuilder
118
    {
119
        // Early return if array is empty
120
        if (empty($this->filters)) {
121
            return $this->query;
122
        }
123
        // Calculate and cache fields.
124
        [$filterParameters, $filterWhereClauses, $relationJoins] = $this->cache->get(
125
            $this->cacheKey,
126
            $this->addWhereClauses()
127
        );
128
129
        $this->applyRelationJoinToQuery($relationJoins);
130
131
        $this->applyWhereClausesToQuery($filterWhereClauses);
132
133
        $this->applyParametersToQuery($filterParameters);
134
135
        return $this->query;
136
    }
137
138
    private function applyWhereClausesToQuery($filterWhereClauses): void
139
    {
140
        if (!empty($filterWhereClauses)) {
141
            if ($this->withOr) {
142
                $this->query->andWhere($this->query->expr()->orX()->addMultiple($filterWhereClauses));
143
            } else {
144
                $this->query->andWhere($this->query->expr()->andx()->addMultiple($filterWhereClauses));
145
            }
146
        }
147
    }
148
149
    private function applyParametersToQuery($filterParameters): void
150
    {
151
        foreach ($filterParameters as $parameterName => $parameterValue) {
152
            $this->query->setParameter($parameterName, $parameterValue);
153
        }
154
    }
155
156
    private function applyRelationJoinToQuery($relationJoins): void
157
    {
158
        // Remove exist joined from a list
159
        $filteredJoins = array_diff($relationJoins, $this->query->getAllAliases());
160
161
        // Add a left join to query which does not exist in the query
162
        if (!empty($filteredJoins)) {
163
            foreach ($filteredJoins as $property => $column) {
164
                $this->query->addSelect($column);
165
                $this->query->leftJoin($property, $column);
166
            }
167
        }
168
    }
169
170
    /**
171
     * @param $array
172
     *
173
     * @throws JsonException
174
     */
175
    private function createCacheKey($array): void
176
    {
177
        $this->cacheKey = md5($this->rootEntity.json_encode($array, \JSON_THROW_ON_ERROR));
178
    }
179
180
    /**
181
     * @author Milad Ghofrani <[email protected]>
182
     */
183
    private function addWhereClauses(): Closure
184
    {
185
        return function (CacheItemInterface $item) {
186
            $item->expiresAfter($this->cacheTime ?: $this->defaultCacheTime);
187
188
            $filterParameters = [];
189
            $filterWhereClauses = [];
190
            $relationJoins = [];
191
192
            foreach ($this->filters as $parameter => $value) {
193
                // Check user set a strategy for this $parameter
194
                $strategy = $this->strategies[$parameter] ?? null;
195
196
                // Check user set a type for this $parameter
197
                $type = $this->types[$parameter] ?? null;
198
199
                // Check $parameter exists in mapper
200
                $parameter = (\array_key_exists($parameter, $this->mapper))
201
                    ? $this->mapper[$parameter] : $parameter;
202
203
                $relationsAndFieldName = explode($this->separator, $parameter);
204
205
                $filteringHandler = $this->filterFactory->createFilterHandler($relationsAndFieldName);
206
207
                $filterParameter = $filteringHandler->filterParameter(
208
                    $this->rootAlias,
209
                    $this->rootClass,
210
                    $relationsAndFieldName,
211
                    $type
212
                );
213
214
                if($type !== ColumnType::NULLABLE){
215
                    $filterParameters[$filterParameter] = $filteringHandler->filterValue($value, $strategy);
216
                }
217
218
                $filterWhereClauses[] = $filteringHandler->filterWhereClause(
219
                    $this->rootAlias,
220
                    $relationsAndFieldName,
221
                    $filterParameter,
222
                    $strategy,
223
                    $value
224
                );
225
226
                if ($filteringHandler instanceof WithRelationInterface) {
227
                    $relationJoins = $filteringHandler->relationJoin(
228
                        $relationJoins,
229
                        $this->rootAlias,
230
                        $this->rootClass,
231
                        $relationsAndFieldName
232
                    );
233
                }
234
            }
235
236
            return [$filterParameters, $filterWhereClauses, $relationJoins];
237
        };
238
    }
239
240
    private function initializeRootQueryConfig($query): void
241
    {
242
        $rootEntities = $query->getRootEntities();
243
        $rootAliasArray = $query->getRootAliases();
244
245
        if (!isset($rootEntities[0], $rootAliasArray[0])) {
246
            throw new \InvalidArgumentException('Root Alias not defined correctly.');
247
        }
248
249
        $this->query = $query;
250
        $this->rootAlias = $rootAliasArray[0];
251
        $this->rootEntity = $rootEntities[0];
252
        $this->rootClass = $this->entityManager->getClassMetadata($this->rootEntity);
253
    }
254
}
255