Completed
Push — master ( f3e554...ebd28e )
by Ivan
01:53
created

TableQuery::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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)preg_replace('([^+\-0-9]+)', '', $value);
188 3
            case 'float':
189
                return (int)preg_replace('([^+\-0-9.]+)', '', str_replace(',', '.', $value));
190
            default:
191 3
                return $value;
192
        }
193
    }
194
195
    /**
196
     * Filter the results by a column and a value
197
     * @param  string $column  the column name to filter by (related columns can be used - for example: author.name)
198
     * @param  mixed  $value   a required value, array of values or range of values (range example: ['beg'=>1,'end'=>3])
199
     * @param  bool   $negate  optional boolean indicating that the filter should be negated
200
     * @return $this
201
     */
202 2
    public function filter(string $column, $value, bool $negate = false) : TableQuery
203
    {
204 2
        list($name, $column) = array_values($this->getColumn($column));
205 2
        if (is_null($value)) {
206
            return $negate ?
207
                $this->where($name . ' IS NOT NULL') :
208
                $this->where($name . ' IS NULL');
209
        }
210 2
        if (!is_array($value)) {
211 2
            return $negate ?
212
                $this->where(
213
                    $name . ' <> ?',
214
                    [ $this->normalizeValue($column, $value) ]
215
                ) :
216 2
                $this->where(
217 2
                    $name . ' = ?',
218 2
                    [ $this->normalizeValue($column, $value) ]
219
                );
220
        }
221 1
        if (isset($value['beg']) && strlen($value['beg']) && (!isset($value['end']) || !strlen($value['end']))) {
222
            $value = [ 'gte' => $value['beg'] ];
223
        }
224 1
        if (isset($value['end']) && strlen($value['end']) && (!isset($value['beg']) || !strlen($value['beg']))) {
225
            $value = [ 'lte' => $value['end'] ];
226
        }
227 1
        if (isset($value['beg']) && isset($value['end'])) {
228
            return $negate ?
229
                $this->where(
230
                    $name.' NOT BETWEEN ? AND ?',
231
                    [
232
                        $this->normalizeValue($column, $value['beg']),
233
                        $this->normalizeValue($column, $value['end'])
234
                    ]
235
                ) :
236
                $this->where(
237
                    $name.' BETWEEN ? AND ?',
238
                    [
239
                        $this->normalizeValue($column, $value['beg']),
240
                        $this->normalizeValue($column, $value['end'])
241
                    ]
242
                );
243
        }
244 1
        if (isset($value['gt']) || isset($value['lt']) || isset($value['gte']) || isset($value['lte'])) {
245 1
            if (isset($value['gt'])) {
246 1
                $this->where(
247 1
                    $name. ' ' . ($negate ? '<=' : '>') . ' ?',
248 1
                    [ $this->normalizeValue($column, $value['gt']) ]
249
                );
250
            }
251 1
            if (isset($value['gte'])) {
252 1
                $this->where(
253 1
                    $name. ' ' . ($negate ? '<' : '>=') . ' ?',
254 1
                    [ $this->normalizeValue($column, $value['gte']) ]
255
                );
256
            }
257 1
            if (isset($value['lt'])) {
258 1
                $this->where(
259 1
                    $name. ' ' . ($negate ? '>=' : '<') . ' ?',
260 1
                    [ $this->normalizeValue($column, $value['lt']) ]
261
                );
262
            }
263 1
            if (isset($value['lte'])) {
264 1
                $this->where(
265 1
                    $name. ' ' . ($negate ? '>' : '<=') . ' ?',
266 1
                    [ $this->normalizeValue($column, $value['lte']) ]
267
                );
268
            }
269 1
            return $this;
270
        }
271
        return $negate ?
272
            $this->where(
273
                $name . ' NOT IN (??)',
274
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
275
            ) :
276
            $this->where(
277
                $name . ' IN (??)',
278
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
279
            );
280
    }
281
    /**
282
     * Sort by a column
283
     * @param  string       $column the column name to sort by (related columns can be used - for example: author.name)
284
     * @param  bool|boolean $desc   should the sorting be in descending order, defaults to `false`
285
     * @return $this
286
     */
287
    public function sort(string $column, bool $desc = false) : TableQuery
288
    {
289
        return $this->order($this->getColumn($column)['name'] . ' ' . ($desc ? 'DESC' : 'ASC'));
290
    }
291
    /**
292
     * Group by a column (or columns)
293
     * @param  string|array        $column the column name (or names) to group by
294
     * @return $this
295
     */
296 1
    public function group($column) : TableQuery
