Completed
Push — master ( eb4f06...1a7436 )
by Ivan
03:42
created

TableQuery::ids()   F

Complexity

Conditions 38
Paths > 20000

Size

Total Lines 149
Code Lines 109

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 1482

Importance

Changes 0
Metric Value
dl 0
loc 149
ccs 0
cts 106
cp 0
rs 2
c 0
b 0
f 0
cc 38
eloc 109
nc 9465600
nop 0
crap 1482

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace vakata\database\schema;
3
4
use vakata\collection\Collection;
5
use vakata\database\DBInterface;
6
use vakata\database\DBException;
7
use vakata\database\ResultInterface;
8
9
/**
10
 * A database query class
11
 */
12
class TableQuery implements \IteratorAggregate, \ArrayAccess, \Countable
13
{
14
    const SEP = '___';
15
    /**
16
     * @var DBInterface
17
     */
18
    protected $db;
19
    /**
20
     * @var Table
21
     */
22
    protected $definition;
23
    /**
24
     * @var TableQueryIterator|null
25
     */
26
    protected $qiterator;
27
28
    /**
29
     * @var array
30
     */
31
    protected $where = [];
32
    /**
33
     * @var array
34
     */
35
    protected $order = [];
36
    /**
37
     * @var array
38
     */
39
    protected $group = [];
40
    /**
41
     * @var array
42
     */
43
    protected $having = [];
44
    /**
45
     * @var int[]
46
     */
47
    protected $li_of = [0,0,0];
48
    /**
49
     * @var array
50
     */
51
    protected $fields = [];
52
    /**
53
     * @var array
54
     */
55
    protected $withr = [];
56
    /**
57
     * @var array
58
     */
59
    protected $joins = [];
60
    /**
61
     * @var array
62
     */
63
    protected $pkey = [];
64
    /**
65
     * @var array
66
     */
67
    protected $aliases = [];
68
69
    /**
70
     * Create an instance
71
     * @param  DBInterface    $db         the database connection
72
     * @param  Table|string   $table      the name or definition of the main table in the query
73
     */
74 13
    public function __construct(DBInterface $db, $table)
75
    {
76 13
        $this->db = $db;
77 13
        $this->definition = $table instanceof Table ? $table : $this->db->definition((string)$table);
78 13
        $primary = $this->definition->getPrimaryKey();
79 13
        $columns = $this->definition->getColumns();
80 13
        $this->pkey = count($primary) ? $primary : $columns;
81 13
        $this->columns($columns);
82 13
    }
83
    public function __clone()
84
    {
85
        $this->reset();
86
    }
87
    /**
88
     * Get the table definition of the queried table
89
     * @return Table        the definition
90
     */
91
    public function getDefinition() : Table
92
    {
93
        return $this->definition;
94
    }
95
96 13
    protected function getColumn($column)
97
    {
98 13
        $column = explode('.', $column);
99 13
        if (count($column) === 1) {
100 13
            $column = [ $this->definition->getName(), $column[0] ];
101 13
            $col = $this->definition->getColumn($column[1]);
102 13
            if (!$col) {
103 13
                throw new DBException('Invalid column name in own table');
104
            }
105 2
        } elseif (count($column) === 2) {
106 2
            if ($column[0] === $this->definition->getName()) {
107
                $col = $this->definition->getColumn($column[1]);
108
                if (!$col) {
109
                    throw new DBException('Invalid column name in own table');
110
                }
111
            } else {
112 2
                if ($this->definition->hasRelation($column[0])) {
113 1
                    $col = $this->definition->getRelation($column[0])->table->getColumn($column[1]);
114 1
                    if (!$col) {
115 1
                        throw new DBException('Invalid column name in related table');
116
                    }
117 1
                } else if (isset($this->joins[$column[0]])) {
118 1
                    $col = $this->joins[$column[0]]->table->getColumn($column[1]);
119 1
                    if (!$col) {
120 1
                        throw new DBException('Invalid column name in related table');
121
                    }
122
                } else {
123 2
                    throw new DBException('Invalid foreign table name: ' . implode(',', $column));
124
                }
125
            }
126
        } else {
127
            $name = array_pop($column);
128
            $this->with(implode('.', $column));
129
            $table = $this->definition;
130
            $table = array_reduce(
131
                $column,
132
                function ($carry, $item) use (&$table) {
133
                    $table = $table->getRelation($item)->table;
134
                    return $table;
135
                }
136
            );
137
            $col = $table->getColumn($name);
138
            $column = [ implode(static::SEP, $column), $name ];
139
        }
140 13
        return [ 'name' => implode('.', $column), 'data' => $col ];
141
    }
142 4
    protected function normalizeValue(TableColumn $col, $value)
143
    {
144 4
        if ($value === null && $col->isNullable()) {
145
            return null;
146
        }
147 4
        switch ($col->getBasicType()) {
148 4
            case 'date':
149
                if (is_string($value)) {
150
                    $temp = strtotime($value);
151
                    if (!$temp) {
152
                        return null;
153
                    }
154
                    return date('Y-m-d', strtotime($value));
155
                }
156
                if (is_int($value)) {
157
                    return date('Y-m-d', $value);
158
                }
159
                if ($value instanceof \DateTime) {
160
                    return $value->format('Y-m-d');
161
                }
162
                return $value;
163 4
            case 'datetime':
164
                if (is_string($value)) {
165
                    $temp = strtotime($value);
166
                    if (!$temp) {
167
                        return null;
168
                    }
169
                    return date('Y-m-d H:i:s', strtotime($value));
170
                }
171
                if (is_int($value)) {
172
                    return date('Y-m-d H:i:s', $value);
173
                }
174
                if ($value instanceof \DateTime) {
175
                    return $value->format('Y-m-d H:i:s');
176
                }
177
                return $value;
178 4
            case 'enum':
179
                if (is_int($value)) {
180
                    return $value;
181
                }
182
                if (!in_array($value, $col->getValues())) {
183
                    return 0;
184
                }
185
                return $value;
186 4
            case 'int':
187 2
                return (int)$value;
188
            default:
189 3
                return $value;
190
        }
191
    }
192
193
    /**
194
     * Filter the results by a column and a value
195
     * @param  string $column  the column name to filter by (related columns can be used - for example: author.name)
196
     * @param  mixed  $value   a required value, array of values or range of values (range example: ['beg'=>1,'end'=>3])
197
     * @param  bool   $negate  optional boolean indicating that the filter should be negated
198
     * @return $this
199
     */
200 2
    public function filter(string $column, $value, bool $negate = false) : TableQuery
201
    {
202 2
        list($name, $column) = array_values($this->getColumn($column));
203 2
        if (is_null($value)) {
204
            return $negate ?
205
                $this->where($name . ' IS NOT NULL') :
206
                $this->where($name . ' IS NULL');
207
        }
208 2
        if (!is_array($value)) {
209 2
            return $negate ?
210
                $this->where(
211
                    $name . ' <> ?',
212
                    [ $this->normalizeValue($column, $value) ]
213
                ) :
214 2
                $this->where(
215 2
                    $name . ' = ?',
216 2
                    [ $this->normalizeValue($column, $value) ]
217
                );
218
        }
219 1
        if (isset($value['beg']) && strlen($value['beg']) && (!isset($value['end']) || !strlen($value['end']))) {
220
            $value = [ 'gte' => $value['beg'] ];
221
        }
222 1
        if (isset($value['end']) && strlen($value['end']) && (!isset($value['beg']) || !strlen($value['beg']))) {
223
            $value = [ 'lte' => $value['end'] ];
224
        }
225 1
        if (isset($value['beg']) && isset($value['end'])) {
226
            return $negate ?
227
                $this->where(
228
                    $name.' NOT BETWEEN ? AND ?',
229
                    [
230
                        $this->normalizeValue($column, $value['beg']),
231
                        $this->normalizeValue($column, $value['end'])
232
                    ]
233
                ) :
234
                $this->where(
235
                    $name.' BETWEEN ? AND ?',
236
                    [
237
                        $this->normalizeValue($column, $value['beg']),
238
                        $this->normalizeValue($column, $value['end'])
239
                    ]
240
                );
241
        }
242 1
        if (isset($value['gt']) || isset($value['lt']) || isset($value['gte']) || isset($value['lte'])) {
243 1
            if (isset($value['gt'])) {
244 1
                $this->where(
245 1
                    $name. ' ' . ($negate ? '<=' : '>') . ' ?',
246 1
                    [ $this->normalizeValue($column, $value['gt']) ]
247
                );
248
            }
249 1
            if (isset($value['gte'])) {
250 1
                $this->where(
251 1
                    $name. ' ' . ($negate ? '<' : '>=') . ' ?',
252 1
                    [ $this->normalizeValue($column, $value['gte']) ]
253
                );
254
            }
255 1
            if (isset($value['lt'])) {
256 1
                $this->where(
257 1
                    $name. ' ' . ($negate ? '>=' : '<') . ' ?',
258 1
                    [ $this->normalizeValue($column, $value['lt']) ]
259
                );
260
            }
261 1
            if (isset($value['lte'])) {
262 1
                $this->where(
263 1
                    $name. ' ' . ($negate ? '>' : '<=') . ' ?',
264 1
                    [ $this->normalizeValue($column, $value['lte']) ]
265
                );
266
            }
267 1
            return $this;
268
        }
269
        return $negate ?
270
            $this->where(
271
                $name . ' NOT IN (??)',
272
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
273
            ) :
274
            $this->where(
275
                $name . ' IN (??)',
276
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
277
            );
278
    }
279
    /**
280
     * Sort by a column
281
     * @param  string       $column the column name to sort by (related columns can be used - for example: author.name)
282
     * @param  bool|boolean $desc   should the sorting be in descending order, defaults to `false`
283
     * @return $this
284
     */
285
    public function sort(string $column, bool $desc = false) : TableQuery
286
    {
287
        return $this->order($this->getColumn($column)['name'] . ' ' . ($desc ? 'DESC' : 'ASC'));
288
    }
289
    /**
290
     * Group by a column (or columns)
291
     * @param  string|array        $column the column name (or names) to group by
292
     * @return $this
293
     */
294 1
    public function group($column) : TableQuery
295
    {
296 1
        if (!is_array($column)) {
297 1
            $column = [ $column ];
298
        }
299 1
        foreach ($column as $k => $v) {
300 1
            $column[$k] = $this->getColumn($v)['name'];
301
        }
302 1
        return $this->groupBy(implode(', ', $column), []);
303
    }
304
    /**
305
     * Get a part of the data
306
     * @param  int|integer $page    the page number to get (1-based), defaults to 1
307
     * @param  int|integer $perPage the number of records per page - defaults to 25
308
     * @return $this
309
     */
310
    public function paginate(int $page = 1, int $perPage = 25) : TableQuery
311
    {
312
        return $this->limit($perPage, ($page - 1) * $perPage);
313
    }
314 1
    public function __call($name, $data)
315
    {
316 1
        if (strpos($name, 'filterBy') === 0) {
317
            return $this->filter(strtolower(substr($name, 8)), $data[0]);
318
        }
319 1
        if (strpos($name, 'sortBy') === 0) {
320
            return $this->sort(strtolower(substr($name, 6)), $data[0]);
321
        }
322 1
        if (strpos($name, 'groupBy') === 0) {
323 1
            return $this->group(strtolower(substr($name, 7)));
324
        }
325
    }
326
    /**
327
     * Remove all filters, sorting, etc
328
     * @return $this
329
     */
330
    public function reset() : TableQuery
331
    {
332
        $this->where = [];
333
        $this->joins = [];
334
        $this->group = [];
335
        $this->withr = [];
336
        $this->order = [];
337
        $this->having = [];
338
        $this->aliases = [];
339
        $this->li_of = [0,0,0];
340
        $this->qiterator = null;
341
        return $this;
342
    }
343
    /**
344
     * Apply advanced grouping
345
     * @param  string $sql    SQL statement to use in the GROUP BY clause
346
     * @param  array  $params optional params for the statement (defaults to an empty array)
347
     * @return $this
348
     */
349 1
    public function groupBy(string $sql, array $params = []) : TableQuery
350
    {
351 1
        $this->qiterator = null;
352 1
        $this->group = [ $sql, $params ];
353 1
        return $this;
354
    }
355
    /**
356
     * Join a table to the query (no need to do this for relations defined with foreign keys)
357
     * @param  Table|string $table     the table to join
358
     * @param  array        $fields    what to join on (joined_table_field => other_field) 
359
     * @param  string|null  $name      alias for the join, defaults to the table name 
360
     * @param  bool         $multiple  are multiple rows joined (results in a LEFT JOIN), default to true 
361
     * @return $this
362
     */
363 1
    public function join($table, array $fields, string $name = null, bool $multiple = true)
364
    {
365 1
        $table = $table instanceof Table ? $table : $this->db->definition((string)$table);
366 1
        $name = $name ?? $table->getName();
367 1
        if (isset($this->joins[$name]) || $this->definition->hasRelation($name)) {
368
            throw new DBException('Alias / table name already in use');
369
        }
370 1
        $this->joins[$name] = new TableRelation($name, $table, [], $multiple);
371 1
        foreach ($fields as $k => $v) {
372 1
            $k = explode('.', $k, 2);
373 1
            $k = count($k) == 2 ? $k[1] : $k[0];
374 1
            $this->joins[$name]->keymap[$this->getColumn($name . '.' . $k)['name']] = $this->getColumn($v)['name'];
375
        }
376 1
        return $this;
377
    }
378
    /**
379
     * Apply an advanced filter (can be called multiple times)
380
     * @param  string $sql    SQL statement to be used in the where clause
381
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
382
     * @return $this
383
     */
384 3
    public function where(string $sql, array $params = []) : TableQuery
385
    {
386 3
        $this->qiterator = null;
387 3
        $this->where[] = [ $sql, $params ];
388 3
        return $this;
389
    }
390
    /**
391
     * Apply an advanced HAVING filter (can be called multiple times)
392
     * @param  string $sql    SQL statement to be used in the HAING clause
393
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
394
     * @return $this
395
     */
396 1
    public function having(string $sql, array $params = []) : TableQuery
397
    {
398 1
        $this->qiterator = null;
399 1
        $this->having[] = [ $sql, $params ];
400 1
        return $this;
401
    }
402
    /**
403
     * Apply advanced sorting
404
     * @param  string $sql    SQL statement to use in the ORDER clause
405
     * @param  array  $params optional params for the statement (defaults to an empty array)
406
     * @return $this
407
     */
408 1
    public function order(string $sql, array $params = []) : TableQuery
409
    {
410 1
        $this->qiterator = null;
411 1
        $this->order = [ $sql, $params ];
412 1
        return $this;
413
    }
414
    /**
415
     * Apply an advanced limit
416
     * @param  int         $limit  number of rows to return
417
     * @param  int         $offset number of rows to skip from the beginning (defaults to 0)
418
     * @return $this
419
     */
420 1
    public function limit(int $limit, int $offset = 0, bool $limitOnMainTable = false) : TableQuery
421
    {
422 1
        $this->qiterator = null;
423 1
        $this->li_of = [ $limit, $offset, $limitOnMainTable ? 1 : 0 ];
424 1
        return $this;
425
    }
426
    /**
427
     * Get the number of records
428
     * @return int the total number of records (does not respect pagination)
429
     */
430 3
    public function count() : int
431
    {
432 3
        $aliases = [];
433 3
        $getAlias = function ($name) use (&$aliases) {
434
            // to bypass use: return $name;
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
435 1
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
436 3
        };
437 3
        $table = $this->definition->getName();
438 3
        $sql = 'SELECT COUNT(DISTINCT '.$table.'.'.implode(', '.$table.'.', $this->pkey).') FROM '.$table.' ';
439 3
        $par = [];
440
        
441 3
        $relations = $this->withr;
442 3
        foreach ($relations as $k => $v) {
443
            $getAlias($k);
444
        }
445 3
        $f = $this->fields;
0 ignored issues
show
Unused Code introduced by
$f is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
446 3
        $w = $this->where;
447 3
        $h = $this->having;
448 3
        $o = $this->order;
449 3
        $g = $this->group;
450
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
451 3
        foreach ($this->definition->getRelations() as $k => $v) {
452 3
            foreach ($w as $kk => $vv) {
453 1
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
454 1
                    $relations[$k] = [ $v, $table ];
455 1
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
456
                }
457
            }
458 3
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
459
                $relations[$k] = [ $v, $table ];
460
            }
461 3
            foreach ($h as $kk => $vv) {
462
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
463
                    $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
464
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
465
                }
466
            }
