Completed
Push — master ( 740836...6931a9 )
by Ivan
01:50
created

TableQuery::getAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
namespace vakata\database\schema;
3
4
use vakata\database\DBInterface;
5
use vakata\database\DBException;
6
use vakata\database\ResultInterface;
7
8
/**
9
 * A database query class
10
 */
11
class TableQuery implements \IteratorAggregate, \ArrayAccess, \Countable
12
{
13
    const SEP = '___';
14
    /**
15
     * @var DBInterface
16
     */
17
    protected $db;
18
    /**
19
     * @var Table
20
     */
21
    protected $definition;
22
    /**
23
     * @var TableQueryIterator|null
24
     */
25
    protected $qiterator;
26
27
    /**
28
     * @var array
29
     */
30
    protected $where = [];
31
    /**
32
     * @var array
33
     */
34
    protected $order = [];
35
    /**
36
     * @var array
37
     */
38
    protected $group = [];
39
    /**
40
     * @var array
41
     */
42
    protected $having = [];
43
    /**
44
     * @var int[]
45
     */
46
    protected $li_of = [0,0];
47
    /**
48
     * @var array
49
     */
50
    protected $fields = [];
51
    /**
52
     * @var array
53
     */
54
    protected $withr = [];
55
    /**
56
     * @var array
57
     */
58
    protected $joins = [];
59
    /**
60
     * @var array
61
     */
62
    protected $pkey = [];
63
    /**
64
     * @var array
65
     */
66
    protected $aliases = [];
67
68
    /**
69
     * Create an instance
70
     * @param  DBInterface    $db         the database connection
71
     * @param  Table|string   $table      the name or definition of the main table in the query
72
     */
73 13
    public function __construct(DBInterface $db, $table)
74
    {
75 13
        $this->db = $db;
76 13
        $this->definition = $table instanceof Table ? $table : $this->db->definition((string)$table);
77 13
        $primary = $this->definition->getPrimaryKey();
78 13
        $columns = $this->definition->getColumns();
79 13
        $this->pkey = count($primary) ? $primary : $columns;
80 13
        $this->columns($columns);
81 13
    }
82
    public function __clone()
83
    {
84
        $this->reset();
85
    }
86
    /**
87
     * Get the table definition of the queried table
88
     * @return Table        the definition
89
     */
90
    public function getDefinition() : Table
91
    {
92
        return $this->definition;
93
    }
94
95 13
    protected function getColumn($column)
96
    {
97 13
        $column = explode('.', $column);
98 13
        if (count($column) === 1) {
99 13
            $column = [ $this->definition->getName(), $column[0] ];
100 13
            $col = $this->definition->getColumn($column[1]);
101 13
            if (!$col) {
102 13
                throw new DBException('Invalid column name in own table');
103
            }
104 2
        } elseif (count($column) === 2) {
105 2
            if ($column[0] === $this->definition->getName()) {
106
                $col = $this->definition->getColumn($column[1]);
107
                if (!$col) {
108
                    throw new DBException('Invalid column name in own table');
109
                }
110
            } else {
111 2
                if ($this->definition->hasRelation($column[0])) {
112 1
                    $col = $this->definition->getRelation($column[0])->table->getColumn($column[1]);
113 1
                    if (!$col) {
114 1
                        throw new DBException('Invalid column name in related table');
115
                    }
116 1
                } else if (isset($this->joins[$column[0]])) {
117 1
                    $col = $this->joins[$column[0]]->table->getColumn($column[1]);
118 1
                    if (!$col) {
119 1
                        throw new DBException('Invalid column name in related table');
120
                    }
121
                } else {
122 2
                    throw new DBException('Invalid foreign table name: ' . implode(',', $column));
123
                }
124
            }
125
        } else {
126
            $name = array_pop($column);
127
            $this->with(implode('.', $column));
128
            $table = $this->definition;
129
            $table = array_reduce(
130
                $column,
131
                function ($carry, $item) use (&$table) {
132
                    $table = $table->getRelation($item)->table;
133
                    return $table;
134
                }
135
            );
136
            $col = $table->getColumn($name);
137
            $column = [ implode(static::SEP, $column), $name ];
138
        }
139 13
        return [ 'name' => implode('.', $column), 'data' => $col ];
140
    }
141 4
    protected function normalizeValue(TableColumn $col, $value)
142
    {
143 4
        if ($value === null && $col->isNullable()) {
144
            return null;
145
        }
146 4
        switch ($col->getBasicType()) {
147 4
            case 'date':
148
                if (is_string($value)) {
149
                    $temp = strtotime($value);
150
                    if (!$temp) {
151
                        return null;
152
                    }
153
                    return date('Y-m-d', strtotime($value));
154
                }
155
                if (is_int($value)) {
156
                    return date('Y-m-d', $value);
157
                }
158
                if ($value instanceof \DateTime) {
159
                    return $value->format('Y-m-d');
160
                }
161
                return $value;
162 4
            case 'datetime':
163
                if (is_string($value)) {
164
                    $temp = strtotime($value);
165
                    if (!$temp) {
166
                        return null;
167
                    }
168
                    return date('Y-m-d H:i:s', strtotime($value));
169
                }
170
                if (is_int($value)) {
171
                    return date('Y-m-d H:i:s', $value);
172
                }
173
                if ($value instanceof \DateTime) {
174
                    return $value->format('Y-m-d H:i:s');
175
                }
176
                return $value;
177 4
            case 'enum':
178
                if (is_int($value)) {
179
                    return $value;
180
                }
181
                if (!in_array($value, $col->getValues())) {
182
                    return 0;
183
                }
184
                return $value;
185 4
            case 'int':
186 2
                return (int)$value;
187
            default:
188 3
                return $value;
189
        }
190
    }
191
192
    /**
193
     * Filter the results by a column and a value
194
     * @param  string $column  the column name to filter by (related columns can be used - for example: author.name)
195
     * @param  mixed  $value   a required value, array of values or range of values (range example: ['beg'=>1,'end'=>3])
196
     * @param  bool   $negate  optional boolean indicating that the filter should be negated
197
     * @return $this
198
     */
199 2
    public function filter(string $column, $value, bool $negate = false) : TableQuery
200
    {
201 2
        list($name, $column) = array_values($this->getColumn($column));
202 2
        if (is_null($value)) {
203
            return $negate ?
204
                $this->where($name . ' IS NOT NULL') :
205
                $this->where($name . ' IS NULL');
206
        }
207 2
        if (!is_array($value)) {
208 2
            return $negate ?
209
                $this->where(
210
                    $name . ' <> ?',
211
                    [ $this->normalizeValue($column, $value) ]
212
                ) :
213 2
                $this->where(
214 2
                    $name . ' = ?',
215 2
                    [ $this->normalizeValue($column, $value) ]
216
                );
217
        }
218 1
        if (isset($value['beg']) && isset($value['end'])) {
219
            return $negate ?
220
                $this->where(
221
                    $name.' NOT BETWEEN ? AND ?',
222
                    [
223
                        $this->normalizeValue($column, $value['beg']),
224
                        $this->normalizeValue($column, $value['end'])
225
                    ]
226
                ) :
227
                $this->where(
228
                    $name.' BETWEEN ? AND ?',
229
                    [
230
                        $this->normalizeValue($column, $value['beg']),
231
                        $this->normalizeValue($column, $value['end'])
232
                    ]
233
                );
234
        }
235 1
        if (isset($value['gt']) || isset($value['lt']) || isset($value['gte']) || isset($value['lte'])) {
236 1
            if (isset($value['gt'])) {
237 1
                $this->where(
238 1
                    $name. ' ' . ($negate ? '<=' : '>') . ' ?',
239 1
                    [ $this->normalizeValue($column, $value['gt']) ]
240
                );
241
            }
242 1
            if (isset($value['gte'])) {
243 1
                $this->where(
244 1
                    $name. ' ' . ($negate ? '<' : '>=') . ' ?',
245 1
                    [ $this->normalizeValue($column, $value['gte']) ]
246
                );
247
            }
248 1
            if (isset($value['lt'])) {
249 1
                $this->where(
250 1
                    $name. ' ' . ($negate ? '>=' : '<') . ' ?',
251 1
                    [ $this->normalizeValue($column, $value['lt']) ]
252
                );
253
            }
254 1
            if (isset($value['lte'])) {
255 1
                $this->where(
256 1
                    $name. ' ' . ($negate ? '>' : '<=') . ' ?',
257 1
                    [ $this->normalizeValue($column, $value['lte']) ]
258
                );
259
            }
260 1
            return $this;
261
        }
262
        return $negate ?
263
            $this->where(
264
                $name . ' NOT IN (??)',
265
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
266
            ) :
267
            $this->where(
268
                $name . ' IN (??)',
269
                [ array_map(function ($v) use ($column) { return $this->normalizeValue($column, $v); }, $value) ]
270
            );
271
    }
272
    /**
273
     * Sort by a column
274
     * @param  string       $column the column name to sort by (related columns can be used - for example: author.name)
275
     * @param  bool|boolean $desc   should the sorting be in descending order, defaults to `false`
276
     * @return $this
277
     */
278
    public function sort(string $column, bool $desc = false) : TableQuery
279
    {
280
        return $this->order($this->getColumn($column)['name'] . ' ' . ($desc ? 'DESC' : 'ASC'));
281
    }
282
    /**
283
     * Group by a column (or columns)
284
     * @param  string|array        $column the column name (or names) to group by
285
     * @return $this
286
     */
287 1
    public function group($column) : TableQuery
288
    {
289 1
        if (!is_array($column)) {
290 1
            $column = [ $column ];
291
        }
292 1
        foreach ($column as $k => $v) {
293 1
            $column[$k] = $this->getColumn($v)['name'];
294
        }
295 1
        return $this->groupBy(implode(', ', $column), []);
296
    }
297
    /**
298
     * Get a part of the data
299
     * @param  int|integer $page    the page number to get (1-based), defaults to 1
300
     * @param  int|integer $perPage the number of records per page - defaults to 25
301
     * @return $this
302
     */
303
    public function paginate(int $page = 1, int $perPage = 25) : TableQuery
304
    {
305
        return $this->limit($perPage, ($page - 1) * $perPage);
306
    }
307 1
    public function __call($name, $data)
308
    {
309 1
        if (strpos($name, 'filterBy') === 0) {
310
            return $this->filter(strtolower(substr($name, 8)), $data[0]);
311
        }
312 1
        if (strpos($name, 'sortBy') === 0) {
313
            return $this->sort(strtolower(substr($name, 6)), $data[0]);
314
        }
315 1
        if (strpos($name, 'groupBy') === 0) {
316 1
            return $this->group(strtolower(substr($name, 7)));
317
        }
318
    }
319
    /**
320
     * Remove all filters, sorting, etc
321
     * @return $this
322
     */
323
    public function reset() : TableQuery
324
    {
325
        $this->where = [];
326
        $this->joins = [];
327
        $this->group = [];
328
        $this->withr = [];
329
        $this->order = [];
330
        $this->having = [];
331
        $this->li_of = [0,0];
332
        $this->qiterator = null;
333
        return $this;
334
    }
335
    /**
336
     * Apply advanced grouping
337
     * @param  string $sql    SQL statement to use in the GROUP BY clause
338
     * @param  array  $params optional params for the statement (defaults to an empty array)
339
     * @return $this
340
     */
341 1
    public function groupBy(string $sql, array $params = []) : TableQuery
342
    {
343 1
        $this->qiterator = null;
344 1
        $this->group = [ $sql, $params ];
345 1
        return $this;
346
    }
347
    /**
348
     * Join a table to the query (no need to do this for relations defined with foreign keys)
349
     * @param  Table|string $table     the table to join
350
     * @param  array        $fields    what to join on (joined_table_field => other_field) 
351
     * @param  string|null  $name      alias for the join, defaults to the table name 
352
     * @param  bool         $multiple  are multiple rows joined (results in a LEFT JOIN), default to true 
353
     * @return $this
354
     */
355 1
    public function join($table, array $fields, string $name = null, bool $multiple = true)
356
    {
357 1
        $table = $table instanceof Table ? $table : $this->db->definition((string)$table);
358 1
        $name = $name ?? $table->getName();
359 1
        if (isset($this->joins[$name]) || $this->definition->hasRelation($name)) {
360
            throw new DBException('Alias / table name already in use');
361
        }
362 1
        $this->joins[$name] = new TableRelation($name, $table, [], $multiple);
363 1
        foreach ($fields as $k => $v) {
364 1
            $k = explode('.', $k, 2);
365 1
            $k = count($k) == 2 ? $k[1] : $k[0];
366 1
            $this->joins[$name]->keymap[$this->getColumn($name . '.' . $k)['name']] = $this->getColumn($v)['name'];
367
        }
368 1
        return $this;
369
    }
370
    /**
371
     * Apply an advanced filter (can be called multiple times)
372
     * @param  string $sql    SQL statement to be used in the where clause
373
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
374
     * @return $this
375
     */
376 3
    public function where(string $sql, array $params = []) : TableQuery
377
    {
378 3
        $this->qiterator = null;
379 3
        $this->where[] = [ $sql, $params ];
380 3
        return $this;
381
    }
382
    /**
383
     * Apply an advanced HAVING filter (can be called multiple times)
384
     * @param  string $sql    SQL statement to be used in the HAING clause
385
     * @param  array  $params parameters for the SQL statement (defaults to an empty array)
386
     * @return $this
387
     */
388 1
    public function having(string $sql, array $params = []) : TableQuery
389
    {
390 1
        $this->qiterator = null;
391 1
        $this->having[] = [ $sql, $params ];
392 1
        return $this;
393
    }
394
    /**
395
     * Apply advanced sorting
396
     * @param  string $sql    SQL statement to use in the ORDER clause
397
     * @param  array  $params optional params for the statement (defaults to an empty array)
398
     * @return $this
399
     */
400 1
    public function order(string $sql, array $params = []) : TableQuery
401
    {
402 1
        $this->qiterator = null;
403 1
        $this->order = [ $sql, $params ];
404 1
        return $this;
405
    }
406
    /**
407
     * Apply an advanced limit
408
     * @param  int         $limit  number of rows to return
409
     * @param  int         $offset number of rows to skip from the beginning (defaults to 0)
410
     * @return $this
411
     */
412 1
    public function limit(int $limit, int $offset = 0) : TableQuery
413
    {
414 1
        $this->qiterator = null;
415 1
        $this->li_of = [ $limit, $offset ];
416 1
        return $this;
417
    }
418
    /**
419
     * Get the number of records
420
     * @return int the total number of records (does not respect pagination)
421
     */
422 3
    public function count() : int
423
    {
424 3
        $table = $this->definition->getName();
425 3
        $sql = 'SELECT COUNT(DISTINCT '.$table.'.'.implode(', '.$table.'.', $this->pkey).') FROM '.$table.' ';
426 3
        $par = [];
427
        
428 3
        $relations = $this->withr;
429 3
        foreach ($this->definition->getRelations() as $k => $v) {
430 3
            foreach ($this->where as $vv) {
431 1
                if (strpos($vv[0], $k . '.') !== false) {
432 1
                    $relations[$k] = [ $v, $table ];
433
                }
434
            }
435 3
            if (isset($this->order[0]) && strpos($this->order[0], $k . '.') !== false) {
436 3
                $relations[$k] = [ $v, $table ];
437
            }
438
        }
439
440 3
        foreach ($this->joins as $k => $v) {
441
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
442
            $tmp = [];
443
            foreach ($v->keymap as $kk => $vv) {
444
                $tmp[] = $kk.' = '.$vv;
445
            }
446
            $sql .= implode(' AND ', $tmp) . ' ';
447
        }
448 3
        foreach ($relations as $k => $v) {
449 1
            $table = $v[1];
450 1
            $v = $v[0];
451 1
            if ($v->pivot) {
452 1
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$k.'_pivot ON ';
453 1
                $tmp = [];
454 1
                foreach ($v->keymap as $kk => $vv) {
455 1
                    $tmp[] = $table.'.'.$kk.' = '.$k.'_pivot.'.$vv.' ';
456
                }
457 1
                $sql .= implode(' AND ', $tmp) . ' ';
458 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$k.' ON ';
459 1
                $tmp = [];
460 1
                foreach ($v->pivot_keymap as $kk => $vv) {
461 1
                    $tmp[] = $k.'.'.$vv.' = '.$k.'_pivot.'.$kk.' ';
462
                }
463 1
                $sql .= implode(' AND ', $tmp) . ' ';
464
            } else {
465 1
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$k.' ON ';
466 1
                $tmp = [];
467 1
                foreach ($v->keymap as $kk => $vv) {
468 1
                    $tmp[] = $table.'.'.$kk.' = '.$k.'.'.$vv.' ';
469
                }
470 1
                if ($v->sql) {
471
                    $tmp[] = $v->sql . ' ';
472
                    $par = array_merge($par, $v->par ?? []);
473
                }
474 1
                $sql .= implode(' AND ', $tmp) . ' ';
475
            }
476
        }
477 3
        if (count($this->where)) {
478 1
            $sql .= 'WHERE ';
479 1
            $tmp = [];
480 1
            foreach ($this->where as $v) {
481 1
                $tmp[] = '(' . $v[0] . ')';
482 1
                $par = array_merge($par, $v[1]);
483
            }
484 1
            $sql .= implode(' AND ', $tmp).' ';
485
        }
486 3
        if (count($this->group)) {
487
            $sql .= 'GROUP BY ' . $this->group[0] . ' ';
488
            $par = array_merge($par, $this->group[1]);
489
        }
490 3
        if (count($this->having)) {
491
            $sql .= 'HAVING ';
492
            $tmp = [];
493
            foreach ($this->having as $v) {
494
                $tmp[] = '(' . $v[0] . ')';
495
                $par = array_merge($par, $v[1]);
496
            }
497
            $sql .= implode(' AND ', $tmp).' ';
498
        }
499 3
        return $this->db->one($sql, $par);
500
    }
501
    /**
502
     * Specify which columns to fetch (be default all table columns are fetched)
503
     * @param  array $fields optional array of columns to select (related columns can be used too)
504
     * @return $this
505
     */
506 13
    public function columns(array $fields) : TableQuery
507
    {
508 13
        foreach ($fields as $k => $v) {
509 13
            if (strpos($v, '*') !== false) {
510
                $temp = explode('.', $v);
511
                if (count($temp) === 1) {
512
                    $table = $this->definition->getName();
513
                    $cols = $this->definition->getColumns();
514
                } else if (count($temp) === 2) {
515
                    $table = $temp[0];
516
                    if ($this->definition->hasRelation($table)) {
517
                        $cols = $this->definition->getRelation($table)->table->getColumns();
518
                    } else if (isset($this->joins[$table])) {
519
                        $cols = $this->joins[$table]->table->getColumns();
520
                    } else {
521
                        throw new DBException('Invalid foreign table name');
522
                    }
523
                } else {
524
                    array_pop($temp);
525
                    $this->with(implode('.', $temp));
526
                    $table = array_reduce(
527
                        $temp,
528
                        function ($carry, $item) use (&$table) {
529
                            return $table->getRelation($item)->table;
530
                        }
531
                    );
532
                    $cols = $table->getColumns();
533
                    $table = implode(static::SEP, $temp);
534
                }
535
                unset($fields[$k]);
536
                foreach ($cols as $col) {
537 13
                    $fields[] = $table . '.' . $col;
538
                }
539
            }
540
        }
541 13
        $primary = $this->definition->getPrimaryKey();
542 13
        foreach ($fields as $k => $v) {
543
            try {
544 13
                $fields[$k] = $this->getColumn($v)['name'];
545 1
            } catch (DBException $e) {
546 13
                $fields[$k] = $v;
547
            }
548
        }
549 13
        foreach ($primary as $field) {
550 13
            $field = $this->getColumn($field)['name'];
551 13
            if (!in_array($field, $fields)) {
552 13
                $fields[] = $field;
553
            }
554
        }
555 13
        $this->fields = $fields;
556 13
        return $this;
557
    }
558
    /**
559
     * Perform the actual fetch
560
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
561
     * @return TableQueryIterator               the query result as an iterator
562
     */
563 11
    public function iterator(array $fields = null) : TableQueryIterator
564
    {
565 11
        if ($this->qiterator) {
566 5
            return $this->qiterator;
567
        }
568 11
        $table = $this->definition->getName();
569 11
        if ($fields !== null) {
570 1
            $this->columns($fields);
571
        }
572 11
        $relations = $this->withr;
573 11
        foreach ($relations as $k => $v) {
574 4
            $this->getAlias($k);
575
        }
576 11
        foreach ($this->definition->getRelations() as $k => $relation) {
577 11
            foreach ($this->fields as $kk => $field) {
578 11
                if (strpos($field, $k . '.') === 0) {
579
                    $relations[$k] = [ $relation, $table ];
580 11
                    $this->fields[$kk] = str_replace($k . '.', $this->getAlias($k) . '.', $field);
581
                }
582
            }
583 11
            foreach ($this->where as $kk => $v) {
584
                if (strpos($v[0], $k . '.') !== false) {
585
                    $relations[$k] = [ $relation, $table ];
586
                    $this->where[$kk] = str_replace($k . '.', $this->getAlias($k) . '.', $v);
587
                }
588
            }
589 11
            if (isset($this->order[0]) && strpos($this->order[0], $k . '.') !== false) {
590
                $relations[$k] = [ $relation, $table ];
591 11
                $this->order[0] = str_replace($k . '.', $this->getAlias($k) . '.', $this->order[0]);
592
            }
593
        }
594 11
        $select = [];
595 11
        foreach ($this->fields as $k => $field) {
596 11
            $select[] = $field . (!is_numeric($k) ? ' ' . $k : '');
597
        }
598 11
        foreach ($this->withr as $name => $relation) {
599 4
            foreach ($relation[0]->table->getColumns() as $column) {
600 4
                $select[] = $this->getAlias($name) . '.' . $column . ' ' . $this->getAlias($name . static::SEP . $column);
601
            }
602
        }
603 11
        $sql = 'SELECT '.implode(', ', $select).' FROM '.$table.' ';
604 11
        $par = [];
605 11
        foreach ($this->joins as $k => $v) {
606 1
            $sql .= ($v->many ? 'LEFT ' : '' ) . 'JOIN '.$v->table->getName().' '.$k.' ON ';
607 1
            $tmp = [];
608 1
            foreach ($v->keymap as $kk => $vv) {
609 1
                $tmp[] = $kk.' = '.$vv;
610
            }
611 1
            $sql .= implode(' AND ', $tmp) . ' ';
612
        }
613 11
        foreach ($relations as $relation => $v) {
614 4
            $table = $v[1] !== $this->definition->getName() ? $this->getAlias($v[1]) : $v[1];
615 4
            $v = $v[0];
616 4
            if ($v->pivot) {
617 3
                $alias = $this->getAlias($relation.'_pivot');
618 3
                $sql .= 'LEFT JOIN '.$v->pivot->getName().' '.$alias.' ON ';
619 3
                $tmp = [];
620 3
                foreach ($v->keymap as $kk => $vv) {
621 3
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
622
                }
623 3
                $sql .= implode(' AND ', $tmp) . ' ';
624 3
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$this->getAlias($relation).' ON ';
625 3
                $tmp = [];
626 3
                foreach ($v->pivot_keymap as $kk => $vv) {
627 3
                    $tmp[] = $this->getAlias($relation).'.'.$vv.' = '.$alias.'.'.$kk.' ';
628
                }
629 3
                $sql .= implode(' AND ', $tmp) . ' ';
630
            } else {
631 4
                $alias = $this->getAlias($relation);
632
633 4
                $sql .= 'LEFT JOIN '.$v->table->getName().' '.$alias.' ON ';
634 4
                $tmp = [];
635 4
                foreach ($v->keymap as $kk => $vv) {
636 4
                    $tmp[] = $table.'.'.$kk.' = '.$alias.'.'.$vv.' ';
637
                }
638 4
                if ($v->sql) {
639
                    $tmp[] = $v->sql . ' ';
640
                    $par = array_merge($par, $v->par ?? []);
641
                }
642 4
                $sql .= implode(' AND ', $tmp) . ' ';
643
            }
644
        }
645 11
        if (count($this->where)) {
646
            $sql .= 'WHERE ';
647
            $tmp = [];
648
            foreach ($this->where as $v) {
649
                $tmp[] = '(' . $v[0] . ')';
650
                $par = array_merge($par, $v[1]);
651
            }
652
            $sql .= implode(' AND ', $tmp).' ';
653
        }
654 11
        if (count($this->group)) {
655 1
            $sql .= 'GROUP BY ' . $this->group[0] . ' ';
656 1
            $par = array_merge($par, $this->group[1]);
657
        }
658 11
        if (count($this->having)) {
659 1
            $sql .= 'HAVING ';
660 1
            $tmp = [];
661 1
            foreach ($this->having as $v) {
662 1
                $tmp[] = '(' . $v[0] . ')';
663 1
                $par = array_merge($par, $v[1]);
664
            }
665 1
            $sql .= implode(' AND ', $tmp).' ';
666
        }
667 11
        if (count($this->order)) {
668 1
            $sql .= 'ORDER BY ' . $this->order[0] . ' ';
669 1
            $par = array_merge($par, $this->order[1]);
670
        }
671 11
        $porder = [];
672 11
        foreach ($this->definition->getPrimaryKey() as $field) {
673 11
            $porder[] = $this->getColumn($field)['name'];
674
        }
675 11
        if (count($porder)) {
676 11
            $sql .= (count($this->order) ? ', ' : 'ORDER BY ') . implode(', ', $porder) . ' ';
677
        }
678
679 11
        if ($this->li_of[0]) {
680 1
            if ($this->db->driverName() === 'oracle') {
681
                if ((int)$this->db->driverOption('version', 0) >= 12) {
682
                    $sql .= 'OFFSET ' . $this->li_of[1] . ' ROWS FETCH NEXT ' . $this->li_of[0] . ' ROWS ONLY';
683
                } else {
684
                    $f = array_map(function ($v) {
685
                        $v = explode(' ', trim($v), 2);
686
                        if (count($v) === 2) { return $v[1]; }
687
                        $v = explode('.', $v[0], 2);
688
                        return count($v) === 2 ? $v[1] : $v[0];
689
                    }, $select);
690
                    $sql = "SELECT " . implode(', ', $f) . " 
691
                            FROM (
692
                                SELECT tbl__.*, rownum rnum__ FROM (
693
                                    " . $sql . "
694
                                ) tbl__ 
695
                                WHERE rownum <= " . ($this->li_of[0] + $this->li_of[1]) . "
696
                            ) WHERE rnum__ > " . $this->li_of[1];
697
                }
698
            } else {
699 1
                $sql .= 'LIMIT ' . $this->li_of[0] . ' OFFSET ' . $this->li_of[1];
700
            }
701
        }
702 11
        return $this->qiterator = new TableQueryIterator(
703 11
            $this->db->get($sql, $par), 
704 11
            $this->pkey,
705 11
            $this->withr,
706 11
            $this->aliases
707
        );
708
    }
709 4
    protected function getAlias($name)
710
    {
711
        // 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...
712 4
        return $this->aliases[$name] = $this->aliases[$name] ?? 'alias' . static::SEP . count($this->aliases);
713
    }
714
    /**
715
     * Perform the actual fetch
716
     * @param  array|null $fields optional array of columns to select (related columns can be used too)
717
     * @return array               the query result as an array
718
     */
719 1
    public function select(array $fields = null) : array
720
    {
721 1
        return iterator_to_array($this->iterator($fields));
722
    }
723
    /**
724
     * Insert a new row in the table
725
     * @param  array   $data   key value pairs, where each key is the column name and the value is the value to insert
726
     * @return array           the inserted ID where keys are column names and values are column values
727
     */
728 1
    public function insert(array $data) : array
729
    {
730 1
        $table = $this->definition->getName();
731 1
        $columns = $this->definition->getFullColumns();
732 1
        $insert = [];
733 1
        foreach ($data as $column => $value) {
734 1
            if (isset($columns[$column])) {
735 1
                $insert[$column] = $this->normalizeValue($columns[$column], $value);
736
            }
737
        }
738 1
        if (!count($insert)) {
739
            throw new DBException('No valid columns to insert');
740
        }
741 1
        $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_keys($insert)).') VALUES (??)';
742 1
        $par = [$insert];
743 1
        $primary = $this->definition->getPrimaryKey();
744 1
        if (!count($primary)) {
745
            $this->db->query($sql, $par);
746
            return [];
747
        }
748 1
        if ($this->db->driverName() === 'oracle') {
749
            $ret = [];
750
            foreach ($primary as $k) {
751
                $ret[$k] = str_repeat(' ', 255);
752
                $par[] = &$ret[$k];
753
            }
754
            $sql .= ' RETURNING ' . implode(',', $primary) . ' INTO ' . implode(',', array_fill(0, count($primary), '?'));
755
            $this->db->query($sql, $par);
756
            return $ret;
757
        } else {
758 1
            $ret = [];
759 1
            $ins = $this->db->query($sql, $par)->insertID();
760 1
            foreach ($primary as $k) {
761 1
                $ret[$k] = $data[$k] ?? $ins;
762
            }
763 1
            return $ret;
764
        }
765
    }