297
    {
298 1
        if (!is_array($column)) {
299 1
            $column = [ $column ];
300
        }
301 1
        foreach ($column as $k => $v) {
302 1
            $column[$k] = $this->getColumn($v)['name'];
303
        }
304 1
        return $this->groupBy(implode(', ', $column), []);
305
    }
306
    /**
307
     * Get a part of the data
308
     * @param  int|integer $page    the page number to get (1-based), defaults to 1
309
     * @param  int|integer $perPage the number of records per page - defaults to 25
310
     * @return $this
311
     */
312
    public function paginate(int $page = 1, int $perPage = 25) : TableQuery
313
    {
314
        return $this->limit($perPage, ($page - 1) * $perPage);
315
    }
316 1
    public function __call($name, $data)
317
    {
318 1
        if (strpos($name, 'filterBy') === 0) {
319
            return $this->filter(strtolower(substr($name, 8)), $data[0]);
320
        }
321 1
        if (strpos($name, 'sortBy') === 0) {
322
            return $this->sort(strtolower(substr($name, 6)), $data[0]);
323
        }
324 1
        if (strpos($name, 'groupBy') === 0) {
325 1
            return $this->group(strtolower(substr($name, 7)));
326
        }
327
    }
328
    /**
329
     * Remove all filters, sorting, etc
330
     * @return $this
331
     */
332
    public function reset() : TableQuery
333
    {
334
        $this->where = [];
335
        $this->joins = [];
336
        $this->group = [];
337
        $this->withr = [];
338
        $this->order = [];
339
        $this->having = [];
340
        $this->aliases = [];
341
        $this->li_of = [0,0,0];
342
        $this->qiterator = null;
343
        return $this;
344
    }
345
    /**
346
     * Apply advanced grouping
347
     * @param  string $sql    SQL statement to use in the GROUP BY clause
348
     * @param  array  $params optional params for the statement (defaults to an empty array)
349
     * @return $this
350
     */
351 1
    public function groupBy(string $sql, array $params = []) : TableQuery
352
    {
353 1
        $this->qiterator = null;
354 1
        $this->group = [ $sql, $params ];
355 1
        return $this;
356
    }
357
    /**
358
     * Join a table to the query (no need to do this for relations defined with foreign keys)
359
     * @param  Table|string $table     the table to join
360
     * @param  array        $fields    what to join on (joined_table_field => other_field) 
361
     * @param  string|null  $name      alias for the join, defaults to the table name 
362
     * @param  bool         $multiple  are multiple rows joined (results in a LEFT JOIN), default to true 
363
     * @return $this
364
     */
365 1
    public function join($table, array $fields, string $name = null, bool $multiple = true)
366
    {
367 1
        $table = $table instanceof Table ? $table : $this->db->definition((string)$table);
368 1
        $name = $name ?? $table->getName();
369 1
        if (isset($this->joins[$name]) || $this->definition->hasRelation($name)) {
370
            throw new DBException('Alias / table name already in use');
371
        }
372 1
        $this->joins[$name] = new TableRelation($name, $table, [], $multiple);
373 1
        foreach ($fields as $k => $v) {
374 1
            $k = explode('.', $k, 2);
375 1
            $k = count($k) == 2 ? $k[1] : $k[0];
376 1
            $this->joins[$name]->keymap[$this->getColumn($name . '.' . $k)['name']] = $this->getColumn($v)['name'];
377
        }
378 1
        return $this;
379
    }
380
    /**
381
     * Apply an advanced filter (can be called multiple times)
382
     * @param  string $sql    SQL statement to be used in the where clause
383
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
384
     * @return $this
385
     */
386 3
    public function where(string $sql, array $params = []) : TableQuery
387
    {
388 3
        $this->qiterator = null;
389 3
        $this->where[] = [ $sql, $params ];
390 3
        return $this;
391
    }
392
    /**
393
     * Apply an advanced HAVING filter (can be called multiple times)
394
     * @param  string $sql    SQL statement to be used in the HAING clause
395
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
396
     * @return $this
397
     */
398 1
    public function having(string $sql, array $params = []) : TableQuery
399
    {
400 1
        $this->qiterator = null;
401 1
        $this->having[] = [ $sql, $params ];
402 1
        return $this;
403
    }
404
    /**
405
     * Apply advanced sorting
406
     * @param  string $sql    SQL statement to use in the ORDER clause
407
     * @param  array  $params optional params for the statement (defaults to an empty array)
408
     * @return $this
409
     */
410 1
    public function order(string $sql, array $params = []) : TableQuery
411
    {
412 1
        $this->qiterator = null;
413 1
        $this->order = [ $sql, $params ];
414 1
        return $this;
415
    }