467 3
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
468
                $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
469
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
470
            }
471 3
            foreach ($j as $kk => $v) {
472
                foreach ($v->keymap as $kkk => $vv) {
473
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
474
                        $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
475 3
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
476
                    }
477
                }
478
            }
479
        }
480
481 3
        foreach ($j as $k => $v) {
482
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
483
            $tmp = [];
484
            foreach ($v->keymap as $kk => $vv) {
485
                $tmp[] = $kk.' = '.$vv;
486
            }
487
            $sql .= implode(' AND ', $tmp) . ' ';
488
        }
489 3
        foreach ($relations as $k => $v) {
490 1
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
491 1
            $v = $v[0];
492 1
            if ($v->pivot) {
493 1
                $alias = $getAlias($k.'_pivot');
494 1
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
495 1
                $tmp = [];
496 1
                foreach ($v->keymap as $kk => $vv) {
497 1
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
498
                }
499 1
                $sql .= implode(' AND ', $tmp) . ' ';
500 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($k).' ON ';
501 1
                $tmp = [];
502 1
                foreach ($v->pivot_keymap as $kk => $vv) {
503 1
                    $tmp[] = $getAlias($k).'.'.$vv.' = '.$alias.'.'.$kk.' ';
504
                }
505 1
                $sql .= implode(' AND ', $tmp) . ' ';
506
            } else {
507 1
                $alias = $getAlias($k);
508 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
509 1
                $tmp = [];
510 1
                foreach ($v->keymap as $kk => $vv) {
511 1
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
512
                }
513 1
                if ($v->sql) {
514
                    $tmp[] = $v->sql . ' ';
515
                    $par = array_merge($par, $v->par ?? []);
516
                }
517 1
                $sql .= implode(' AND ', $tmp) . ' ';
518
            }
519
        }