766
    /**
767
     * Update the filtered rows with new data
768
     * @param  array  $data key value pairs, where each key is the column name and the value is the value to insert
769
     * @return int          the number of affected rows
770
     */
771 1
    public function update(array $data) : int
772
    {
773 1
        $table = $this->definition->getName();
774 1
        $columns = $this->definition->getFullColumns();
775 1
        $update = [];
776 1
        foreach ($data as $column => $value) {
777 1
            if (isset($columns[$column])) {
778 1
                $update[$column] = $this->normalizeValue($columns[$column], $value);
779
            }
780
        }
781 1
        if (!count($update)) {
782
            throw new DBException('No valid columns to update');
783
        }
784 1
        $sql = 'UPDATE '.$table.' SET ';
785 1
        $par = [];
786
        $sql .= implode(', ', array_map(function ($v) { return $v . ' = ?'; }, array_keys($update))) . ' ';
787 1
        $par = array_merge($par, array_values($update));
788 1
        if (count($this->where)) {
789 1
            $sql .= 'WHERE ';
790 1
            $tmp = [];
791 1
            foreach ($this->where as $v) {
792 1
                $tmp[] = $v[0];
793 1
                $par = array_merge($par, $v[1]);
794
            }
795 1
            $sql .= implode(' AND ', $tmp) . ' ';
796
        }
797 1
        if (count($this->order)) {
798
            $sql .= $this->order[0];
799
            $par = array_merge($par, $this->order[1]);
800
        }
801 1
        return $this->db->query($sql, $par)->affected();
802
    }