416
    /**
417
     * Apply an advanced limit
418
     * @param  int         $limit  number of rows to return
419
     * @param  int         $offset number of rows to skip from the beginning (defaults to 0)
420
     * @return $this
421
     */
422 1
    public function limit(int $limit, int $offset = 0, bool $limitOnMainTable = false) : TableQuery
423
    {
424 1
        $this->qiterator = null;
425 1
        $this->li_of = [ $limit, $offset, $limitOnMainTable ? 1 : 0 ];
426 1
        return $this;
427
    }
428
    /**
429
     * Get the number of records
430
     * @return int the total number of records (does not respect pagination)
431
     */
432 3
    public function count() : int
433
    {
434 3
        $aliases = [];
435 3
        $getAlias = function ($name) use (&$aliases) {
436
            // 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...
437 1
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
438 3
        };
439 3
        $table = $this->definition->getName();
440 3
        $sql = 'SELECT COUNT(DISTINCT '.$table.'.'.implode(', '.$table.'.', $this->pkey).') FROM '.$table.' ';
441 3
        $par = [];
442
        
443 3
        $relations = $this->withr;
444 3
        foreach ($relations as $k => $v) {
445
            $getAlias($k);
446
        }
447 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...
448 3
        $w = $this->where;
449 3
        $h = $this->having;
450 3
        $o = $this->order;
451 3
        $g = $this->group;
452
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
453 3
        foreach ($this->definition->getRelations() as $k => $v) {
454 3
            foreach ($w as $kk => $vv) {
455 1
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
456 1
                    $relations[$k] = [ $v, $table ];
457 1
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
458
                }
459
            }
460 3
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
461
                $relations[$k] = [ $v, $table ];
462
            }
463 3
            foreach ($h as $kk => $vv) {
464
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
465
                    $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...
466
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
467
                }
468
            }
469 3
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
470
                $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...
471
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
472
            }
473 3
            foreach ($j as $kk => $v) {
474
                foreach ($v->keymap as $kkk => $vv) {
475
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
476
                        $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...
477 3
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
478
                    }
479
                }
480
            }
481
        }
482
483 3
        foreach ($j as $k => $v) {
484
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
485
            $tmp = [];
486
            foreach ($v->keymap as $kk => $vv) {
487
                $tmp[] = $kk.' = '.$vv;
488
            }
489
            $sql .= implode(' AND ', $tmp) . ' ';
490
        }
491 3
        foreach ($relations as $k => $v) {
492 1
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
493 1
            $v = $v[0];
494 1
            if ($v->pivot) {
495 1
                $alias = $getAlias($k.'_pivot');
496 1
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
497 1
                $tmp = [];
498 1
                foreach ($v->keymap as $kk => $vv) {
499 1
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
500
                }
501 1
                $sql .= implode(' AND ', $tmp) . ' ';
502 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($k).' ON ';
503 1
                $tmp = [];
504 1
                foreach ($v->pivot_keymap as $kk => $vv) {
505 1
                    $tmp[] = $getAlias($k).'.'.$vv.' = '.$alias.'.'.$kk.' ';
506
                }
507 1
                $sql .= implode(' AND ', $tmp) . ' ';
508
            } else {
509 1
                $alias = $getAlias($k);
510 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
511 1
                $tmp = [];
512 1
                foreach ($v->keymap as $kk => $vv) {
513 1
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
514
                }
515 1
                if ($v->sql) {
516
                    $tmp[] = $v->sql . ' ';
517
                    $par = array_merge($par, $v->par ?? []);
518
                }
519 1
                $sql .= implode(' AND ', $tmp) . ' ';
520
            }
521
        }
522 3
        if (count($w)) {
523 1
            $sql .= 'WHERE ';
524 1
            $tmp = [];
525 1
            foreach ($w as $v) {
526 1
                $tmp[] = '(' . $v[0] . ')';
527 1
                $par = array_merge($par, $v[1]);
528
            }
529 1
            $sql .= implode(' AND ', $tmp).' ';
530
        }
531 3
        if (count($g)) {
532
            $sql .= 'GROUP BY ' . $g[0] . ' ';
533
            $par = array_merge($par, $g[1]);
534
        }
535 3
        if (count($h)) {
536
            $sql .= 'HAVING ';
537
            $tmp = [];
538
            foreach ($h as $v) {
539
                $tmp[] = '(' . $v[0] . ')';
540
                $par = array_merge($par, $v[1]);
541
            }
542
            $sql .= implode(' AND ', $tmp).' ';
543
        }
544 3
        return $this->db->one($sql, $par);
545
    }