520 3
        if (count($w)) {
521 1
            $sql .= 'WHERE ';
522 1
            $tmp = [];
523 1
            foreach ($w as $v) {
524 1
                $tmp[] = '(' . $v[0] . ')';
525 1
                $par = array_merge($par, $v[1]);
526
            }
527 1
            $sql .= implode(' AND ', $tmp).' ';
528
        }
529 3
        if (count($g)) {
530
            $sql .= 'GROUP BY ' . $g[0] . ' ';
531
            $par = array_merge($par, $g[1]);
532
        }
533 3
        if (count($h)) {
534
            $sql .= 'HAVING ';
535
            $tmp = [];
536
            foreach ($h as $v) {
537
                $tmp[] = '(' . $v[0] . ')';
538
                $par = array_merge($par, $v[1]);
539
            }
540
            $sql .= implode(' AND ', $tmp).' ';
541
        }
542 3
        return $this->db->one($sql, $par);
543
    }
544
    /**
545
     * Specify which columns to fetch (be default all table columns are fetched)
546
     * @param  array $fields optional array of columns to select (related columns can be used too)
547
     * @return $this
548
     */
549 13
    public function columns(array $fields) : TableQuery
550
    {
551 13
        foreach ($fields as $k => $v) {
552 13
            if (strpos($v, '*') !== false) {
553
                $temp = explode('.', $v);
554
                if (count($temp) === 1) {
555
                    $table = $this->definition->getName();
556
                    $cols = $this->definition->getColumns();
557
                } else if (count($temp) === 2) {
558
                    $table = $temp[0];
559
                    if ($this->definition->hasRelation($table)) {
560
                        $cols = $this->definition->getRelation($table)->table->getColumns();
561
                    } else if (isset($this->joins[$table])) {
562
                        $cols = $this->joins[$table]->table->getColumns();
563
                    } else {
564
                        throw new DBException('Invalid foreign table name');
565
                    }
566
                } else {
567
                    array_pop($temp);
568
                    $this->with(implode('.', $temp));
569
                    $table = array_reduce(
570
                        $temp,
571
                        function ($carry, $item) use (&$table) {
572
                            return $table->getRelation($item)->table;
573
                        }
574
                    );
575
                    $cols = $table->getColumns();
576
                    $table = implode(static::SEP, $temp);
577
                }
578
                unset($fields[$k]);
579
                foreach ($cols as $col) {
580 13
                    $fields[] = $table . '.' . $col;
581
                }
582
            }
583
        }
584 13
        $primary = $this->definition->getPrimaryKey();
585 13
        foreach ($fields as $k => $v) {
586
            try {
587 13
                $fields[$k] = $this->getColumn($v)['name'];
588 1
            } catch (DBException $e) {
589 13
                $fields[$k] = $v;
590
            }
591
        }
592 13
        foreach ($primary as $field) {
593 13
            $field = $this->getColumn($field)['name'];
594 13
            if (!in_array($field, $fields)) {
595 13
                $fields[] = $field;
596
            }
597
        }
598 13
        $this->fields = $fields;
599 13
        return $this;
600
    }
