1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @maintainer Timur Shagiakhmetov <[email protected]> |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Badoo\LiveProfilerUI\DB; |
8
|
|
|
|
9
|
|
|
use Badoo\LiveProfilerUI\DB\Validators\Direction; |
10
|
|
|
use Badoo\LiveProfilerUI\DB\Validators\Field; |
11
|
|
|
use Badoo\LiveProfilerUI\DB\Validators\Functions; |
12
|
|
|
use Badoo\LiveProfilerUI\DB\Validators\Operator; |
13
|
|
|
use Badoo\LiveProfilerUI\DB\Validators\Table; |
14
|
|
|
use Badoo\LiveProfilerUI\Exceptions\DatabaseException; |
15
|
|
|
use Badoo\LiveProfilerUI\Exceptions\InvalidFieldValueException; |
16
|
|
|
use Badoo\LiveProfilerUI\Interfaces\StorageInterface; |
17
|
|
|
use Doctrine\DBAL\DBALException; |
18
|
|
|
use Doctrine\DBAL\Query\QueryBuilder; |
19
|
|
|
|
20
|
|
|
class Storage implements StorageInterface |
21
|
|
|
{ |
22
|
|
|
/** @var \Doctrine\DBAL\Connection */ |
23
|
|
|
protected $Conn; |
24
|
|
|
|
25
|
|
|
/** @var int */ |
26
|
|
|
protected $lastInsertId = 0; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Storage constructor. |
30
|
|
|
* @param string $url |
31
|
|
|
*/ |
32
|
83 |
|
public function __construct(string $url) |
33
|
|
|
{ |
34
|
83 |
|
$config = new \Doctrine\DBAL\Configuration(); |
35
|
83 |
|
$connectionParams = ['url' => $url]; |
36
|
|
|
|
37
|
|
|
try { |
38
|
83 |
|
$this->Conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config); |
39
|
|
|
} catch (\Throwable $Ex) { |
40
|
|
|
throw new DatabaseException('Can\'t connect to db server: ' . $Ex->getMessage()); |
41
|
|
|
} |
42
|
|
|
|
43
|
83 |
|
if ($this->Conn->errorCode() && $this->Conn->errorCode() !== '00000') { |
44
|
|
|
throw new DatabaseException('Can\'t connect to db server: ' . print_r($this->Conn->errorInfo(), true)); |
45
|
|
|
} |
46
|
83 |
|
} |
47
|
|
|
|
48
|
|
|
public function getType() : string |
49
|
|
|
{ |
50
|
|
|
return $this->Conn->getDriver()->getName(); |
51
|
|
|
} |
52
|
|
|
|
53
|
28 |
|
public function getAll(string $table, array $fields, array $params) : array |
54
|
|
|
{ |
55
|
28 |
|
$Result = $this->getSelectQueryBuilder($table, $fields, $params)->execute(); |
56
|
28 |
|
if (!\is_object($Result)) { |
57
|
|
|
throw new DatabaseException( |
58
|
|
|
'Can\'t get data from ' . $table . ': ' . print_r($this->Conn->errorInfo(), true) |
59
|
|
|
); |
60
|
|
|
} |
61
|
|
|
|
62
|
28 |
|
return $Result->fetchAll() ?: []; |
63
|
|
|
} |
64
|
|
|
|
65
|
7 |
|
public function getOne(string $table, array $fields, array $params) : array |
66
|
|
|
{ |
67
|
7 |
|
$params['limit'] = 1; |
68
|
7 |
|
$Result = $this->getSelectQueryBuilder($table, $fields, $params)->execute(); |
69
|
7 |
|
if (!\is_object($Result)) { |
70
|
|
|
throw new DatabaseException( |
71
|
|
|
'Can\'t get data from ' . $table . ': ' . print_r($this->Conn->errorInfo(), true) |
72
|
|
|
); |
73
|
|
|
} |
74
|
|
|
|
75
|
7 |
|
return $Result->fetch() ?: []; |
76
|
|
|
} |
77
|
|
|
|
78
|
39 |
|
protected function getSelectQueryBuilder(string $table, array $fields, array $params) : QueryBuilder |
79
|
|
|
{ |
80
|
39 |
|
Table::validate($table); |
81
|
|
|
|
82
|
39 |
|
$QueryBuilder = $this->Conn->createQueryBuilder(); |
83
|
39 |
|
$QueryBuilder->select($this->prepareFieldList($fields))->from($table); |
84
|
|
|
|
85
|
39 |
|
if (!empty($params['filter'])) { |
86
|
35 |
|
$this->fillQueryBuilderFilter($QueryBuilder, $params['filter']); |
87
|
|
|
} |
88
|
|
|
|
89
|
39 |
|
if (!empty($params['group'])) { |
90
|
9 |
|
$this->fillQueryBuilderGroup($QueryBuilder, $params['group']); |
91
|
|
|
} |
92
|
|
|
|
93
|
39 |
|
if (!empty($params['having'])) { |
94
|
1 |
|
$this->fillQueryBuilderHaving($QueryBuilder, $params['having']); |
95
|
|
|
} |
96
|
|
|
|
97
|
39 |
|
if (!empty($params['order'])) { |
98
|
14 |
|
$this->fillQueryBuilderOrder($QueryBuilder, $params['order']); |
99
|
|
|
} |
100
|
|
|
|
101
|
39 |
|
if (!empty($params['limit'])) { |
102
|
13 |
|
$this->fillQueryBuilderLimit($QueryBuilder, (int)$params['limit']); |
103
|
|
|
} |
104
|
|
|
|
105
|
39 |
|
return $QueryBuilder; |
106
|
|
|
} |
107
|
|
|
|
108
|
35 |
|
protected function fillQueryBuilderFilter(QueryBuilder $QueryBuilder, array $filter) |
109
|
|
|
{ |
110
|
35 |
|
$is_first = true; |
111
|
35 |
|
foreach ($filter as $param) { |
112
|
35 |
|
if (empty($param[2])) { |
113
|
30 |
|
$param[2] = '='; |
114
|
|
|
} |
115
|
35 |
|
Operator::validate($param[2]); |
116
|
35 |
|
list($field_name, $value, $operator) = $param; |
117
|
|
|
|
118
|
35 |
|
if ($field_name === 'union') { |
119
|
1 |
|
$parts = []; |
120
|
1 |
|
foreach ($value as $item) { |
121
|
1 |
|
$sub_parts = []; |
122
|
1 |
|
foreach ($item as list($union_field_name, $union_value)) { |
123
|
1 |
|
$named_param = $QueryBuilder->createNamedParameter($union_value); |
124
|
1 |
|
$sub_parts[] = $QueryBuilder->expr()->eq($union_field_name, $named_param); |
125
|
1 |
|
$QueryBuilder->setParameter($named_param, $union_value); |
126
|
|
|
} |
127
|
1 |
|
$parts[] = $QueryBuilder->expr()->andX(...$sub_parts); |
128
|
|
|
} |
129
|
1 |
|
$where_expr = $QueryBuilder->expr()->orX(...$parts); |
130
|
35 |
|
} elseif (\is_array($value)) { |
131
|
15 |
|
$named_params = []; |
132
|
15 |
|
foreach ($value as $item) { |
133
|
15 |
|
$named_param = $QueryBuilder->createNamedParameter($item); |
134
|
15 |
|
$named_params[] = $named_param; |
135
|
15 |
|
$QueryBuilder->setParameter($named_param, $item); |
136
|
|
|
} |
137
|
15 |
|
$where_expr = $field_name . ' IN (' . implode(',', $named_params) . ')'; |
138
|
|
|
} else { |
139
|
25 |
|
$value = $this->prepareValue($value, $operator); |
140
|
25 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
141
|
25 |
|
$QueryBuilder->setParameter($named_param, $value); |
142
|
25 |
|
$where_expr = $QueryBuilder->expr()->comparison($field_name, $operator, $named_param); |
143
|
|
|
} |
144
|
|
|
|
145
|
35 |
|
if ($is_first) { |
146
|
35 |
|
$QueryBuilder->where($where_expr); |
147
|
35 |
|
$is_first = false; |
148
|
35 |
|
continue; |
149
|
|
|
} |
150
|
19 |
|
$QueryBuilder->andWhere($where_expr); |
151
|
|
|
} |
152
|
35 |
|
} |
153
|
|
|
|
154
|
25 |
|
protected function prepareValue($value, string $operator) |
155
|
|
|
{ |
156
|
25 |
|
if ($operator === 'like') { |
157
|
1 |
|
if ($this->Conn->getDriver()->getName() !== 'pdo_sqlite') { |
158
|
|
|
$value = addcslashes($value, '%_\\'); |
159
|
|
|
} |
160
|
1 |
|
$value = '%' . $value . '%'; |
161
|
|
|
} |
162
|
|
|
|
163
|
25 |
|
return $value; |
164
|
|
|
} |
165
|
|
|
|
166
|
9 |
|
protected function fillQueryBuilderGroup(QueryBuilder $QueryBuilder, array $group) |
167
|
|
|
{ |
168
|
9 |
|
$is_first = true; |
169
|
9 |
|
foreach ($group as $field) { |
170
|
9 |
|
Field::validate($field); |
171
|
9 |
|
if ($is_first) { |
172
|
9 |
|
$QueryBuilder->groupBy($field); |
173
|
9 |
|
$is_first = false; |
174
|
|
|
} else { |
175
|
9 |
|
$QueryBuilder->addGroupBy($field); |
176
|
|
|
} |
177
|
|
|
} |
178
|
9 |
|
} |
179
|
|
|
|
180
|
1 |
|
protected function fillQueryBuilderHaving(QueryBuilder $QueryBuilder, array $havings) |
181
|
|
|
{ |
182
|
1 |
|
$is_first = true; |
183
|
1 |
|
foreach ($havings as $having) { |
184
|
1 |
|
if (empty($having[2])) { |
185
|
1 |
|
$having[2] = '='; |
186
|
|
|
} else { |
187
|
1 |
|
Operator::validate($having[2]); |
188
|
|
|
} |
189
|
1 |
|
list($field_name, $value, $operator) = $having; |
190
|
1 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
191
|
1 |
|
$having_expr = $QueryBuilder->expr()->comparison($field_name, $operator, $named_param); |
192
|
1 |
|
if ($is_first) { |
193
|
1 |
|
$QueryBuilder->having($having_expr); |
194
|
1 |
|
$is_first = false; |
195
|
|
|
} else { |
196
|
1 |
|
$QueryBuilder->andHaving($having_expr); |
197
|
|
|
} |
198
|
1 |
|
$QueryBuilder->setParameter($named_param, $value); |
199
|
|
|
} |
200
|
1 |
|
} |
201
|
|
|
|
202
|
14 |
|
protected function fillQueryBuilderOrder(QueryBuilder $QueryBuilder, array $order) |
203
|
|
|
{ |
204
|
14 |
|
$is_first = true; |
205
|
14 |
|
foreach ($order as $field => $direction) { |
206
|
14 |
|
Field::validate($field); |
207
|
14 |
|
Direction::validate($direction); |
208
|
14 |
|
if ($is_first) { |
209
|
14 |
|
$QueryBuilder->orderBy($field, $direction); |
210
|
14 |
|
$is_first = false; |
211
|
|
|
} else { |
212
|
14 |
|
$QueryBuilder->addOrderBy($field, $direction); |
213
|
|
|
} |
214
|
|
|
} |
215
|
14 |
|
} |
216
|
|
|
|
217
|
13 |
|
protected function fillQueryBuilderLimit(QueryBuilder $QueryBuilder, int $limit) |
218
|
|
|
{ |
219
|
13 |
|
$QueryBuilder->setMaxResults($limit); |
220
|
13 |
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @param string $sql |
224
|
|
|
* @return \Doctrine\DBAL\Driver\Statement|false |
225
|
|
|
*/ |
226
|
71 |
|
public function query(string $sql) |
227
|
|
|
{ |
228
|
|
|
try { |
229
|
71 |
|
return $this->Conn->query($sql); |
230
|
1 |
|
} catch (DBALException $Ex) { |
|
|
|
|
231
|
1 |
|
return false; |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @param string $sql |
237
|
|
|
* @return bool |
238
|
|
|
* @throws \Doctrine\DBAL\DBALException |
239
|
|
|
*/ |
240
|
1 |
|
public function multiQuery(string $sql) : bool |
241
|
|
|
{ |
242
|
1 |
|
$queries = array_filter(array_map('trim', explode(";\n\n", $sql))); |
243
|
|
|
|
244
|
1 |
|
foreach ($queries as $query) { |
245
|
1 |
|
$this->Conn->exec($query); |
246
|
|
|
} |
247
|
|
|
|
248
|
1 |
|
return true; |
249
|
|
|
} |
250
|
|
|
|
251
|
39 |
|
protected function prepareFieldList(array $fields) : string |
252
|
|
|
{ |
253
|
39 |
|
if (\in_array('all', $fields, true)) { |
254
|
22 |
|
return '*'; |
255
|
|
|
} |
256
|
|
|
|
257
|
17 |
|
foreach ($fields as $key => $field) { |
258
|
17 |
|
if (\is_array($field)) { |
259
|
6 |
|
if (!empty($field['function'])) { |
260
|
5 |
|
Functions::validate($field['function']); |
261
|
5 |
|
$fields[$key] = $field['function'] . '(' . $field['field'] . ')'; |
262
|
|
|
} else { |
263
|
1 |
|
$fields[$key] = $field['field']; |
264
|
|
|
} |
265
|
6 |
|
$alias = $field['alias'] ?? $field['field']; |
266
|
6 |
|
Field::validate($field['field']); |
267
|
6 |
|
Field::validate($alias); |
268
|
6 |
|
$fields[$key] .= ' ' . $alias; |
269
|
|
|
} else { |
270
|
17 |
|
Field::validate($field); |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
|
274
|
17 |
|
return implode(',', $fields); |
275
|
|
|
} |
276
|
|
|
|
277
|
71 |
|
public function insert(string $table, array $fields) : int |
278
|
|
|
{ |
279
|
71 |
|
$this->insertMany($table, [$fields]); |
280
|
|
|
|
281
|
70 |
|
return $this->getLastInsertId(); |
282
|
|
|
} |
283
|
|
|
|
284
|
70 |
|
protected function getLastInsertId() : int |
285
|
|
|
{ |
286
|
70 |
|
return $this->lastInsertId; |
287
|
|
|
} |
288
|
|
|
|
289
|
70 |
|
protected function setLastInsertId(int $lastInsertId) |
290
|
|
|
{ |
291
|
70 |
|
$this->lastInsertId = $lastInsertId; |
292
|
70 |
|
} |
293
|
|
|
|
294
|
74 |
|
public function insertMany(string $table, array $fields) : bool |
295
|
|
|
{ |
296
|
74 |
|
Table::validate($table); |
297
|
|
|
|
298
|
74 |
|
if (empty($fields)) { |
299
|
1 |
|
throw new InvalidFieldValueException('Can\'t insert empty data'); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
try { |
303
|
73 |
|
$this->Conn->beginTransaction(); |
304
|
73 |
|
foreach ($fields as $field) { |
305
|
73 |
|
if (!$field) { |
306
|
1 |
|
throw new InvalidFieldValueException('Can\'t insert empty data'); |
307
|
|
|
} |
308
|
|
|
|
309
|
72 |
|
$QueryBuilder = $this->Conn->createQueryBuilder(); |
310
|
72 |
|
$QueryBuilder->insert($table); |
311
|
72 |
|
foreach ($field as $field_name => $value) { |
312
|
72 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
313
|
72 |
|
$QueryBuilder->setValue($field_name, $named_param); |
314
|
72 |
|
$QueryBuilder->setParameter($named_param, $value); |
315
|
|
|
} |
316
|
72 |
|
$QueryBuilder->execute(); |
317
|
|
|
} |
318
|
|
|
|
319
|
70 |
|
$this->setLastInsertId((int)$this->Conn->lastInsertId()); |
320
|
70 |
|
$this->Conn->commit(); |
321
|
3 |
|
} catch (DBALException $Ex) { |
|
|
|
|
322
|
2 |
|
throw new DatabaseException('Can\'t insert into ' . $table . ': ' . $Ex->getMessage()); |
323
|
|
|
} |
324
|
|
|
|
325
|
70 |
|
return true; |
326
|
|
|
} |
327
|
|
|
|
328
|
4 |
|
public function delete(string $table, array $params) : bool |
329
|
|
|
{ |
330
|
4 |
|
Table::validate($table); |
331
|
|
|
|
332
|
4 |
|
if (empty($params)) { |
333
|
1 |
|
throw new \InvalidArgumentException('Can\'t delete without any conditions'); |
334
|
|
|
} |
335
|
|
|
|
336
|
3 |
|
$QueryBuilder = $this->Conn->createQueryBuilder(); |
337
|
3 |
|
$QueryBuilder->delete($table); |
338
|
|
|
|
339
|
3 |
|
$is_first = true; |
340
|
3 |
|
foreach ($params as $field => $value) { |
341
|
3 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
342
|
3 |
|
if ($is_first) { |
343
|
3 |
|
$QueryBuilder->where($field . ' = ' . $named_param); |
344
|
3 |
|
$is_first = false; |
345
|
|
|
} else { |
346
|
|
|
$QueryBuilder->andWhere($field . ' = ' . $named_param); |
347
|
|
|
} |
348
|
3 |
|
$QueryBuilder->setParameter($named_param, $value); |
349
|
|
|
} |
350
|
|
|
|
351
|
3 |
|
$affected_rows = $QueryBuilder->execute(); |
352
|
|
|
|
353
|
3 |
|
return $affected_rows >= 0; |
354
|
|
|
} |
355
|
|
|
|
356
|
4 |
|
public function update(string $table, array $fields, array $params) : bool |
357
|
|
|
{ |
358
|
4 |
|
Table::validate($table); |
359
|
|
|
|
360
|
4 |
|
if (empty($fields)) { |
361
|
1 |
|
throw new \InvalidArgumentException('Can\'t update without any fields'); |
362
|
|
|
} |
363
|
|
|
|
364
|
3 |
|
if (empty($params)) { |
365
|
1 |
|
throw new \InvalidArgumentException('Can\'t update without any conditions'); |
366
|
|
|
} |
367
|
|
|
|
368
|
2 |
|
$QueryBuilder = $this->Conn->createQueryBuilder(); |
369
|
2 |
|
$QueryBuilder->update($table); |
370
|
|
|
|
371
|
2 |
|
foreach ($fields as $field => $value) { |
372
|
2 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
373
|
2 |
|
$QueryBuilder->set($field, $named_param); |
374
|
2 |
|
$QueryBuilder->setParameter($named_param, $value); |
375
|
|
|
} |
376
|
|
|
|
377
|
2 |
|
$is_first = true; |
378
|
2 |
|
foreach ($params as $field => $value) { |
379
|
2 |
|
$named_param = $QueryBuilder->createNamedParameter($value); |
380
|
2 |
|
if ($is_first) { |
381
|
2 |
|
$QueryBuilder->where($field . ' = ' . $named_param); |
382
|
2 |
|
$is_first = false; |
383
|
|
|
} else { |
384
|
|
|
$QueryBuilder->andWhere($field . ' = ' . $named_param); |
385
|
|
|
} |
386
|
2 |
|
$QueryBuilder->setParameter($named_param, $value); |
387
|
|
|
} |
388
|
|
|
|
389
|
2 |
|
$affected_rows = $QueryBuilder->execute(); |
390
|
|
|
|
391
|
2 |
|
return $affected_rows >= 0; |
392
|
|
|
} |
393
|
|
|
} |
394
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.