546
    /**
547
     * Specify which columns to fetch (be default all table columns are fetched)
548
     * @param  array $fields optional array of columns to select (related columns can be used too)
549
     * @return $this
550
     */
551 13
    public function columns(array $fields) : TableQuery
552
    {
553 13
        foreach ($fields as $k => $v) {
554 13
            if (strpos($v, '*') !== false) {
555
                $temp = explode('.', $v);
556
                if (count($temp) === 1) {
557
                    $table = $this->definition->getName();
558
                    $cols = $this->definition->getColumns();
559
                } else if (count($temp) === 2) {
560
                    $table = $temp[0];
561
                    if ($this->definition->hasRelation($table)) {
562
                        $cols = $this->definition->getRelation($table)->table->getColumns();
563
                    } else if (isset($this->joins[$table])) {
564
                        $cols = $this->joins[$table]->table->getColumns();
565
                    } else {
566
                        throw new DBException('Invalid foreign table name');
567
                    }
568
                } else {
569
                    array_pop($temp);
570
                    $this->with(implode('.', $temp));
571
                    $table = array_reduce(
572
                        $temp,
573
                        function ($carry, $item) use (&$table) {
574
                            return $table->getRelation($item)->table;
575
                        }
576
                    );
577
                    $cols = $table->getColumns();
578
                    $table = implode(static::SEP, $temp);
579
                }
580
                unset($fields[$k]);
581
                foreach ($cols as $col) {
582 13
                    $fields[] = $table . '.' . $col;
583
                }
584
            }
585
        }
586 13
        $primary = $this->definition->getPrimaryKey();
587 13
        foreach ($fields as $k => $v) {
588
            try {
589 13
                $fields[$k] = $this->getColumn($v)['name'];
590 1
            } catch (DBException $e) {
591 13
                $fields[$k] = $v;
592
            }
593
        }
594 13
        foreach ($primary as $field) {
595 13
            $field = $this->getColumn($field)['name'];
596 13
            if (!in_array($field, $fields)) {
597 13
                $fields[] = $field;
598
            }
599
        }
600 13
        $this->fields = $fields;
601 13
        return $this;
602
    }
603
    /**
604
     * Perform the actual fetch
605
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
606
     * @return TableQueryIterator               the query result as an iterator
607
     */
608 11
    public function iterator(array $fields = null) : TableQueryIterator
609
    {
610 11
        if ($this->qiterator) {
611 5
            return $this->qiterator;
612
        }
613 11
        $aliases = [];
614 11
        $getAlias = function ($name) use (&$aliases) {
615
            // 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...
616 4
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
617 11
        };
618 11
        $table = $this->definition->getName();
619 11
        if ($fields !== null) {
620 1
            $this->columns($fields);
621
        }
622 11
        $relations = $this->withr;
623 11
        foreach ($relations as $k => $v) {
624 4
            $getAlias($k);
625
        }
626
627 11
        $f = $this->fields;
628 11
        $w = $this->where;
629 11
        $h = $this->having;
630 11
        $o = $this->order;
631 11
        $g = $this->group;
632
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
633
634 11
        $porder = [];
635 11
        foreach ($this->definition->getPrimaryKey() as $field) {
636 11
            $porder[] = $this->getColumn($field)['name'];
637
        }
638
639 11
        if (count($porder) && $this->li_of[2] === 1) {
640
            $ids = $this->ids();
641
            if (count($ids)) {
642
                if (count($porder) > 1) {
643
                    $pkw = [];
644
                    foreach ($porder as $name) {
645
                        $pkw[] = $name . ' = ?';
646
                    }
647
                    $pkw = '(' . implode(' AND ', $pkw) . ')';
648
                    $pkp = [];
649
                    foreach ($ids as $id) {
650
                        foreach ($id as $p) {
651
                            $pkp[] = $p;
652
                        }
653
                    }
654
                    $w[] = [
655
                        implode(' OR ', array_fill(0, count($ids), $pkw)),
656
                        $pkp
657
                    ];
658
                } else {
659
                    $w[] = [ $porder[0] . ' IN ('.implode(',', array_fill(0, count($ids), '?')).')', $ids ];
660
                }
661
            } else {
662
                $w[] = [ '1=0', [] ];
663
            }
664
        }
665
666 11
        foreach ($this->definition->getRelations() as $k => $relation) {
667 11
            foreach ($f as $kk => $field) {
668 11
                if (strpos($field, $k . '.') === 0) {
669
                    $relations[$k] = [ $relation, $table ];
670 11
                    $f[$kk] = str_replace($k . '.', $getAlias($k) . '.', $field);
671
                }
672
            }
673 11
            foreach ($w as $kk => $v) {
674
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $v[0])) {
675
                    $relations[$k] = [ $relation, $table ];
676
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $v[0]);
677
                }