601
    /**
602
     * Perform the actual fetch
603
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
604
     * @return TableQueryIterator               the query result as an iterator
605
     */
606 11
    public function iterator(array $fields = null) : TableQueryIterator
607
    {
608 11
        if ($this->qiterator) {
609 5
            return $this->qiterator;
610
        }
611 11
        $aliases = [];
612 11
        $getAlias = function ($name) use (&$aliases) {
613
            // to bypass use: return $name;
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
614 4
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
615 11
        };
616 11
        $table = $this->definition->getName();
617 11
        if ($fields !== null) {
618 1
            $this->columns($fields);
619
        }
620 11
        $relations = $this->withr;
621 11
        foreach ($relations as $k => $v) {
622 4
            $getAlias($k);
623
        }
624
625 11
        $f = $this->fields;
626 11
        $w = $this->where;
627 11
        $h = $this->having;
628 11
        $o = $this->order;
629 11
        $g = $this->group;
630
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
631
632 11
        $porder = [];
633 11
        foreach ($this->definition->getPrimaryKey() as $field) {
634 11
            $porder[] = $this->getColumn($field)['name'];
635
        }
636
637 11
        if (count($porder) && $this->li_of[2] === 1) {
638
            $ids = $this->ids();
639
            if (count($ids)) {
640
                if (count($porder) > 1) {
641
                    $pkw = [];
642
                    foreach ($porder as $name) {
643
                        $pkw[] = $name . ' = ?';
644
                    }
645
                    $pkw = '(' . implode(' AND ', $pkw) . ')';
646
                    $pkp = [];
647
                    foreach ($ids as $id) {
648
                        foreach ($id as $p) {
649
                            $pkp[] = $p;
650
                        }
651
                    }
652
                    $w[] = [
653
                        implode(' OR ', array_fill(0, count($ids), $pkw)),
654
                        $pkp
655
                    ];
656
                } else {
657
                    $w[] = [ $porder[0] . ' IN ('.implode(',', array_fill(0, count($ids), '?')).')', $ids ];
658
                }
659
            } else {
660
                $w[] = [ '1=0', [] ];
661
            }
662
        }
663
664 11
        foreach ($this->definition->getRelations() as $k => $relation) {
665 11
            foreach ($f as $kk => $field) {
666 11
                if (strpos($field, $k . '.') === 0) {
667
                    $relations[$k] = [ $relation, $table ];
668 11
                    $f[$kk] = str_replace($k . '.', $getAlias($k) . '.', $field);
669
                }
670
            }
671 11
            foreach ($w as $kk => $v) {
672
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $v[0])) {
673
                    $relations[$k] = [ $relation, $table ];
674
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $v[0]);
675
                }
