Completed
Push — master ( 94708f...b171a6 )
by Shagiakhmetov
02:18
created

Storage::prepareValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 9.9
c 0
b 0
f 0
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) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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.

Loading history...
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