678
            }
679 11
            foreach ($h as $kk => $v) {
680 1
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $v[0])) {
681
                    $relations[$k] = [ $relation, $table ];
682 1
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $v[0]);
683
                }
684
            }
685 11
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
686
                $relations[$k] = [ $relation, $table ];
687
                $o[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $o[0]);
688
            }
689 11
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
690
                $relations[$k] = [ $relation, $table ];
691
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
692
            }
693 11
            foreach ($j as $kk => $v) {
694 1
                foreach ($v->keymap as $kkk => $vv) {
695 1
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
696
                        $relations[$k] = [ $relation, $table ];
697 11
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
698
                    }
699
                }
700
            }
701
        }
702 11
        $select = [];
703 11
        foreach ($f as $k => $field) {
704 11
            $select[] = $field . (!is_numeric($k) ? ' ' . $k : '');
705
        }
706 11
        foreach ($this->withr as $name => $relation) {
707 4
            foreach ($relation[0]->table->getColumns() as $column) {
708 4
                $select[] = $getAlias($name) . '.' . $column . ' ' . $getAlias($name . static::SEP . $column);
709
            }
710
        }
711 11
        $sql = 'SELECT '.implode(', ', $select).' FROM '.$table.' ';
712 11
        $par = [];
713 11
        foreach ($j as $k => $v) {
714 1
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
715 1
            $tmp = [];
716 1
            foreach ($v->keymap as $kk => $vv) {
717 1
                $tmp[] = $kk.' = '.$vv;
718
            }
719 1
            $sql .= implode(' AND ', $tmp) . ' ';
720
        }
721 11
        foreach ($relations as $relation => $v) {
722 4
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
723 4
            $v = $v[0];
724 4
            if ($v->pivot) {
725 3
                $alias = $getAlias($relation.'_pivot');
726 3
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
727 3
                $tmp = [];
728 3
                foreach ($v->keymap as $kk => $vv) {
729 3
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
730
                }
731 3
                $sql .= implode(' AND ', $tmp) . ' ';
732 3
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($relation).' ON ';
733 3
                $tmp = [];
734 3
                foreach ($v->pivot_keymap as $kk => $vv) {
735 3
                    $tmp[] = $getAlias($relation).'.'.$vv.' = '.$alias.'.'.$kk.' ';
736
                }
737 3
                $sql .= implode(' AND ', $tmp) . ' ';
738
            } else {
739 4
                $alias = $getAlias($relation);
740
741 4
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
742 4
                $tmp = [];
743 4
                foreach ($v->keymap as $kk => $vv) {
744 4
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
745
                }
746 4
                if ($v->sql) {
747
                    $tmp[] = $v->sql . ' ';
748
                    $par = array_merge($par, $v->par ?? []);
749
                }
750 4
                $sql .= implode(' AND ', $tmp) . ' ';
751
            }
752
        }
753 11
        if (count($w)) {
754
            $sql .= 'WHERE ';
755
            $tmp = [];
756
            foreach ($w as $v) {
757
                $tmp[] = '(' . $v[0] . ')';
758
                $par = array_merge($par, $v[1]);
759
            }
760
            $sql .= implode(' AND ', $tmp).' ';
761
        }
762 11
        if (count($g)) {
763 1
            $sql .= 'GROUP BY ' . $g[0] . ' ';
764 1
            $par = array_merge($par, $g[1]);
765
        }
766 11
        if (count($h)) {
767 1
            $sql .= 'HAVING ';
768 1
            $tmp = [];
769 1
            foreach ($h as $v) {
770 1
                $tmp[] = '(' . $v[0] . ')';
771 1
                $par = array_merge($par, $v[1]);
772
            }
773 1
            $sql .= implode(' AND ', $tmp).' ';
774
        }
775 11
        if (count($o)) {
776 1
            $sql .= 'ORDER BY ' . $o[0] . ' ';
777 1
            $par = array_merge($par, $o[1]);
778
        }
779 11
        if (count($porder)) {
780 11
            $sql .= (count($o) ? ', ' : 'ORDER BY ') . implode(', ', $porder) . ' ';
781
        }