676
            }
677 11
            foreach ($h as $kk => $v) {
678 1
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $v[0])) {
679
                    $relations[$k] = [ $relation, $table ];
680 1
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $v[0]);
681
                }
682
            }
683 11
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
684
                $relations[$k] = [ $relation, $table ];
685
                $o[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $o[0]);
686
            }
687 11
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
688
                $relations[$k] = [ $relation, $table ];
689
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
690
            }
691 11
            foreach ($j as $kk => $v) {
692 1
                foreach ($v->keymap as $kkk => $vv) {
693 1
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
694
                        $relations[$k] = [ $relation, $table ];
695 11
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
696
                    }
697
                }
698
            }
699
        }
700 11
        $select = [];
701 11
        foreach ($f as $k => $field) {
702 11
            $select[] = $field . (!is_numeric($k) ? ' ' . $k : '');
703
        }
704 11
        foreach ($this->withr as $name => $relation) {
705 4
            foreach ($relation[0]->table->getColumns() as $column) {
706 4
                $select[] = $getAlias($name) . '.' . $column . ' ' . $getAlias($name . static::SEP . $column);
707
            }
708
        }
709 11
        $sql = 'SELECT '.implode(', ', $select).' FROM '.$table.' ';
710 11
        $par = [];
711 11
        foreach ($j as $k => $v) {
712 1
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
713 1
            $tmp = [];
714 1
            foreach ($v->keymap as $kk => $vv) {
715 1
                $tmp[] = $kk.' = '.$vv;
716
            }
717 1
            $sql .= implode(' AND ', $tmp) . ' ';
718
        }
