DbQuery   F
last analyzed

Complexity

Total Complexity 76

Size/Duplication

Total Lines 452
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 167
dl 0
loc 452
rs 2.32
c 4
b 0
f 0
wmc 76

44 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A array() 0 8 2
A avg() 0 8 2
A __call() 0 14 3
A avgArray() 0 5 1
A selectRaw() 0 5 1
A delete() 0 9 2
A whereNull() 0 5 1
A whereLt() 0 5 1
A table() 0 3 1
A whereBetween() 0 5 1
A select() 0 10 2
A minArray() 0 5 1
A maxArray() 0 5 1
A insertGetId() 0 7 2
A orderMulti() 0 7 2
A max() 0 8 2
A insert() 0 23 5
A reorder() 0 5 1
A offset() 0 5 1
A where() 0 17 4
A whereNotEq() 0 5 1
A whereNotNull() 0 5 1
A count() 0 15 5
A sql() 0 21 5
A limit() 0 5 1
A orderBy() 0 10 2
A min() 0 8 2
A whereGte() 0 5 1
A countArray() 0 5 1
A sum() 0 8 2
A groupBy() 0 10 2
A whereNotIn() 0 5 1
A whereGt() 0 5 1
A whereLte() 0 5 1
A get() 0 3 1
A whereLike() 0 5 1
A having() 0 5 1
A whereIn() 0 5 1
A first() 0 10 2
A whereEq() 0 5 1
A db() 0 7 2
A sumArray() 0 5 1
A update() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like DbQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DbQuery, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2023-2024 Carlos Garcia Gomez <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScripts\Core;
21
22
use Exception;
23
use FacturaScripts\Core\Base\DataBase;
24
25
/**
26
 * @author Carlos García Gómez <[email protected]>
27
 */
28
final class DbQuery
29
{
30
    /** @var DataBase */
31
    private static $db;
32
33
    /** @var string */
34
    public $fields = '*';
35
36
    /** @var string */
37
    public $groupBy;
38
39
    /** @var string */
40
    public $having;
41
42
    /** @var int */
43
    public $limit = 0;
44
45
    /** @var int */
46
    public $offset = 0;
47
48
    /** @var array */
49
    public $orderBy = [];
50
51
    /** @var string */
52
    private $table;
53
54
    /** @var Where[] */
55
    private $where = [];
56
57
    public function __construct(string $table)
58
    {
59
        $this->table = $table;
60
    }
61
62
    public function __call($method, $parameters)
63
    {
64
        // Si se llama al where dinámicamente
65
        // whereNombre(), whereCiudad()
66
        if (str_starts_with($method, 'where')) {
67
            $field = strtolower(substr($method, 5));
68
            return $this->whereEq($field, $parameters[0]);
69
        }
70
71
        if (false === method_exists($this, $method)) {
72
            throw new Exception('Call to undefined method ' . get_class($this) . '::' . $method . '()');
73
        }
74
75
        return $this->$method(...$parameters);
76
    }
77
78
    public function array(string $key, string $value): array
79
    {
80
        $result = [];
81
        foreach ($this->get() as $row) {
82
            $result[$row[$key]] = $row[$value];
83
        }
84
85
        return $result;
86
    }
87
88
    public function avg(string $field, ?int $decimals = null): float
89
    {
90
        $this->fields = 'AVG(' . self::db()->escapeColumn($field) . ') as _avg';
91
92
        $row = $this->first();
93
        return is_null($decimals) ?
94
            (float)$row['_avg'] :
95
            round((float)$row['_avg'], $decimals);
96
    }
97
98
    public function avgArray(string $field, string $groupByKey): array
99
    {
100
        $this->fields = self::db()->escapeColumn($groupByKey) . ', AVG(' . self::db()->escapeColumn($field) . ') as _avg';
101
102
        return $this->groupBy($groupByKey)->array($groupByKey, '_avg');
103
    }
104
105
    public function count(string $field = ''): int
106
    {
107
        if ($field !== '') {
108
            $this->fields = 'COUNT(DISTINCT ' . self::db()->escapeColumn($field) . ') as _count';
109
        } elseif ($this->fields === '*' || empty($this->fields)) {
110
            $this->fields = 'COUNT(*) as _count';
111
        } else {
112
            $this->fields = 'COUNT(' . $this->fields . ') as _count';
113
        }
114
115
        foreach ($this->first() as $value) {
116
            return (int)$value;
117
        }
118
119
        return 0;
120
    }
121
122
    public function countArray(string $field, string $groupByKey): array
123
    {
124
        $this->fields = self::db()->escapeColumn($groupByKey) . ', COUNT(' . self::db()->escapeColumn($field) . ') as _count';
125
126
        return $this->groupBy($groupByKey)->array($groupByKey, '_count');
127
    }
128
129
    public function delete(): bool
130
    {
131
        $sql = 'DELETE FROM ' . self::db()->escapeColumn($this->table);
132
133
        if (!empty($this->where)) {
134
            $sql .= ' WHERE ' . Where::multiSql($this->where);
135
        }
136
137
        return self::db()->exec($sql);
138
    }
139
140
    public function first(): array
141
    {
142
        $this->limit = 1;
143
        $this->offset = 0;
144
145
        foreach ($this->get() as $row) {
146
            return $row;
147
        }
148
149
        return [];
150
    }
151
152
    public function get(): array
153
    {
154
        return self::db()->selectLimit($this->sql(), $this->limit, $this->offset);
155
    }
156
157
    public function groupBy(string $fields): self
158
    {
159
        $list = [];
160
        foreach (explode(',', $fields) as $field) {
161
            $list[] = self::db()->escapeColumn(trim($field));
162
        }
163
164
        $this->groupBy = implode(', ', $list);
165
166
        return $this;
167
    }
168
169
    public function having(string $having): self
170
    {
171
        $this->having = $having;
172
173
        return $this;
174
    }
175
176
    public function insert(array $data): bool
177
    {
178
        if (empty($data)) {
179
            return false;
180
        }
181
182
        $fields = [];
183
        foreach (array_keys($data[0]) as $field) {
184
            $fields[] = self::db()->escapeColumn($field);
185
        }
186
187
        $values = [];
188
        foreach ($data as $row) {
189
            $line = [];
190
            foreach ($row as $value) {
191
                $line[] = self::db()->var2str($value);
192
            }
193
            $values[] = '(' . implode(', ', $line) . ')';
194
        }
195
196
        $sql = 'INSERT INTO ' . self::db()->escapeColumn($this->table)
197
            . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $values) . ';';
198
        return self::db()->exec($sql);
199
    }