782 11
        if (($this->li_of[2] === 0 || !count($porder)) && $this->li_of[0]) {
783 1
            if ($this->db->driverName() === 'oracle') {
784
                if ((int)$this->db->driverOption('version', 0) >= 12) {
785
                    $sql .= 'OFFSET ' . $this->li_of[1] . ' ROWS FETCH NEXT ' . $this->li_of[0] . ' ROWS ONLY';
786
                } else {
787
                    $f = array_map(function ($v) {
788
                        $v = explode(' ', trim($v), 2);
789
                        if (count($v) === 2) { return $v[1]; }
790
                        $v = explode('.', $v[0], 2);
791
                        return count($v) === 2 ? $v[1] : $v[0];
792
                    }, $select);
793
                    $sql = "SELECT " . implode(', ', $f) . " 
794
                            FROM (
795
                                SELECT tbl__.*, rownum rnum__ FROM (
796
                                    " . $sql . "
797
                                ) tbl__ 
798
                                WHERE rownum <= " . ($this->li_of[0] + $this->li_of[1]) . "
799
                            ) WHERE rnum__ > " . $this->li_of[1];
800
                }
801
            } else {
802 1
                $sql .= 'LIMIT ' . $this->li_of[0] . ' OFFSET ' . $this->li_of[1];
803
            }
804
        }
805 11
        return $this->qiterator = new TableQueryIterator(
806 11
            $this->db->get($sql, $par), 
807 11
            $this->pkey,
808 11
            $this->withr,
809 11
            $aliases
810
        );
811
    }
812
    /**
813
     * Perform the actual fetch
814
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
815
     * @return array               the query result as an array
816
     */
817 1
    public function select(array $fields = null) : array
818
    {
819 1
        return iterator_to_array($this->iterator($fields));
820
    }
821
    /**
822
     * Insert a new row in the table
823
     * @param  array   $data   key value pairs, where each key is the column name and the value is the value to insert
824
     * @return array           the inserted ID where keys are column names and values are column values
825
     */
826 1
    public function insert(array $data) : array
827
    {
828 1
        $table = $this->definition->getName();
829 1
        $columns = $this->definition->getFullColumns();
830 1
        $insert = [];
831 1
        foreach ($data as $column => $value) {
832 1
            if (isset($columns[$column])) {
833 1
                $insert[$column] = $this->normalizeValue($columns[$column], $value);
834
            }
835
        }
836 1
        if (!count($insert)) {
837
            throw new DBException('No valid columns to insert');
838
        }
839 1
        $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_keys($insert)).') VALUES (??)';
840 1
        $par = [$insert];
841 1
        $primary = $this->definition->getPrimaryKey();
842 1
        if (!count($primary)) {
843
            $this->db->query($sql, $par);
844
            return [];
845
        }
846 1
        if ($this->db->driverName() === 'oracle') {
847
            $ret = [];
848
            foreach ($primary as $k) {
849
                $ret[$k] = str_repeat(' ', 255);
850
                $par[] = &$ret[$k];
851
            }
852
            $sql .= ' RETURNING ' . implode(',', $primary) . ' INTO ' . implode(',', array_fill(0, count($primary), '?'));
853
            $this->db->query($sql, $par);
854
            return $ret;
855
        } else {
856 1
            $ret = [];
857 1
            $ins = $this->db->query($sql, $par)->insertID();
858 1
            foreach ($primary as $k) {
859 1
                $ret[$k] = $data[$k] ?? $ins;
860
            }
861 1
            return $ret;
862
        }
863
    }
864
    /**
865
     * Update the filtered rows with new data
866
     * @param  array  $data key value pairs, where each key is the column name and the value is the value to insert
867
     * @return int          the number of affected rows
868
     */
869 1
    public function update(array $data) : int
870
    {
871 1
        $table = $this->definition->getName();
872 1
        $columns = $this->definition->getFullColumns();
873 1
        $update = [];
874 1
        foreach ($data as $column => $value) {
875 1
            if (isset($columns[$column])) {
876 1
                $update[$column] = $this->normalizeValue($columns[$column], $value);
877
            }
878
        }
879 1
        if (!count($update)) {
880
            throw new DBException('No valid columns to update');
881
        }
882 1
        $sql = 'UPDATE '.$table.' SET ';
883 1
        $par = [];
884
        $sql .= implode(', ', array_map(function ($v) { return $v . ' = ?'; }, array_keys($update))) . ' ';
885 1
        $par = array_merge($par, array_values($update));
886 1
        if (count($this->where)) {
887 1
            $sql .= 'WHERE ';
888 1
            $tmp = [];
889 1
            foreach ($this->where as $v) {
890 1
                $tmp[] = $v[0];
891 1
                $par = array_merge($par, $v[1]);
892
            }
893 1
            $sql .= implode(' AND ', $tmp) . ' ';
894
        }
895 1
        if (count($this->order)) {
896
            $sql .= $this->order[0];
897
            $par = array_merge($par, $this->order[1]);
898
        }
899 1
        return $this->db->query($sql, $par)->affected();
900
    }