719 11
        foreach ($relations as $relation => $v) {
720 4
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
721 4
            $v = $v[0];
722 4
            if ($v->pivot) {
723 3
                $alias = $getAlias($relation.'_pivot');
724 3
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
725 3
                $tmp = [];
726 3
                foreach ($v->keymap as $kk => $vv) {
727 3
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
728
                }
729 3
                $sql .= implode(' AND ', $tmp) . ' ';
730 3
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($relation).' ON ';
731 3
                $tmp = [];
732 3
                foreach ($v->pivot_keymap as $kk => $vv) {
733 3
                    $tmp[] = $getAlias($relation).'.'.$vv.' = '.$alias.'.'.$kk.' ';
734
                }
735 3
                $sql .= implode(' AND ', $tmp) . ' ';
736
            } else {
737 4
                $alias = $getAlias($relation);
738
739 4
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
740 4
                $tmp = [];
741 4
                foreach ($v->keymap as $kk => $vv) {
742 4
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
743
                }
744 4
                if ($v->sql) {
745
                    $tmp[] = $v->sql . ' ';
746
                    $par = array_merge($par, $v->par ?? []);
747
                }
748 4
                $sql .= implode(' AND ', $tmp) . ' ';
749
            }
750
        }
751 11
        if (count($w)) {
752
            $sql .= 'WHERE ';
753
            $tmp = [];
754
            foreach ($w as $v) {
755
                $tmp[] = '(' . $v[0] . ')';
756
                $par = array_merge($par, $v[1]);
757
            }
758
            $sql .= implode(' AND ', $tmp).' ';
759
        }
760 11
        if (count($g)) {
761 1
            $sql .= 'GROUP BY ' . $g[0] . ' ';
762 1
            $par = array_merge($par, $g[1]);
763
        }
764 11
        if (count($h)) {
765 1
            $sql .= 'HAVING ';
766 1
            $tmp = [];
767 1
            foreach ($h as $v) {
768 1
                $tmp[] = '(' . $v[0] . ')';
769 1
                $par = array_merge($par, $v[1]);
770
            }
771 1
            $sql .= implode(' AND ', $tmp).' ';
772
        }
773 11
        if (count($o)) {
774 1
            $sql .= 'ORDER BY ' . $o[0] . ' ';
775 1
            $par = array_merge($par, $o[1]);
776
        }
777 11
        if (count($porder)) {
778 11
            $sql .= (count($o) ? ', ' : 'ORDER BY ') . implode(', ', $porder) . ' ';
779
        }
780 11
        if (($this->li_of[2] === 0 || !count($porder)) && $this->li_of[0]) {
781 1
            if ($this->db->driverName() === 'oracle') {
782
                if ((int)$this->db->driverOption('version', 0) >= 12) {
783
                    $sql .= 'OFFSET ' . $this->li_of[1] . ' ROWS FETCH NEXT ' . $this->li_of[0] . ' ROWS ONLY';
784
                } else {
785
                    $f = array_map(function ($v) {
786
                        $v = explode(' ', trim($v), 2);
787
                        if (count($v) === 2) { return $v[1]; }
788
                        $v = explode('.', $v[0], 2);
789
                        return count($v) === 2 ? $v[1] : $v[0];
790
                    }, $select);
791
                    $sql = "SELECT " . implode(', ', $f) . " 
792
                            FROM (
793
                                SELECT tbl__.*, rownum rnum__ FROM (
794
                                    " . $sql . "
795
                                ) tbl__ 
796
                                WHERE rownum <= " . ($this->li_of[0] + $this->li_of[1]) . "
797
                            ) WHERE rnum__ > " . $this->li_of[1];
798
                }
799
            } else {
800 1
                $sql .= 'LIMIT ' . $this->li_of[0] . ' OFFSET ' . $this->li_of[1];
801
            }
802
        }
803 11
        return $this->qiterator = new TableQueryIterator(
804 11
            $this->db->get($sql, $par), 
805 11
            $this->pkey,
806 11
            $this->withr,
807 11
            $aliases
808
        );
809
    }
810
    /**
811
     * Perform the actual fetch
812
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
813
     * @return array               the query result as an array
814
     */
815 1
    public function select(array $fields = null) : array
816
    {
817 1
        return iterator_to_array($this->iterator($fields));
818
    }
819
    /**
820
     * Insert a new row in the table
821
     * @param  array   $data   key value pairs, where each key is the column name and the value is the value to insert
822
     * @return array           the inserted ID where keys are column names and values are column values
823
     */
824 1
    public function insert(array $data) : array
825
    {
826 1
        $table = $this->definition->getName();
827 1
        $columns = $this->definition->getFullColumns();
828 1
        $insert = [];
829 1
        foreach ($data as $column => $value) {
830 1
            if (isset($columns[$column])) {
831 1
                $insert[$column] = $this->normalizeValue($columns[$column], $value);
832
            }
833
        }
834 1
        if (!count($insert)) {
835
            throw new DBException('No valid columns to insert');
836
        }
837 1
        $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_keys($insert)).') VALUES (??)';
838 1
        $par = [$insert];
839 1
        $primary = $this->definition->getPrimaryKey();
840 1
        if (!count($primary)) {
841
            $this->db->query($sql, $par);
842
            return [];
843
        }