803
    /**
804
     * Delete the filtered rows from the DB
805
     * @return int the number of deleted rows
806
     */
807 1
    public function delete() : int
808
    {
809 1
        $table = $this->definition->getName();
810 1
        $sql = 'DELETE FROM '.$table.' ';
811 1
        $par = [];
812 1
        if (count($this->where)) {
813 1
            $sql .= 'WHERE ';
814 1
            $tmp = [];
815 1
            foreach ($this->where as $v) {
816 1
                $tmp[] = $v[0];
817 1
                $par = array_merge($par, $v[1]);
818
            }
819 1
            $sql .= implode(' AND ', $tmp) . ' ';
820
        }
821 1
        if (count($this->order)) {
822
            $sql .= $this->order[0];
823
            $par = array_merge($par, $this->order[1]);
824
        }
825 1
        return $this->db->query($sql, $par)->affected();
826
    }
827
    /**
828
     * Solve the n+1 queries problem by prefetching a relation by name
829
     * @param  string $relation the relation name to fetch along with the data
830
     * @return $this
831
     */
832 4
    public function with(string $relation) : TableQuery
833
    {
834 4
        $this->qiterator = null;
835 4
        $parts = explode('.', $relation);
836 4
        $table = $this->definition;
837 4
        array_reduce(
838 4
            $parts,
839 4
            function ($carry, $item) use (&$table) {
840 4
                $relation = $table->getRelation($item);
841 4
                if (!$relation) {
842
                    throw new DBException('Invalid relation name');
843
                }
844 4
                $name = $carry ? $carry . static::SEP . $item : $item;
845 4
                $this->withr[$name] = [ $relation, $carry ?? $table->getName() ];
846 4
                $table = $relation->table;
847 4
                return $name;
848 4
            }
849
        );
850 4
        return $this;
851
    }
852
853 1
    public function getIterator()
854
    {
855 1
        return $this->iterator();
856
    }
857
858 9
    public function offsetGet($offset)
859
    {
860 9
        return $this->iterator()->offsetGet($offset);
861
    }
862
    public function offsetExists($offset)
863
    {
864
        return $this->iterator()->offsetExists($offset);
865
    }
866
    public function offsetUnset($offset)
867
    {
868
        return $this->iterator()->offsetUnset($offset);
869
    }
870
    public function offsetSet($offset, $value)
871
    {
872
        return $this->iterator()->offsetSet($offset, $value);
873
    }
874
875
    public function collection(array $fields = null) : Collection
876
    {
877
        return new Collection($this->iterator($fields));
878
    }
879
}
880