901
    /**
902
     * Delete the filtered rows from the DB
903
     * @return int the number of deleted rows
904
     */
905 1
    public function delete() : int
906
    {
907 1
        $table = $this->definition->getName();
908 1
        $sql = 'DELETE FROM '.$table.' ';
909 1
        $par = [];
910 1
        if (count($this->where)) {
911 1
            $sql .= 'WHERE ';
912 1
            $tmp = [];
913 1
            foreach ($this->where as $v) {
914 1
                $tmp[] = $v[0];
915 1
                $par = array_merge($par, $v[1]);
916
            }
917 1
            $sql .= implode(' AND ', $tmp) . ' ';
918
        }
919 1
        if (count($this->order)) {
920
            $sql .= $this->order[0];
921
            $par = array_merge($par, $this->order[1]);
922
        }
923 1
        return $this->db->query($sql, $par)->affected();
924
    }
925
    /**
926
     * Solve the n+1 queries problem by prefetching a relation by name
927
     * @param  string $relation the relation name to fetch along with the data
928
     * @return $this
929
     */
930 4
    public function with(string $relation) : TableQuery
931
    {
932 4
        $this->qiterator = null;
933 4
        $parts = explode('.', $relation);
934 4
        $table = $this->definition;
935 4
        array_reduce(
936 4
            $parts,
937 4
            function ($carry, $item) use (&$table) {
938 4
                $relation = $table->getRelation($item);
939 4
                if (!$relation) {
940
                    throw new DBException('Invalid relation name');
941
                }
942 4
                $name = $carry ? $carry . static::SEP . $item : $item;
943 4
                $this->withr[$name] = [ $relation, $carry ?? $table->getName() ];
944 4
                $table = $relation->table;
945 4
                return $name;
946 4
            }
947
        );
948 4
        return $this;
949
    }
950
951 1
    public function getIterator()
952
    {
953 1
        return $this->iterator();
954
    }
955
956 9
    public function offsetGet($offset)
957
    {
958 9
        return $this->iterator()->offsetGet($offset);
959
    }
960
    public function offsetExists($offset)
961
    {
962
        return $this->iterator()->offsetExists($offset);
963
    }
964
    public function offsetUnset($offset)
965
    {
966
        return $this->iterator()->offsetUnset($offset);
967
    }
968
    public function offsetSet($offset, $value)
969
    {
970
        return $this->iterator()->offsetSet($offset, $value);
971
    }
972
973
    public function collection(array $fields = null) : Collection
974
    {
975
        return new Collection($this->iterator($fields));
976
    }
977
978
    public function ids()
979
    {
980
        $aliases = [];
981
        $getAlias = function ($name) use (&$aliases) {
982
            // 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...
983
            return $aliases[$name] = $aliases[$name] ?? 'alias' . static::SEP . count($aliases);
984
        };
985
        $table = $this->definition->getName();
986
        $sql = 'SELECT DISTINCT '.$table.'.'.implode(', '.$table.'.', $this->pkey).' FROM '.$table.' ';
987
        $par = [];
988
        
989
        $relations = $this->withr;
990
        foreach ($relations as $k => $v) {
991
            $getAlias($k);
992
        }
993
        $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...
994
        $w = $this->where;
995
        $h = $this->having;
996
        $o = $this->order;
997
        $g = $this->group;
998
        $j = array_map(function ($v) { return clone $v; }, $this->joins);
999
        foreach ($this->definition->getRelations() as $k => $v) {
1000
            foreach ($w as $kk => $vv) {
1001
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
1002
                    $relations[$k] = [ $v, $table ];
1003
                    $w[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
1004
                }
1005
            }
1006
            if (isset($o[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $o[0])) {
1007
                $relations[$k] = [ $v, $table ];
1008
                $o[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $o[0]);
1009
            }
1010
            foreach ($h as $kk => $vv) {
1011
                if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv[0])) {
1012
                    $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...
1013
                    $h[$kk][0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv[0]);
1014
                }
1015
            }
1016
            if (isset($g[0]) && preg_match('(\b'.preg_quote($k . '.'). ')i', $g[0])) {
1017
                $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...
1018
                $g[0] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $g[0]);
1019
            }
1020
            foreach ($j as $kk => $v) {
1021
                foreach ($v->keymap as $kkk => $vv) {
1022
                    if (preg_match('(\b'.preg_quote($k . '.'). ')i', $vv)) {
1023
                        $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...
1024
                        $j[$k]->keymap[$kkk] = preg_replace('(\b'.preg_quote($k . '.'). ')i', $getAlias($k) . '.', $vv);
1025
                    }
1026
                }
1027
            }