844 1
        if ($this->db->driverName() === 'oracle') {
845
            $ret = [];
846
            foreach ($primary as $k) {
847
                $ret[$k] = str_repeat(' ', 255);
848
                $par[] = &$ret[$k];
849
            }
850
            $sql .= ' RETURNING ' . implode(',', $primary) . ' INTO ' . implode(',', array_fill(0, count($primary), '?'));
851
            $this->db->query($sql, $par);
852
            return $ret;
853
        } else {
854 1
            $ret = [];
855 1
            $ins = $this->db->query($sql, $par)->insertID();
856 1
            foreach ($primary as $k) {
857 1
                $ret[$k] = $data[$k] ?? $ins;
858
            }
859 1
            return $ret;
860
        }
861
    }
862
    /**
863
     * Update the filtered rows with new data
864
     * @param  array  $data key value pairs, where each key is the column name and the value is the value to insert
865
     * @return int          the number of affected rows
866
     */
867 1
    public function update(array $data) : int
868
    {
869 1
        $table = $this->definition->getName();
870 1
        $columns = $this->definition->getFullColumns();
871 1
        $update = [];
872 1
        foreach ($data as $column => $value) {
873 1
            if (isset($columns[$column])) {
874 1
                $update[$column] = $this->normalizeValue($columns[$column], $value);
875
            }
876
        }
877 1
        if (!count($update)) {
878
            throw new DBException('No valid columns to update');
879
        }
880 1
        $sql = 'UPDATE '.$table.' SET ';
881 1
        $par = [];
882
        $sql .= implode(', ', array_map(function ($v) { return $v . ' = ?'; }, array_keys($update))) . ' ';
883 1
        $par = array_merge($par, array_values($update));
884 1
        if (count($this->where)) {
885 1
            $sql .= 'WHERE ';
886 1
            $tmp = [];
887 1
            foreach ($this->where as $v) {
888 1
                $tmp[] = $v[0];
889 1
                $par = array_merge($par, $v[1]);
890
            }
891 1
            $sql .= implode(' AND ', $tmp) . ' ';
892
        }
893 1
        if (count($this->order)) {
894
            $sql .= $this->order[0];
895
            $par = array_merge($par, $this->order[1]);
896
        }
897 1
        return $this->db->query($sql, $par)->affected();
898
    }
899
    /**
900
     * Delete the filtered rows from the DB
901
     * @return int the number of deleted rows
902
     */
903 1
    public function delete() : int
904
    {
905 1
        $table = $this->definition->getName();
906 1
        $sql = 'DELETE FROM '.$table.' ';
907 1
        $par = [];
908 1
        if (count($this->where)) {
909 1
            $sql .= 'WHERE ';
910 1
            $tmp = [];
911 1
            foreach ($this->where as $v) {
912 1
                $tmp[] = $v[0];
913 1
                $par = array_merge($par, $v[1]);
914
            }
915 1
            $sql .= implode(' AND ', $tmp) . ' ';
916
        }
917 1
        if (count($this->order)) {
918
            $sql .= $this->order[0];
919
            $par = array_merge($par, $this->order[1]);
920
        }
921 1
        return $this->db->query($sql, $par)->affected();
922
    }
923
    /**
924
     * Solve the n+1 queries problem by prefetching a relation by name
925
     * @param  string $relation the relation name to fetch along with the data
926
     * @return $this
927
     */
928 4
    public function with(string $relation) : TableQuery
929
    {
930 4
        $this->qiterator = null;
931 4
        $parts = explode('.', $relation);
932 4
        $table = $this->definition;
933 4
        array_reduce(
934 4
            $parts,
935 4
            function ($carry, $item) use (&$table) {
936 4
                $relation = $table->getRelation($item);
937 4
                if (!$relation) {
938
                    throw new DBException('Invalid relation name');
939
                }
940 4
                $name = $carry ? $carry . static::SEP . $item : $item;
941 4
                $this->withr[$name] = [ $relation, $carry ?? $table->getName() ];
942 4
                $table = $relation->table;
943 4
                return $name;
944 4
            }
945
        );
946 4
        return $this;
947
    }
948
949 1
    public function getIterator()
950
    {
951 1
        return $this->iterator();
952
    }
953
954 9
    public function offsetGet($offset)
955
    {
956 9
        return $this->iterator()->offsetGet($offset);
957
    }
958
    public function offsetExists($offset)
959
    {
960
        return $this->iterator()->offsetExists($offset);
961
    }
962
    public function offsetUnset($offset)
963
    {
964
        return $this->iterator()->offsetUnset($offset);
965
    }
966
    public function offsetSet($offset, $value)
967
    {
968
        return $this->iterator()->offsetSet($offset, $value);
969
    }
970
971
    public function collection(array $fields = null) : Collection
972
    {
973
        return new Collection($this->iterator($fields));
974
    }
975
976
    public function ids()
977
    {
978
        $aliases = [];
979
        $getAlias = function ($name) use (&$aliases) {
980
            // to bypass use: return $name;
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
981
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
982
        };
983
        $table = $this->definition->getName();
984
        $sql = 'SELECT DISTINCT '.$table.'.'.implode(', '.$table.'.', $this->pkey).' FROM '.$table.' ';
985
        $par = [];
986
        
987
        $relations = $this->withr;
988
        foreach ($relations as $k => $v) {
989
            $getAlias($k);
990
        }
991
        $f = $this->fields;
0 ignored issues
show
Unused Code introduced by
$f is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
992
        $w = $this->where;
993
        $h = $this->having;
994
        $o = $this->order;
995
        $g = $this->group;
996
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
997
        foreach ($this->definition->getRelations() as $k => $v) {
998
            foreach ($w as $kk => $vv) {
999
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
1000
                    $relations[$k] = [ $v, $table ];
1001
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
1002
                }
1003
            }
1004
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
1005
                $relations[$k] = [ $v, $table ];
1006
            }
