Completed
Pull Request — master (#3)
by
unknown
01:54
created

Storage::setLastInsertId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
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 82
    public function __construct(string $url)
33
    {
34 82
        $config = new \Doctrine\DBAL\Configuration();
35 82
        $connectionParams = ['url' => $url];
36
37
        try {
38 82
            $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 82
        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 82
    }
47
48
    public function getType() : string
49
    {
50
        return $this->Conn->getDriver()->getName();
51
    }
52
53 26
    public function getAll(string $table, array $fields, array $params) : array
54
    {
55 26
        $Result = $this->getSelectQueryBuilder($table, $fields, $params)->execute();
56 26
        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 26
        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 37
    protected function getSelectQueryBuilder(string $table, array $fields, array $params) : QueryBuilder
79
    {
80 37
        Table::validate($table);
81
82 37
        $QueryBuilder = $this->Conn->createQueryBuilder();
83 37
        $QueryBuilder->select($this->prepareFieldList($fields))->from($table);
84
85 37
        if (!empty($params['filter'])) {
86 33
            $this->fillQueryBuilderFilter($QueryBuilder, $params['filter']);
87
        }
88
89 37
        if (!empty($params['group'])) {
90 8
            $this->fillQueryBuilderGroup($QueryBuilder, $params['group']);
91
        }
92
93 37
        if (!empty($params['having'])) {
94 1
            $this->fillQueryBuilderHaving($QueryBuilder, $params['having']);
95
        }
96
97 37
        if (!empty($params['order'])) {
98 13
            $this->fillQueryBuilderOrder($QueryBuilder, $params['order']);
99
        }
100
101 37
        if (!empty($params['limit'])) {
102 12
            $this->fillQueryBuilderLimit($QueryBuilder, (int)$params['limit']);
103
        }
104
105 37
        return $QueryBuilder;
106
    }
107
108 33
    protected function fillQueryBuilderFilter(QueryBuilder $QueryBuilder, array $filter)
109
    {
110 33
        $is_first = true;
111 33
        foreach ($filter as $param) {
112 33
            if (empty($param[2])) {
113 28
                $param[2] = '=';
114
            } else {
115 10
                Operator::validate($param[2]);
116
            }
117 33
            list($field_name, $value, $operator) = $param;
118
119 33
            if ($field_name === 'union') {
120 1
                $parts = [];
121 1
                foreach ($value as $item) {
122 1
                    $sub_parts = [];
123 1
                    foreach ($item as list($union_field_name, $union_value)) {
124 1
                        $named_param = $QueryBuilder->createNamedParameter($union_value);
125 1
                        $sub_parts[] = $QueryBuilder->expr()->eq($union_field_name, $named_param);
126 1
                        $QueryBuilder->setParameter($named_param, $union_value);
127
                    }
128 1
                    $parts[] = $QueryBuilder->expr()->andX(...$sub_parts);
129
                }
130 1
                $where_expr = $QueryBuilder->expr()->orX(...$parts);
131 33
            } elseif (\is_array($value)) {
132 15
                $named_params = [];
133 15
                foreach ($value as $item) {
134 15
                    $named_param = $QueryBuilder->createNamedParameter($item);
135 15
                    $named_params[] = $named_param;
136 15
                    $QueryBuilder->setParameter($named_param, $item);
137
                }
138 15
                $where_expr = $field_name . ' IN (' . implode(',', $named_params) . ')';
139
            } else {
140 23
                if ($operator === 'like') {
141 1
                    $value = $this->prepareLikeValue($value);
142
                }
143
144 23
                $named_param = $QueryBuilder->createNamedParameter($value);
145 23
                $QueryBuilder->setParameter($named_param, $value);
146 23
                $where_expr = $QueryBuilder->expr()->comparison($field_name, $operator, $named_param);
147
            }
148
149 33
            if ($is_first) {
150 33
                $QueryBuilder->where($where_expr);
151 33
                $is_first = false;
152
            } else {
153 33
                $QueryBuilder->andWhere($where_expr);
154
            }
155
        }
156 33
    }
157
158 1
    protected function prepareLikeValue(string $value) : string
159
    {
160 1
        if ($this->Conn->getDriver()->getName() !== 'pdo_sqlite') {
161
            $value = addcslashes($value, '%_\\');
162
        }
163 1
        $value = '%' . $value . '%';
164
165 1
        return $value;
166
    }
167
168 8
    protected function fillQueryBuilderGroup(QueryBuilder $QueryBuilder, array $group)
169
    {
170 8
        $is_first = true;
171 8
        foreach ($group as $field) {
172 8
            Field::validate($field);
173 8
            if ($is_first) {
174 8
                $QueryBuilder->groupBy($field);
175 8
                $is_first = false;
176
            } else {
177 8
                $QueryBuilder->addGroupBy($field);
178
            }
179
        }
180 8
    }
181
182 1
    protected function fillQueryBuilderHaving(QueryBuilder $QueryBuilder, array $havings)
183
    {
184 1
        $is_first = true;
185 1
        foreach ($havings as $having) {
186 1
            if (empty($having[2])) {
187 1
                $having[2] = '=';
188
            } else {
189 1
                Operator::validate($having[2]);
190
            }
191 1
            list($field_name, $value, $operator) = $having;
192 1
            $named_param = $QueryBuilder->createNamedParameter($value);
193 1
            $having_expr = $QueryBuilder->expr()->comparison($field_name, $operator, $named_param);
194 1
            if ($is_first) {
195 1
                $QueryBuilder->having($having_expr);
196 1
                $is_first = false;
197
            } else {
198 1
                $QueryBuilder->andHaving($having_expr);
199
            }
200 1
            $QueryBuilder->setParameter($named_param, $value);
201
        }
202 1
    }
203
204 13
    protected function fillQueryBuilderOrder(QueryBuilder $QueryBuilder, array $order)
205
    {
206 13
        $is_first = true;
207 13
        foreach ($order as $field => $direction) {
208 13
            Field::validate($field);
209 13
            Direction::validate($direction);
210 13
            if ($is_first) {
211 13
                $QueryBuilder->orderBy($field, $direction);
212 13
                $is_first = false;
213
            } else {
214 13
                $QueryBuilder->addOrderBy($field, $direction);
215
            }
216
        }
217 13
    }
218
219 12
    protected function fillQueryBuilderLimit(QueryBuilder $QueryBuilder, int $limit)
220
    {
221 12
        $QueryBuilder->setMaxResults($limit);
222 12
    }
223
224
    /**
225
     * @param string $sql
226
     * @return \Doctrine\DBAL\Driver\Statement|false
227
     */
228 70
    public function query(string $sql)
229
    {
230
        try {
231 70
            return $this->Conn->query($sql);
232 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...
233 1
            return false;
234
        }
235
    }
236
237
    /**
238
     * @param string $sql
239
     * @return bool
240
     * @throws \Doctrine\DBAL\DBALException
241
     */
242 1
    public function multiQuery(string $sql) : bool
243
    {
244 1
        $queries = array_filter(array_map('trim', explode(";\n\n", $sql)));
245
246 1
        foreach ($queries as $query) {
247 1
            $this->Conn->exec($query);
248
        }
249
250 1
        return true;
251
    }
252
253 37
    protected function prepareFieldList(array $fields) : string
254
    {
255 37
        if (\in_array('all', $fields, true)) {
256 22
            return '*';
257
        }
258
259 15
        foreach ($fields as $key => $field) {
260 15
            if (\is_array($field)) {
261 6
                if (!empty($field['function'])) {
262 5
                    Functions::validate($field['function']);
263 5
                    $fields[$key] = $field['function'] . '(' . $field['field'] . ')';
264
                } else {
265 1
                    $fields[$key] = $field['field'];
266
                }
267 6
                $alias = $field['alias'] ?? $field['field'];
268 6
                Field::validate($field['field']);
269 6
                Field::validate($alias);
270 6
                $fields[$key] .= ' ' . $alias;
271
            } else {
272 15
                Field::validate($field);
273
            }
274
        }
275
276 15
        return implode(',', $fields);
277
    }
278
279 70
    public function insert(string $table, array $fields) : int
280
    {
281 70
        $this->insertMany($table, [$fields]);
282
283 69
        return $this->getLastInsertId();
284
    }
285
286 69
    protected function getLastInsertId() : int
287
    {
288 69
        return $this->lastInsertId;
289
    }
290
291 69
    protected function setLastInsertId(int $lastInsertId)
292
    {
293 69
        $this->lastInsertId = $lastInsertId;
294 69
    }
295
296 73
    public function insertMany(string $table, array $fields) : bool
297
    {
298 73
        Table::validate($table);
299
300 73
        if (empty($fields)) {
301 1
            throw new InvalidFieldValueException('Can\'t insert empty data');
302
        }
303
304
        try {
305 72
            $this->Conn->beginTransaction();
306 72
            foreach ($fields as $field) {
307 72
                if (!$field) {
308 1
                    throw new InvalidFieldValueException('Can\'t insert empty data');
309
                }
310
311 71
                $QueryBuilder = $this->Conn->createQueryBuilder();
312 71
                $QueryBuilder->insert($table);
313 71
                foreach ($field as $field_name => $value) {
314 71
                    $named_param = $QueryBuilder->createNamedParameter($value);
315 71
                    $QueryBuilder->setValue($field_name, $named_param);
316 71
                    $QueryBuilder->setParameter($named_param, $value);
317
                }
318 71
                $QueryBuilder->execute();
319
            }
320
321 69
            $this->setLastInsertId((int)$this->Conn->lastInsertId());
322 69
            $this->Conn->commit();
323 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...
324 2
            throw new DatabaseException('Can\'t insert into ' . $table);
325
        }
326
327 69
        return true;
328
    }
329
330 4
    public function delete(string $table, array $params) : bool
331
    {
332 4
        Table::validate($table);
333
334 4
        if (empty($params)) {
335 1
            throw new \InvalidArgumentException('Can\'t delete without any conditions');
336
        }
337
338 3
        $QueryBuilder = $this->Conn->createQueryBuilder();
339 3
        $QueryBuilder->delete($table);
340
341 3
        $is_first = true;
342 3
        foreach ($params as $field => $value) {
343 3
            $named_param = $QueryBuilder->createNamedParameter($value);
344 3
            if ($is_first) {
345 3
                $QueryBuilder->where($field . ' = ' . $named_param);
346 3
                $is_first = false;
347
            } else {
348
                $QueryBuilder->andWhere($field . ' = ' . $named_param);
349
            }
350 3
            $QueryBuilder->setParameter($named_param, $value);
351
        }
352
353 3
        $affected_rows = $QueryBuilder->execute();
354
355 3
        return $affected_rows >= 0;
356
    }
357
358 4
    public function update(string $table, array $fields, array $params) : bool
359
    {
360 4
        Table::validate($table);
361
362 4
        if (empty($fields)) {
363 1
            throw new \InvalidArgumentException('Can\'t update without any fields');
364
        }
365
366 3
        if (empty($params)) {
367 1
            throw new \InvalidArgumentException('Can\'t update without any conditions');
368
        }
369
370 2
        $QueryBuilder = $this->Conn->createQueryBuilder();
371 2
        $QueryBuilder->update($table);
372
373 2
        foreach ($fields as $field => $value) {
374 2
            $named_param = $QueryBuilder->createNamedParameter($value);
375 2
            $QueryBuilder->set($field, $named_param);
376 2
            $QueryBuilder->setParameter($named_param, $value);
377
        }
378
379 2
        $is_first = true;
380 2
        foreach ($params as $field => $value) {
381 2
            $named_param = $QueryBuilder->createNamedParameter($value);
382 2
            if ($is_first) {
383 2
                $QueryBuilder->where($field . ' = ' . $named_param);
384 2
                $is_first = false;
385
            } else {
386
                $QueryBuilder->andWhere($field . ' = ' . $named_param);
387
            }
388 2
            $QueryBuilder->setParameter($named_param, $value);
389
        }
390
391 2
        $affected_rows = $QueryBuilder->execute();
392
393 2
        return $affected_rows >= 0;
394
    }
395
}
396