1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* FilterQuery.php |
4
|
|
|
* |
5
|
|
|
* @since 19/01/17 |
6
|
|
|
* @author gseidel |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Enhavo\Bundle\AppBundle\Filter; |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
13
|
|
|
use Doctrine\ORM\QueryBuilder; |
14
|
|
|
use Enhavo\Bundle\AppBundle\Exception\FilterException; |
15
|
|
|
|
16
|
|
|
class FilterQuery |
17
|
|
|
{ |
18
|
|
|
const OPERATOR_EQUALS = '='; |
19
|
|
|
const OPERATOR_GREATER = '>'; |
20
|
|
|
const OPERATOR_LESS = '<'; |
21
|
|
|
const OPERATOR_GREATER_EQUAL = '>='; |
22
|
|
|
const OPERATOR_LESS_EQUAL = '<='; |
23
|
|
|
const OPERATOR_NOT = '!='; |
24
|
|
|
const OPERATOR_LIKE = 'like'; |
25
|
|
|
const OPERATOR_START_LIKE = 'start_like'; |
26
|
|
|
const OPERATOR_END_LIKE = 'end_like'; |
27
|
|
|
|
28
|
|
|
const ORDER_ASC = 'asc'; |
29
|
|
|
const ORDER_DESC = 'desc'; |
30
|
|
|
|
31
|
|
|
const HYDRATE_OBJECT = 'object'; |
32
|
|
|
const HYDRATE_ID = 'id'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var array |
36
|
|
|
*/ |
37
|
|
|
private $where = []; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var array |
41
|
|
|
*/ |
42
|
|
|
private $orderBy = []; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var QueryBuilder |
46
|
|
|
*/ |
47
|
|
|
private $queryBuilder; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
private $alias; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string |
56
|
|
|
*/ |
57
|
|
|
private $hydrate; |
58
|
|
|
|
59
|
|
|
public function __construct(EntityManagerInterface $em, $class, $alias = 'a') |
60
|
|
|
{ |
61
|
|
|
if(strlen($alias) != 1) { |
62
|
|
|
throw new \InvalidArgumentException('Alias should be a single letter'); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$this->alias = $alias; |
66
|
|
|
$this->queryBuilder = new QueryBuilder($em); |
67
|
|
|
$this->queryBuilder->select($this->getAlias()); |
68
|
|
|
$this->queryBuilder->from($class, $this->getAlias()); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
public function addOrderBy($property, $order, $joinProperty = null) |
72
|
|
|
{ |
73
|
|
|
$this->orderBy[] = [ |
74
|
|
|
'property' => $property, |
75
|
|
|
'order' => $order, |
76
|
|
|
'joinProperty' => $joinProperty |
77
|
|
|
]; |
78
|
|
|
|
79
|
|
|
return $this; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
public function removeOrderBy($property, $order) |
83
|
|
|
{ |
84
|
|
|
if(!$property && !$order){ |
85
|
|
|
return $this; |
86
|
|
|
} |
87
|
|
|
foreach ($this->orderBy as $index => $orderBy){ |
88
|
|
|
if($property && $orderBy['property'] !== $property){ |
89
|
|
|
continue; |
90
|
|
|
} |
91
|
|
|
if($order && $orderBy['operator'] !== $order){ |
92
|
|
|
continue; |
93
|
|
|
} |
94
|
|
|
unset($this->orderBy[$index]); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
return $this; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
public function addWhere($property, $operator, $value, $joinProperty = null) |
101
|
|
|
{ |
102
|
|
|
$this->where[] = [ |
103
|
|
|
'property' => $property, |
104
|
|
|
'operator' => $operator, |
105
|
|
|
'value' => $value, |
106
|
|
|
'joinProperty' => $joinProperty |
107
|
|
|
]; |
108
|
|
|
|
109
|
|
|
return $this; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public function removeWhere($property, $operator, $value, $joinProperty = null) |
113
|
|
|
{ |
114
|
|
|
if(!$property && !$operator && !$value && !$joinProperty){ |
115
|
|
|
return $this; |
116
|
|
|
} |
117
|
|
|
foreach ($this->where as $index => $where){ |
118
|
|
|
if($property && $where['property'] !== $property){ |
119
|
|
|
continue; |
120
|
|
|
} |
121
|
|
|
if($operator && $where['operator'] !== $operator){ |
122
|
|
|
continue; |
123
|
|
|
} |
124
|
|
|
if($value && $where['value'] !== $value){ |
125
|
|
|
continue; |
126
|
|
|
} |
127
|
|
|
if($joinProperty && $where['joinProperty'] !== $joinProperty){ |
128
|
|
|
continue; |
129
|
|
|
} |
130
|
|
|
unset($this->where[$index]); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return $this; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
public function getWhere() |
137
|
|
|
{ |
138
|
|
|
return $this->where; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function getOrderBy() |
142
|
|
|
{ |
143
|
|
|
return $this->orderBy; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @return QueryBuilder |
148
|
|
|
*/ |
149
|
|
|
public function getQueryBuilder() |
150
|
|
|
{ |
151
|
|
|
return $this->queryBuilder; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
public function build() |
155
|
|
|
{ |
156
|
|
|
/** @var QueryBuilder $query */ |
157
|
|
|
$query = $this->queryBuilder; |
158
|
|
|
$i = 0; |
159
|
|
|
foreach($this->getWhere() as $index => $where) { |
160
|
|
|
$i++; |
161
|
|
|
if($where['joinProperty']) { |
162
|
|
|
if(is_array($where['joinProperty']) && count($where['joinProperty'])) { |
163
|
|
|
$joinPrefixes = $this->createJoinPropertyArray($index, count($where['joinProperty']) + 1); |
164
|
|
View Code Duplication |
foreach($where['joinProperty'] as $joinProperty) { |
|
|
|
|
165
|
|
|
$joinPrefix = array_shift($joinPrefixes); |
166
|
|
|
$query->join(sprintf('%s.%s', $joinPrefix, $joinProperty), $joinPrefixes[0]); |
167
|
|
|
} |
168
|
|
|
$query->andWhere(sprintf('%s.%s %s %s', $joinPrefixes[0], $where['property'], $this->getOperator($where), $this->getParameter($where, $i))); |
169
|
|
|
} else { |
170
|
|
|
$query->join(sprintf('%s.%s', $this->getAlias(), $where['property']), sprintf('j%s', $i)); |
171
|
|
|
$query->andWhere(sprintf('j%s.%s %s %s', $i, $where['property'], $this->getOperator($where), $this->getParameter($where, $i))); |
172
|
|
|
} |
173
|
|
|
} else { |
174
|
|
|
$query->andWhere(sprintf('%s.%s %s %s', $this->getAlias(), $where['property'], $this->getOperator($where), $this->getParameter($where, $i))); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
if($this->hasParameter($where)) { |
178
|
|
|
$query->setParameter(sprintf('parameter%s', $i), $this->getValue($where)); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$indexMin = count($this->getWhere()); |
183
|
|
|
foreach($this->getOrderBy() as $index => $order) { |
184
|
|
|
$i++; |
185
|
|
|
if($order['joinProperty']) { |
186
|
|
|
if(is_array($order['joinProperty']) && count($order['joinProperty'])) { |
187
|
|
|
$joinPrefixes = $this->createJoinPropertyArray($indexMin + $index, count($order['joinProperty']) + 1); |
188
|
|
View Code Duplication |
foreach($order['joinProperty'] as $joinProperty) { |
|
|
|
|
189
|
|
|
$joinPrefix = array_shift($joinPrefixes); |
190
|
|
|
$query->leftJoin(sprintf('%s.%s', $joinPrefix, $joinProperty), $joinPrefixes[0]); |
191
|
|
|
} |
192
|
|
|
$query->addOrderBy(sprintf('%s.%s', $joinPrefixes[0], $order['property']), $order['order']); |
193
|
|
|
} else { |
194
|
|
|
$query->leftJoin(sprintf('%s.%s', $this->getAlias(), $order['property']), sprintf('j%s', $i)); |
195
|
|
|
$query->addOrderBy(sprintf('j%s.%s', $i, $order['property']), $order['order']); |
196
|
|
|
} |
197
|
|
|
} else { |
198
|
|
|
$query->addOrderBy(sprintf('%s.%s', $this->getAlias(), $order['property']), $order['order']); |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
if ($this->getHydrate() === self::HYDRATE_ID) { |
203
|
|
|
$query->select($this->getAlias() . '.id'); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
return $this; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
public function createJoinPropertyArray($index, $length) |
210
|
|
|
{ |
211
|
|
|
$i=0; |
212
|
|
|
$allLetters = []; |
213
|
|
|
|
214
|
|
|
$letters = range($this->getAlias(), 'z'); |
215
|
|
|
$lettersAdded = $letters; |
216
|
|
|
while ($length > 0) { |
217
|
|
|
$i++; |
218
|
|
|
foreach ($lettersAdded as $indexFor => &$addLetter){ |
219
|
|
|
$addLetter .= $letters[$indexFor]; |
220
|
|
|
} |
221
|
|
|
foreach ($lettersAdded as $letter) { |
222
|
|
|
$letter .= $index; |
223
|
|
|
$allLetters[] = $letter; |
224
|
|
|
$length--; |
225
|
|
|
if($length <= 0){ |
226
|
|
|
$allLetters[0] = $this->getAlias(); |
227
|
|
|
return $allLetters; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
return $allLetters; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
private function getValue($where) |
235
|
|
|
{ |
236
|
|
|
$value = $where['value']; |
237
|
|
|
|
238
|
|
|
if($where['operator'] == FilterQuery::OPERATOR_LIKE) { |
239
|
|
|
return '%'.$value.'%'; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
if($where['operator'] == FilterQuery::OPERATOR_START_LIKE) { |
243
|
|
|
return $value.'%'; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
if($where['operator'] == FilterQuery::OPERATOR_END_LIKE) { |
247
|
|
|
return '%'.$value; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return $value; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
private function getOperator($where) |
254
|
|
|
{ |
255
|
|
|
$value = $where['value']; |
256
|
|
|
|
257
|
|
|
switch($where['operator']) { |
258
|
|
|
case(FilterQuery::OPERATOR_EQUALS): |
259
|
|
|
if($value === null) { |
260
|
|
|
return 'is'; |
261
|
|
|
} |
262
|
|
|
return '='; |
263
|
|
|
case(FilterQuery::OPERATOR_GREATER): |
264
|
|
|
return '>'; |
265
|
|
|
case(FilterQuery::OPERATOR_GREATER_EQUAL): |
266
|
|
|
return '>='; |
267
|
|
|
case(FilterQuery::OPERATOR_LESS): |
268
|
|
|
return '<'; |
269
|
|
|
case(FilterQuery::OPERATOR_LESS_EQUAL): |
270
|
|
|
return '<='; |
271
|
|
|
case(FilterQuery::OPERATOR_NOT): |
272
|
|
|
if($value === null) { |
273
|
|
|
return 'is not'; |
274
|
|
|
} |
275
|
|
|
return '!='; |
276
|
|
|
case(FilterQuery::OPERATOR_LIKE): |
277
|
|
|
case(FilterQuery::OPERATOR_START_LIKE): |
278
|
|
|
case(FilterQuery::OPERATOR_END_LIKE): |
279
|
|
|
return 'like'; |
280
|
|
|
} |
281
|
|
|
throw new FilterException('Operator not supported in Repository'); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
private function getParameter($where, $number) |
285
|
|
|
{ |
286
|
|
|
$value = $where['value']; |
287
|
|
|
if(FilterQuery::OPERATOR_EQUALS && $value === null) { |
288
|
|
|
return 'null'; |
289
|
|
|
} |
290
|
|
|
return sprintf(':parameter%s', $number); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
private function hasParameter($where) |
294
|
|
|
{ |
295
|
|
|
$value = $where['value']; |
296
|
|
|
if(FilterQuery::OPERATOR_EQUALS && $value === null) { |
297
|
|
|
return false; |
298
|
|
|
} |
299
|
|
|
return true; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
public function getAlias() |
303
|
|
|
{ |
304
|
|
|
return $this->alias; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* @return string |
309
|
|
|
*/ |
310
|
|
|
public function getHydrate(): string |
311
|
|
|
{ |
312
|
|
|
return $this->hydrate; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* @param string $hydrate |
317
|
|
|
*/ |
318
|
|
|
public function setHydrate(string $hydrate): void |
319
|
|
|
{ |
320
|
|
|
$this->hydrate = $hydrate; |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.