1028
        }
1029
1030
        foreach ($j as $k => $v) {
1031
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
1032
            $tmp = [];
1033
            foreach ($v->keymap as $kk => $vv) {
1034
                $tmp[] = $kk.' = '.$vv;
1035
            }
1036
            $sql .= implode(' AND ', $tmp) . ' ';
1037
        }
1038
        foreach ($relations as $k => $v) {
1039
            $table = $v[1] !== $this->definition->getName() ? $getAlias($v[1]) : $v[1];
1040
            $v = $v[0];
1041
            if ($v->pivot) {
1042
                $alias = $getAlias($k.'_pivot');
1043
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
1044
                $tmp = [];
1045
                foreach ($v->keymap as $kk => $vv) {
1046
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
1047
                }
1048
                $sql .= implode(' AND ', $tmp) . ' ';
1049
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$getAlias($k).' ON ';
1050
                $tmp = [];
1051
                foreach ($v->pivot_keymap as $kk => $vv) {
1052
                    $tmp[] = $getAlias($k).'.'.$vv.' = '.$alias.'.'.$kk.' ';
1053
                }
1054
                $sql .= implode(' AND ', $tmp) . ' ';
1055
            } else {
1056
                $alias = $getAlias($k);
1057
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
1058
                $tmp = [];
1059
                foreach ($v->keymap as $kk => $vv) {
1060
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
1061
                }
1062
                if ($v->sql) {
1063
                    $tmp[] = $v->sql . ' ';
1064
                    $par = array_merge($par, $v->par ?? []);
1065
                }
1066
                $sql .= implode(' AND ', $tmp) . ' ';
1067
            }
1068
        }
1069
        if (count($w)) {
1070
            $sql .= 'WHERE ';
1071
            $tmp = [];
1072
            foreach ($w as $v) {
1073
                $tmp[] = '(' . $v[0] . ')';
1074
                $par = array_merge($par, $v[1]);
1075
            }
1076
            $sql .= implode(' AND ', $tmp).' ';
1077
        }
1078
        if (count($g)) {
1079
            $sql .= 'GROUP BY ' . $g[0] . ' ';
1080
            $par = array_merge($par, $g[1]);
1081
        }
1082
        if (count($h)) {
1083
            $sql .= 'HAVING ';
1084
            $tmp = [];
1085
            foreach ($h as $v) {
1086
                $tmp[] = '(' . $v[0] . ')';
1087
                $par = array_merge($par, $v[1]);
1088
            }
1089
            $sql .= implode(' AND ', $tmp).' ';
1090
        }
1091
        if (count($o)) {
1092
            $sql .= 'ORDER BY ' . $o[0] . ' ';
1093
            $par = array_merge($par, $o[1]);
1094
        }
1095
        $porder = [];
1096
        foreach ($this->definition->getPrimaryKey() as $field) {
1097
            $porder[] = $this->getColumn($field)['name'];
1098
        }
1099
        if (count($porder)) {
1100
            $sql .= (count($o) ? ', ' : 'ORDER BY ') . implode(', ', $porder) . ' ';
1101
        }
1102
1103
        if ($this->li_of[0]) {
1104
            if ($this->db->driverName() === 'oracle') {
1105
                if ((int)$this->db->driverOption('version', 0) >= 12) {
1106
                    $sql .= 'OFFSET ' . $this->li_of[1] . ' ROWS FETCH NEXT ' . $this->li_of[0] . ' ROWS ONLY';
1107
                } else {
1108
                    $f = array_map(function ($v) {
1109
                        $v = explode(' ', trim($v), 2);
1110
                        if (count($v) === 2) { return $v[1]; }
1111
                        $v = explode('.', $v[0], 2);
1112
                        return count($v) === 2 ? $v[1] : $v[0];
1113
                    }, $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...
1114
                    $sql = "SELECT " . implode(', ', $f) . " 
1115
                            FROM (
1116
                                SELECT tbl__.*, rownum rnum__ FROM (
1117
                                    " . $sql . "
1118
                                ) tbl__ 
1119
                                WHERE rownum <= " . ($this->li_of[0] + $this->li_of[1]) . "
1120
                            ) WHERE rnum__ > " . $this->li_of[1];
1121
                }
1122
            } else {
1123
                $sql .= 'LIMIT ' . $this->li_of[0] . ' OFFSET ' . $this->li_of[1];
1124
            }
1125
        }
1126
        return $this->db->all($sql, $par);
1127
    }
1128
}
1129