1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace DH\Auditor\Provider\Doctrine\Persistence\Reader; |
4
|
|
|
|
5
|
|
|
use DH\Auditor\Exception\InvalidArgumentException; |
6
|
|
|
use DH\Auditor\Model\Entry; |
7
|
|
|
use DH\Auditor\Provider\Doctrine\Persistence\Helper\SchemaHelper; |
8
|
|
|
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\DateRangeFilter; |
9
|
|
|
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\FilterInterface; |
10
|
|
|
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\RangeFilter; |
11
|
|
|
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\SimpleFilter; |
12
|
|
|
use Doctrine\DBAL\Connection; |
13
|
|
|
use Doctrine\DBAL\Query\QueryBuilder; |
14
|
|
|
use Doctrine\DBAL\Result; |
15
|
|
|
use Exception; |
16
|
|
|
|
17
|
|
|
class Query |
18
|
|
|
{ |
19
|
|
|
public const TYPE = 'type'; |
20
|
|
|
public const CREATED_AT = 'created_at'; |
21
|
|
|
public const TRANSACTION_HASH = 'transaction_hash'; |
22
|
|
|
public const OBJECT_ID = 'object_id'; |
23
|
|
|
public const USER_ID = 'blame_id'; |
24
|
|
|
public const ID = 'id'; |
25
|
|
|
public const DISCRIMINATOR = 'discriminator'; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var array |
29
|
|
|
*/ |
30
|
|
|
private $filters = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
private $orderBy = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var Connection |
39
|
|
|
*/ |
40
|
|
|
private $connection; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
private $table; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var int |
49
|
|
|
*/ |
50
|
|
|
private $offset = 0; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var int |
54
|
|
|
*/ |
55
|
|
|
private $limit = 0; |
56
|
|
|
|
57
|
|
|
public function __construct(string $table, Connection $connection) |
58
|
|
|
{ |
59
|
|
|
$this->connection = $connection; |
60
|
|
|
$this->table = $table; |
61
|
|
|
|
62
|
|
|
foreach ($this->getSupportedFilters() as $filterType) { |
63
|
|
|
$this->filters[$filterType] = []; |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
public function execute(): array |
68
|
|
|
{ |
69
|
|
|
$queryBuilder = $this->buildQueryBuilder(); |
70
|
|
|
if (method_exists($queryBuilder, 'executeQuery')) { |
71
|
|
|
// doctrine/dbal v3.x |
72
|
|
|
$statement = $queryBuilder->executeQuery(); |
73
|
|
|
} else { |
74
|
|
|
// doctrine/dbal v2.13.x |
75
|
|
|
$statement = $queryBuilder->execute(); |
|
|
|
|
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
$result = []; |
79
|
|
|
\assert($statement instanceof Result); |
80
|
|
|
foreach ($statement->fetchAllAssociative() as $row) { |
81
|
|
|
$result[] = Entry::fromArray($row); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return $result; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
public function count(): int |
88
|
|
|
{ |
89
|
|
|
$queryBuilder = $this->buildQueryBuilder(); |
90
|
|
|
|
91
|
|
|
try { |
92
|
|
|
$queryBuilder |
93
|
|
|
->resetQueryPart('select') |
94
|
|
|
->resetQueryPart('orderBy') |
95
|
|
|
->setMaxResults(null) |
96
|
|
|
->setFirstResult(null) |
97
|
|
|
->select('COUNT(id)') |
98
|
|
|
; |
99
|
|
|
|
100
|
|
|
if (method_exists($queryBuilder, 'executeQuery')) { |
101
|
|
|
// doctrine/dbal v3.x |
102
|
|
|
$result = $queryBuilder |
103
|
|
|
->executeQuery() |
104
|
|
|
->fetchOne() |
105
|
|
|
; |
106
|
|
|
} else { |
107
|
|
|
// doctrine/dbal v2.13.x |
108
|
|
|
$result = $queryBuilder |
|
|
|
|
109
|
|
|
->execute() |
110
|
|
|
->fetchColumn(0) |
|
|
|
|
111
|
|
|
; |
112
|
|
|
} |
113
|
|
|
} catch (Exception $e) { |
114
|
|
|
$result = false; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
return false === $result ? 0 : $result; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
public function addFilter(FilterInterface $filter): self |
121
|
|
|
{ |
122
|
|
|
$this->checkFilter($filter->getName()); |
123
|
|
|
$this->filters[$filter->getName()][] = $filter; |
124
|
|
|
|
125
|
|
|
return $this; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
public function addOrderBy(string $field, string $direction = 'DESC'): self |
129
|
|
|
{ |
130
|
|
|
$this->checkFilter($field); |
131
|
|
|
|
132
|
|
|
if (!\in_array($direction, ['ASC', 'DESC'], true)) { |
133
|
|
|
throw new InvalidArgumentException('Invalid sort direction, allowed value: ASC, DESC'); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$this->orderBy[$field] = $direction; |
137
|
|
|
|
138
|
|
|
return $this; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function limit(int $limit, int $offset = 0): self |
142
|
|
|
{ |
143
|
|
|
if (0 > $limit) { |
144
|
|
|
throw new InvalidArgumentException('Limit cannot be negative.'); |
145
|
|
|
} |
146
|
|
|
if (0 > $offset) { |
147
|
|
|
throw new InvalidArgumentException('Offset cannot be negative.'); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
$this->limit = $limit; |
151
|
|
|
$this->offset = $offset; |
152
|
|
|
|
153
|
|
|
return $this; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
public function getSupportedFilters(): array |
157
|
|
|
{ |
158
|
|
|
return array_keys(SchemaHelper::getAuditTableIndices('fake')); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
public function getFilters(): array |
162
|
|
|
{ |
163
|
|
|
return $this->filters; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
public function getOrderBy(): array |
167
|
|
|
{ |
168
|
|
|
return $this->orderBy; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function getLimit(): array |
172
|
|
|
{ |
173
|
|
|
return [$this->limit, $this->offset]; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
private function buildQueryBuilder(): QueryBuilder |
177
|
|
|
{ |
178
|
|
|
$queryBuilder = $this->connection->createQueryBuilder(); |
179
|
|
|
$queryBuilder |
180
|
|
|
->select('*') |
181
|
|
|
->from($this->table, 'at') |
182
|
|
|
; |
183
|
|
|
|
184
|
|
|
// build WHERE clause(s) |
185
|
|
|
$queryBuilder = $this->buildWhere($queryBuilder); |
186
|
|
|
|
187
|
|
|
// build ORDER BY part |
188
|
|
|
$queryBuilder = $this->buildOrderBy($queryBuilder); |
189
|
|
|
|
190
|
|
|
// build LIMIT part |
191
|
|
|
return $this->buildLimit($queryBuilder); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
private function groupFilters(array $filters): array |
195
|
|
|
{ |
196
|
|
|
$grouped = []; |
197
|
|
|
|
198
|
|
|
foreach ($filters as $filter) { |
199
|
|
|
$class = \get_class($filter); |
200
|
|
|
if (!isset($grouped[$class])) { |
201
|
|
|
$grouped[$class] = []; |
202
|
|
|
} |
203
|
|
|
$grouped[$class][] = $filter; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
return $grouped; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
private function mergeSimpleFilters(array $filters): SimpleFilter |
210
|
|
|
{ |
211
|
|
|
$merged = []; |
212
|
|
|
$name = null; |
213
|
|
|
|
214
|
|
|
foreach ($filters as $filter) { |
215
|
|
|
if (null === $name) { |
216
|
|
|
$name = $filter->getName(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
if (\is_array($filter->getValue())) { |
220
|
|
|
$merged = array_merge($merged, $filter->getValue()); |
221
|
|
|
} else { |
222
|
|
|
$merged[] = $filter->getValue(); |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return new SimpleFilter($name, $merged); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
private function buildWhere(QueryBuilder $queryBuilder): QueryBuilder |
230
|
|
|
{ |
231
|
|
|
foreach ($this->filters as $name => $rawFilters) { |
232
|
|
|
if (0 === \count($rawFilters)) { |
233
|
|
|
continue; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
// group filters by class |
237
|
|
|
$grouped = $this->groupFilters($rawFilters); |
238
|
|
|
|
239
|
|
|
foreach ($grouped as $class => $filters) { |
240
|
|
|
switch ($class) { |
241
|
|
|
case SimpleFilter::class: |
242
|
|
|
$filters = [$this->mergeSimpleFilters($filters)]; |
243
|
|
|
|
244
|
|
|
break; |
245
|
|
|
case RangeFilter::class: |
246
|
|
|
case DateRangeFilter::class: |
247
|
|
|
break; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
foreach ($filters as $filter) { |
251
|
|
|
$data = $filter->getSQL(); |
252
|
|
|
|
253
|
|
|
$queryBuilder->andWhere($data['sql']); |
254
|
|
|
|
255
|
|
|
foreach ($data['params'] as $name => $value) { |
|
|
|
|
256
|
|
|
if (\is_array($value)) { |
257
|
|
|
$queryBuilder->setParameter($name, $value, Connection::PARAM_STR_ARRAY); |
258
|
|
|
} else { |
259
|
|
|
$queryBuilder->setParameter($name, $value); |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return $queryBuilder; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
private function buildOrderBy(QueryBuilder $queryBuilder): QueryBuilder |
270
|
|
|
{ |
271
|
|
|
foreach ($this->orderBy as $field => $direction) { |
272
|
|
|
$queryBuilder->addOrderBy($field, $direction); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
return $queryBuilder; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
private function buildLimit(QueryBuilder $queryBuilder): QueryBuilder |
279
|
|
|
{ |
280
|
|
|
if (0 < $this->limit) { |
281
|
|
|
$queryBuilder->setMaxResults($this->limit); |
282
|
|
|
} |
283
|
|
|
if (0 < $this->offset) { |
284
|
|
|
$queryBuilder->setFirstResult($this->offset); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
return $queryBuilder; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
private function checkFilter(string $filter): void |
291
|
|
|
{ |
292
|
|
|
if (!\in_array($filter, $this->getSupportedFilters(), true)) { |
293
|
|
|
throw new InvalidArgumentException(sprintf('Unsupported "%s" filter, allowed filters: %s.', $filter, implode(', ', $this->getSupportedFilters()))); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
} |
297
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.