1007
            foreach ($h as $kk => $vv) {
1008
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
1009
                    $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1010
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
1011
                }
1012
            }
1013
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
1014
                $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1015
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
1016
            }
1017
            foreach ($j as $kk => $v) {
1018
                foreach ($v->keymap as $kkk => $vv) {
1019
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
1020
                        $relations[$k] = [ $relation, $table ];
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relations?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1021
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
1022
                    }
1023
                }
1024
            }
1025
        }
1026
1027
        foreach ($j as $k => $v) {
1028
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
1029
            $tmp = [];
1030
            foreach ($v->keymap as $kk => $vv) {
1031
                $tmp[] = $kk.' = '.$vv;
1032
            }
1033
            $sql .= implode(' AND ', $tmp) . ' ';
1034
        }
1035
        foreach ($relations as $k => $v) {
1036
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
1037
            $v = $v[0];
1038
            if ($v->pivot) {
1039
                $alias = $getAlias($k.'_pivot');
1040
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
1041
                $tmp = [];
1042
                foreach ($v->keymap as $kk => $vv) {
1043
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
1044
                }
1045
                $sql .= implode(' AND ', $tmp) . ' ';
1046
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($k).' ON ';
1047
                $tmp = [];
1048
                foreach ($v->pivot_keymap as $kk => $vv) {
1049
                    $tmp[] = $getAlias($k).'.'.$vv.' = '.$alias.'.'.$kk.' ';
1050
                }
1051
                $sql .= implode(' AND ', $tmp) . ' ';
1052
            } else {
1053
                $alias = $getAlias($k);
1054
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
1055
                $tmp = [];
1056
                foreach ($v->keymap as $kk => $vv) {
1057
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
1058
                }
1059
                if ($v->sql) {
1060
                    $tmp[] = $v->sql . ' ';
1061
                    $par = array_merge($par, $v->par ?? []);
1062
                }
1063
                $sql .= implode(' AND ', $tmp) . ' ';
1064
            }
1065
        }
1066
        if (count($w)) {
1067
            $sql .= 'WHERE ';
1068
            $tmp = [];
1069
            foreach ($w as $v) {
1070
                $tmp[] = '(' . $v[0] . ')';
1071
                $par = array_merge($par, $v[1]);
1072
            }
1073
            $sql .= implode(' AND ', $tmp).' ';
1074
        }
1075
        if (count($g)) {
1076
            $sql .= 'GROUP BY ' . $g[0] . ' ';
1077
            $par = array_merge($par, $g[1]);
1078
        }
1079
        if (count($h)) {
1080
            $sql .= 'HAVING ';
1081
            $tmp = [];
1082
            foreach ($h as $v) {
1083
                $tmp[] = '(' . $v[0] . ')';
1084
                $par = array_merge($par, $v[1]);
1085
            }
1086
            $sql .= implode(' AND ', $tmp).' ';
1087
        }
1088
        if (count($o)) {
1089
            $sql .= 'ORDER BY ' . $o[0] . ' ';
1090
            $par = array_merge($par, $o[1]);
1091
        }
1092
        $porder = [];
1093
        foreach ($this->definition->getPrimaryKey() as $field) {
1094
            $porder[] = $this->getColumn($field)['name'];
1095
        }
1096
        if (count($porder)) {
1097
            $sql .= (count($o) ? ', ' : 'ORDER BY ') . implode(', ', $porder) . ' ';
1098
        }
1099
1100
        if ($this->li_of[0]) {
1101
            if ($this->db->driverName() === 'oracle') {
1102
                if ((int)$this->db->driverOption('version', 0) >= 12) {
1103
                    $sql .= 'OFFSET ' . $this->li_of[1] . ' ROWS FETCH NEXT ' . $this->li_of[0] . ' ROWS ONLY';
1104
                } else {
1105
                    $f = array_map(function ($v) {
1106
                        $v = explode(' ', trim($v), 2);
1107
                        if (count($v) === 2) { return $v[1]; }
1108
                        $v = explode('.', $v[0], 2);
1109
                        return count($v) === 2 ? $v[1] : $v[0];
1110
                    }, $select);
0 ignored issues
show
Bug introduced by
The variable $select does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1111
                    $sql = "SELECT " . implode(', ', $f) . " 
1112
                            FROM (
1113
                                SELECT tbl__.*, rownum rnum__ FROM (
1114
                                    " . $sql . "
1115
                                ) tbl__ 
1116
                                WHERE rownum <= " . ($this->li_of[0] + $this->li_of[1]) . "
1117
                            ) WHERE rnum__ > " . $this->li_of[1];
1118
                }
1119
            } else {
1120
                $sql .= 'LIMIT ' . $this->li_of[0] . ' OFFSET ' . $this->li_of[1];
1121
            }
1122
        }
1123
        return $this->db->all($sql, $par);
1124
    }
1125
}
1126