200
201
    public function insertGetId(array $data): ?int
202
    {
203
        if ($this->insert($data)) {
204
            return self::db()->lastval();
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::db()->lastval() could return the type boolean which is incompatible with the type-hinted return integer|null. Consider adding an additional type-check to rule them out.
Loading history...
205
        }
206
207
        return null;
208
    }
209
210
    public function limit(int $limit): self
211
    {
212
        $this->limit = $limit;
213
214
        return $this;
215
    }
216
217
    public function max(string $field, ?int $decimals = null): float
218
    {
219
        $this->fields = 'MAX(' . self::db()->escapeColumn($field) . ') as _max';
220
221
        $row = $this->first();
222
        return is_null($decimals) ?
223
            (float)$row['_max'] :
224
            round((float)$row['_max'], $decimals);
225
    }
226
227
    public function maxArray(string $field, string $groupByKey): array
228
    {
229
        $this->fields = self::db()->escapeColumn($groupByKey) . ', MAX(' . self::db()->escapeColumn($field) . ') as _max';
230
231
        return $this->groupBy($groupByKey)->array($groupByKey, '_max');
232
    }
233
234
    public function min(string $field, ?int $decimals = null): float
235
    {
236
        $this->fields = 'MIN(' . self::db()->escapeColumn($field) . ') as _min';
237
238
        $row = $this->first();
239
        return is_null($decimals) ?
240
            (float)$row['_min'] :
241
            round((float)$row['_min'], $decimals);
242
    }
243
244
    public function minArray(string $field, string $groupByKey): array
245
    {
246
        $this->fields = self::db()->escapeColumn($groupByKey) . ', MIN(' . self::db()->escapeColumn($field) . ') as _min';
247
248
        return $this->groupBy($groupByKey)->array($groupByKey, '_min');
249
    }
250
251
    public function offset(int $offset): self
252
    {
253
        $this->offset = $offset;
254
255
        return $this;
256
    }
257
258
    public function orderBy(string $field, string $order = 'ASC'): self
259
    {
260
        // si el campo comienza por integer: hacemos el cast a integer
261
        if (0 === strpos($field, 'integer:')) {
262
            $field = self::db()->castInteger(substr($field, 8));
263
        }
264
265
        $this->orderBy[] = self::db()->escapeColumn($field) . ' ' . $order;
266
267
        return $this;
268
    }
269
270
    public function orderMulti(array $fields): self
271
    {
272
        foreach ($fields as $field => $order) {
273
            $this->orderBy($field, $order);
274
        }
275
276
        return $this;
277
    }
278
279
    public function reorder(): self
280
    {
281
        $this->orderBy = [];
282
283
        return $this;
284
    }
285
286
    public function select(string $fields): self
287
    {
288
        $list = [];
289
        foreach (explode(',', $fields) as $field) {
290
            $list[] = self::db()->escapeColumn(trim($field));
291
        }
292
293
        $this->fields = implode(', ', $list);
294
295
        return $this;
296
    }
297
298
    public function selectRaw(string $fields): self
299
    {
300
        $this->fields = $fields;
301
302
        return $this;
303
    }
