Completed
Pull Request — master (#9)
by Samuel
03:17
created

Selection::__call()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
ccs 6
cts 9
cp 0.6667
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
crap 3.3332
1
<?php
2
declare(strict_types=1);
3
4
namespace SimpleMapper;
5
6
use Nette\Database\Table\IRow;
7
use Nette\Database\Table\Selection as NetteDatabaseSelection;
8
use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow;
9
use Nette\InvalidArgumentException;
10
use ArrayAccess;
11
use Iterator;
12
use Countable;
13
use SimpleMapper\Structure\Structure;
14
use Traversable;
15
16
class Selection implements Iterator, Countable, ArrayAccess
17
{
18
    /** @var NetteDatabaseSelection */
19
    private $selection;
20
21
    /** @var Structure */
22
    protected $structure;
23
24
    /**
25
     * @param NetteDatabaseSelection $selection
26
     * @param Structure $structure
27
     */
28 60
    public function __construct(NetteDatabaseSelection $selection, Structure $structure)
29
    {
30 60
        $this->selection = $selection;
31 60
        $this->structure = $structure;
32 60
    }
33
34
    /**
35
     * @return NetteDatabaseSelection
36
     */
37 4
    public function getSelection(): NetteDatabaseSelection
38
    {
39 4
        return $this->selection;
40
    }
41
42
    /********************************************************************\
43
    | Magic methods
44
    \********************************************************************/
45
46
    /**
47
     * Clone object
48
     */
49
    public function __clone()
50
    {
51
        $this->selection = clone $this->selection;
52
    }
53
54
    /**
55
     * @param string $name
56
     * @param array $arguments
57
     * @return mixed
58
     */
59 2
    public function __call($name, array $arguments)
60
    {
61 2
        if (substr($name, 0, 5) === 'scope') {
62 2
            $scopeName = lcfirst(substr($name, 5));
63 2
            $scope = $this->structure->getScope($this->selection->getName(), $scopeName);
64 2
            if (!$scope) {
65
                trigger_error('Scope ' . $scopeName . ' is not defined for table ' . $this->selection->getName(), E_USER_ERROR);
66
            }
67 2
            return $this->where(call_user_func_array($scope->getCallback(), $arguments));
68
        }
69
70
        trigger_error('Call to undefined method ' . get_class($this) . '::' . $name . '()', E_USER_ERROR);
71
    }
72
73
    /**********************************************************************\
74
     * Wrapper function - fetch
75
    \**********************************************************************/
76
77
    /**
78
     * Returns row specified by primary key
79
     * @param mixed $key Primary key
80
     * @return ActiveRow|null
81
     */
82 2
    public function get($key): ?ActiveRow
83
    {
84 2
        $row = $this->selection->get($key);
85 2
        return $row instanceof NetteDatabaseActiveRow ? $this->prepareRecord($row) : null;
86
    }
87
88
    /**
89
     * Returns one record
90
     * @return ActiveRow|null
91
     */
92 32
    public function fetch(): ?ActiveRow
93
    {
94 32
        $row = $this->selection->fetch();
95 32
        return $row ? $this->prepareRecord($row) : null;
96
    }
97
98
    /**
99
     * Fetches single field
100
     * @param string|null $column
101
     * @return mixed
102
     */
103 2
    public function fetchField(string $column = null)
104
    {
105 2
        return $this->selection->fetchField($column);
106
    }
107
108
    /**
109
     * Fetch key => value pairs
110
     * @param mixed $key
111
     * @param mixed $value
112
     * @return array
113
     */
114 2
    public function fetchPairs($key = null, $value = null): array
115
    {
116 2
        $result = [];
117
118 2
        $pairs = $this->selection->fetchPairs($key, $value);
119 2
        foreach ($pairs as $k => $v) {
120 2
            $result[$k] = $v instanceof NetteDatabaseActiveRow ? $this->prepareRecord($v) : $v;
121
        }
122 2
        return $result;
123
    }
124
125
    /**
126
     * Returns all records
127
     * @return array
128
     */
129 4
    public function fetchAll(): array
130
    {
131 4
        return $this->prepareRecords($this->selection->fetchAll());
132
    }
133
134
    /**
135
     * Some examples of usage: https://github.com/nette/utils/blob/master/tests%2FUtils%2FArrays.associate().phpt
136
     * @param mixed $path
137
     * @return array|\stdClass
138
     */
139 2
    public function fetchAssoc($path)
140
    {
141 2
        return $this->selection->fetchAssoc($path);
142
    }
143
144
    /**********************************************************************\
145
     * Wrapper function - sql selections
146
    \**********************************************************************/
147
148
    /**
149
     * Adds select clause, more calls appends to the end
150
     * @param string $columns   for example "column, MD5(column) AS column_md5"
151
     * @param mixed ...$params
152
     * @return Selection
153
     */
154 4
    public function select($columns, ...$params): Selection
155
    {
156 4
        $this->selection->select($columns, ...$params);
157 4
        return $this;
158
    }
159
160
    /**
161
     * Adds condition for primary key
162
     * @param mixed $key
163
     * @return Selection
164
     */
165 8
    public function wherePrimary($key): Selection
166
    {
167 8
        $this->selection->wherePrimary($key);
168 8
        return $this;
169
    }
170
171
    /**
172
     * Adds where condition, more calls appends with AND
173
     * @param string|string[] $condition
174
     * @param mixed ...$params
175
     * @return Selection
176
     */
177 14
    public function where($condition, ...$params): Selection
178
    {
179 14
        $this->selection->where($condition, ...$params);
180 14
        return $this;
181
    }
182
183
    /**
184
     * Adds ON condition when joining specified table, more calls appends with AND
185
     * @param string $tableChain    table chain or table alias for which you need additional left join condition
186
     * @param string|string[] $condition     condition possibly containing ?
187
     * @param mixed ...$params
188
     * @return Selection
189
     */
190
    public function joinWhere(string $tableChain, $condition, ...$params): Selection
191
    {
192
        $this->selection->joinWhere($tableChain, $condition, ...$params);
193
        return $this;
194
    }
195
196
    /**
197
     * Adds where condition using the OR operator between parameters
198
     * More calls appends with AND.
199
     * @param array $parameters     ['column1' => 1, 'column2 > ?' => 2, 'full condition']
200
     * @return Selection
201
     * @throws InvalidArgumentException
202
     */
203 2
    public function whereOr(array $parameters): Selection
204
    {
205 2
        $this->selection->whereOr($parameters);
206 2
        return $this;
207
    }
208
209
    /**
210
     * Adds order clause, more calls appends to the end
211
     * @param string $columns       for example 'column1, column2 DESC'
212
     * @param mixed ...$params
213
     * @return Selection
214
     */
215 4
    public function order(string $columns, ...$params): Selection
216
    {
217 4
        $this->selection->order($columns, ...$params);
218 4
        return $this;
219
    }
220
221
    /**
222
     * Sets limit clause, more calls rewrite old values
223
     * @param int $limit
224
     * @param int $offset
225
     * @return Selection
226
     */
227 2
    public function limit(int $limit, int $offset = null): Selection
228
    {
229 2
        $this->selection->limit($limit, $offset);
230 2
        return $this;
231
    }
232
233
    /**
234
     * Sets offset using page number, more calls rewrite old values
235
     * @param int $page
236
     * @param int $itemsPerPage
237
     * @param int|null $numOfPages
238
     * @return Selection
239
     */
240 2
    public function page(int $page, int $itemsPerPage, int & $numOfPages = null): Selection
241
    {
242 2
        $this->selection->page($page, $itemsPerPage, $numOfPages);
243 2
        return $this;
244
    }
245
246
    /**
247
     * Sets group clause, more calls rewrite old value
248
     * @param string $columns
249
     * @param mixed ...$params
250
     * @return Selection
251
     */
252 2
    public function group(string $columns, ...$params): Selection
253
    {
254 2
        $this->selection->group($columns, ...$params);
255 2
        return $this;
256
    }
257
258
    /**
259
     * Sets having clause, more calls rewrite old value
260
     * @param string $having
261
     * @param mixed ...$params
262
     * @return Selection
263
     */
264 2
    public function having(string $having, ...$params): Selection
265
    {
266 2
        $this->selection->having($having, ...$params);
267 2
        return $this;
268
    }
269
270
    /**
271
     * Aliases table. Example ':book:book_tag.tag', 'tg'
272
     * @param string $tableChain
273
     * @param string $alias
274
     * @return Selection
275
     */
276
    public function alias(string $tableChain, string $alias): Selection
277
    {
278
        $this->selection->alias($tableChain, $alias);
279
        return $this;
280
    }
281
282
    /**********************************************************************\
283
     * Wrapper function - aggregations
284
    \**********************************************************************/
285
286
    /**
287
     * Executes aggregation function
288
     * @param string $function Select call in "FUNCTION(column)" format
289
     * @return float
290
     */
291 2
    public function aggregation(string $function): float
292
    {
293 2
        return (float) $this->selection->aggregation($function);
294
    }
295
296
    /**
297
     * Counts number of rows
298
     * Countable interface
299
     * @param string $column If it is not provided returns count of result rows, otherwise runs new sql counting query
300
     * @return int
301
     */
302 14
    public function count(string $column = null): int
303
    {
304 14
        return (int) $this->selection->count($column);
305
    }
306
307
    /**
308
     * Returns minimum value from a column
309
     * @param string $column
310
     * @return float
311
     */
312 2
    public function min(string $column): float
313
    {
314 2
        return (float) $this->selection->min($column);
315
    }
316
317
    /**
318
     * Returns maximum value from a column
319
     * @param string $column
320
     * @return float
321
     */
322 2
    public function max(string $column): float
323
    {
324 2
        return (float) $this->selection->max($column);
325
    }
326
327
    /**
328
     * Returns sum of values in a column
329
     * @param string $column
330
     * @return float
331
     */
332 2
    public function sum(string $column): float
333
    {
334 2
        return (float) $this->selection->sum($column);
335
    }
336
337
    /**********************************************************************\
338
     * Wrapper function - manipulation
339
    \**********************************************************************/
340
341
    /**
342
     * Inserts row in a table
343
     * @param  array|Traversable|Selection $data
344
     * @return IRow|int|bool
345
     */
346 2
    public function insert($data)
347
    {
348 2
        $insertResult = $this->selection->insert($data);
349 2
        return $insertResult instanceof IRow ? $this->prepareRecord($insertResult) : $insertResult;
350
    }
351
352
    /**
353
     * Updates all rows in result set
354
     * @param  array|Traversable $data      ($column => $value)
355
     * @return int
356
     */
357 2
    public function update($data): int
358
    {
359 2
        return $this->selection->update($data);
360
    }
361
362
    /**
363
     * Deletes all rows in result set
364
     * @return int
365
     */
366 2
    public function delete(): int
367
    {
368 2
        return $this->selection->delete();
369
    }
370
371
    /**********************************************************************\
372
     * Iterator interface
373
    \**********************************************************************/
374
375
    /**
376
     * Rewind selection
377
     */
378 12
    public function rewind(): void
379
    {
380 12
        $this->selection->rewind();
381 12
    }
382
383
    /**
384
     * Returns current selection data record
385
     * @return ActiveRow|null
386
     */
387 12
    public function current(): ?ActiveRow
388
    {
389 12
        $row = $this->selection->current();
390 12
        return $row instanceof IRow ? $this->prepareRecord($row) : null;
391
    }
392
393
    /**
394
     * Returns current selection data key
395
     * @return string|int Row ID
396
     */
397 2
    public function key()
398
    {
399 2
        return $this->selection->key();
400
    }
401
402
    /**
403
     * Move iterator
404
     */
405 12
    public function next(): void
406
    {
407 12
        $this->selection->next();
408 12
    }
409
410
    /**
411
     * It is selection valid
412
     * @return bool
413
     */
414 12
    public function valid(): bool
415
    {
416 12
        return $this->selection->valid();
417
    }
418
419
    /**********************************************************************\
420
     * ArrayAccess interface
421
    \**********************************************************************/
422
423
    /**
424
     * @param string $key Row ID
425
     * @param IRow $value
426
     */
427 2
    public function offsetSet($key, $value): void
428
    {
429 2
        $this->selection->offsetSet($key, $value);
430 2
    }
431
432
    /**
433
     * Returns specified row
434
     * @param string $key Row ID
435
     * @return ActiveRow|null
436
     */
437 2
    public function offsetGet($key): ?ActiveRow
438
    {
439 2
        $row = $this->selection->offsetGet($key);
440 2
        return $row instanceof IRow ? $this->prepareRecord($row) : null;
441
    }
442
443
    /**
444
     * Tests if row exists
445
     * @param string $key Row ID
446
     * @return bool
447
     */
448 2
    public function offsetExists($key): bool
449
    {
450 2
        return $this->selection->offsetExists($key);
451
    }
452
453
    /**
454
     * Removes row from result set
455
     * @param string $key Row ID
456
     */
457 2
    public function offsetUnset($key): void
458
    {
459 2
        $this->selection->offsetUnset($key);
460 2
    }
461
462
    /**********************************************************************\
463
     * Build methods
464
    \**********************************************************************/
465
466
    /**
467
     * Prepare one record
468
     * @param IRow $row
469
     * @return ActiveRow
470
     */
471 48
    protected function prepareRecord(IRow $row): ActiveRow
472
    {
473 48
        $recordClass = $this->structure->getActiveRowClass($row->getTable()->getName());
474 48
        return new $recordClass($row, $this->structure);
475
    }
476
477
    /**
478
     * Prepare records array
479
     * @param array $rows
480
     * @return array
481
     */
482 4
    protected function prepareRecords(array $rows): array
483
    {
484 4
        $result = [];
485 4
        foreach ($rows as $row) {
486 4
            $result[] = $this->prepareRecord($row);
487
        }
488 4
        return $result;
489
    }
490
}
491