304
305
    public function sql(): string
306
    {
307
        $sql = 'SELECT ' . $this->fields . ' FROM ' . self::db()->escapeColumn($this->table);
308
309
        if (!empty($this->where)) {
310
            $sql .= ' WHERE ' . Where::multiSql($this->where);
311
        }
312
313
        if (!empty($this->groupBy)) {
314
            $sql .= ' GROUP BY ' . $this->groupBy;
315
        }
316
317
        if (!empty($this->having)) {
318
            $sql .= ' HAVING ' . $this->having;
319
        }
320
321
        if (!empty($this->orderBy)) {
322
            $sql .= ' ORDER BY ' . implode(', ', $this->orderBy);
323
        }
324
325
        return $sql;
326
    }
327
328
    public function sum(string $field, ?int $decimals = null): float
329
    {
330
        $this->fields = 'SUM(' . self::db()->escapeColumn($field) . ') as _sum';
331
332
        $row = $this->first();
333
        return is_null($decimals) ?
334
            (float)$row['_sum'] :
335
            round((float)$row['_sum'], $decimals);
336
    }
337
338
    public function sumArray(string $field, string $groupByKey): array
339
    {
340
        $this->fields = self::db()->escapeColumn($groupByKey) . ', SUM(' . self::db()->escapeColumn($field) . ') as _sum';
341
342
        return $this->groupBy($groupByKey)->array($groupByKey, '_sum');
343
    }
344
345
    public static function table(string $table): self
346
    {
347
        return new self($table);
348
    }
349
350
    public function update(array $data): bool
351
    {
352
        if (empty($data)) {
353
            return false;
354
        }
355
356
        $fields = [];
357
        foreach ($data as $field => $value) {
358
            $fields[] = self::db()->escapeColumn($field) . ' = ' . self::db()->var2str($value);
359
        }
360
361
        $sql = 'UPDATE ' . self::db()->escapeColumn($this->table) . ' SET ' . implode(', ', $fields);
362
        return self::db()->exec($sql);
363
    }
364
365
    /**
366
     * @param Where[] $where
367
     * @return $this
368
     * @throws Exception
369
     */
370
    public function where(array $where): self
371
    {
372
        // si el array está vacío, no hacemos nada
373
        if (empty($where)) {
374
            return $this;
375
        }
376
377
        foreach ($where as $value) {
378
            // si no es una instancia de Where, lanzamos una excepción
379
            if (!($value instanceof Where)) {
380
                throw new Exception('Invalid where clause ' . print_r($value, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($value, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

380
                throw new Exception('Invalid where clause ' . /** @scrutinizer ignore-type */ print_r($value, true));
Loading history...
381
            }
382
383
            $this->where[] = $value;
384
        }
385
386
        return $this;
387
    }
388
389
    public function whereBetween(string $field, $value1, $value2): self
390
    {
391
        $this->where[] = Where::between($field, $value1, $value2);
392
393
        return $this;
394
    }
395
396
    public function whereEq(string $field, $value): self
397
    {
398
        $this->where[] = Where::eq($field, $value);
399
400
        return $this;
401
    }
402
403
    public function whereGt(string $field, $value): self
404
    {
405
        $this->where[] = Where::gt($field, $value);
406
407
        return $this;
408
    }
409
410
    public function whereGte(string $field, $value): self
411
    {
412
        $this->where[] = Where::gte($field, $value);
413
414
        return $this;
415
    }
416
417
    public function whereIn(string $field, array $values): self
418
    {
419
        $this->where[] = Where::in($field, $values);
420
421
        return $this;
422
    }
423
424
    public function whereLike(string $field, string $value): self
425
    {
426
        $this->where[] = Where::like($field, $value);
427
428
        return $this;
429
    }
430
431
    public function whereLt(string $field, $value): self
432
    {
433
        $this->where[] = Where::lt($field, $value);
434
435
        return $this;
436
    }
437
438
    public function whereLte(string $field, $value): self
439
    {
440
        $this->where[] = Where::lte($field, $value);
441
442
        return $this;
443
    }
444
445
    public function whereNotEq(string $field, $value): self
446
    {
447
        $this->where[] = Where::notEq($field, $value);
448
449
        return $this;
450
    }
451
452
    public function whereNotIn(string $field, array $values): self
453
    {
454
        $this->where[] = Where::notIn($field, $values);
455
456
        return $this;
457
    }
458
459
    public function whereNotNull(string $field): self
460
    {
461
        $this->where[] = Where::isNotNull($field);
462
463
        return $this;
464
    }
465
466
    public function whereNull(string $field): self
467
    {
468
        $this->where[] = Where::isNull($field);
469
470
        return $this;
471
    }
472
473
    private static function db(): DataBase
474
    {
475
        if (null === self::$db) {
476
            self::$db = new DataBase();
477
        }
478
479
        return self::$db;
480
    }
481
}
482