Completed
Branch 7-dev (bf2895)
by Oscar
03:53
created

RowCollection::__get()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 8.2448
c 0
b 0
f 0
cc 7
nc 7
nop 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace SimpleCrud;
5
6
use ArrayAccess;
7
use BadMethodCallException;
8
use Countable;
9
use Iterator;
10
use JsonSerializable;
11
use RuntimeException;
12
use SimpleCrud\Query\Select;
13
14
/**
15
 * Stores a collection of rows.
16
 */
17
class RowCollection implements ArrayAccess, Iterator, Countable, JsonSerializable
18
{
19
    private $table;
20
    private $rows = [];
21
    private $data = [];
22
23
    public function __construct(Table $table, Row ...$rows)
24
    {
25
        $this->table = $table;
26
27
        foreach ($rows as $row) {
28
            $this->rows[$row->id] = $row;
29
        }
30
    }
31
32
    public function __debugInfo(): array
33
    {
34
        return [
35
            'table' => (string) $this->table,
36
            'rows' => $this->rows,
37
        ];
38
    }
39
40
    public function __call(string $name, array $arguments): Select
41
    {
42
        $db = $this->table->getDatabase();
43
44
        //Relations
45
        if (isset($db->$name)) {
46
            return $this->select($db->$name);
47
        }
48
49
        throw new BadMethodCallException(
50
            sprintf('Invalid method call %s', $name)
51
        );
52
    }
53
54
    public function setData(array $data): self
55
    {
56
        $this->data = $data + $this->data;
57
58
        return $this;
59
    }
60
61
    /**
62
     * @see JsonSerializable
63
     */
64
    public function jsonSerialize()
65
    {
66
        return $this->toArray();
67
    }
68
69
    /**
70
     * Magic method to stringify the values.
71
     */
72
    public function __toString()
73
    {
74
        return json_encode($this, JSON_NUMERIC_CHECK);
75
    }
76
77
    /**
78
     * Returns the table associated with this row
79
     */
80
    public function getTable(): Table
81
    {
82
        return $this->table;
83
    }
84
85
    /**
86
     * Return the value of all rows
87
     */
88
    public function __get(string $name)
89
    {
90
        //It's a field
91
        if (isset($this->table->{$name})) {
92
            $result = [];
93
94
            foreach ($this->rows as $id => $row) {
95
                $result[$id] = $row->$name;
96
            }
97
98
            return $result;
99
        }
100
101
        //It's a custom data
102
        if (array_key_exists($name, $this->data)) {
103
            return $this->data[$name];
104
        }
105
106
        $db = $this->table->getDatabase();
107
108
        if (isset($db->$name)) {
109
            $table = $db->$name;
110
            $joinTable = $this->table->getJoinTable($table);
111
112
            //Many-to-many
113
            if (!$this->count()) {
114
                $result = $table->createCollection();
115
116
                $this->link($table, $result);
117
            } elseif ($joinTable) {
118
                $joinRows = $this->select($joinTable)->run();
119
                $result = $joinRows->select($table)->run();
120
121
                $this->link($table, $result, $joinRows);
122
            } else {
123
                $result = $this->select($table)->run();
124
                $this->link($table, $result);
125
            }
126
127
            return $result;
128
        }
129
130
        throw new RuntimeException(
131
            sprintf('Undefined property "%s" in the table "%s"', $name, $this->table)
132
        );
133
    }
134
135
    public function link(Table $table, RowCollection $rows, RowCollection $relations = null)
136
    {
137
        if ($relations) {
138
            return $this->linkThrough($rows, $relations);
139
        }
140
141
        //Has many (inversed of Has one)
142
        if ($this->table->getJoinField($table)) {
143
            return $rows->link($this->table, $this);
144
        }
145
146
        $relations = [];
147
        $foreignKey = $this->table->getForeignKey();
148
149
        foreach ($rows as $row) {
150
            $id = $row->{$foreignKey};
151
            $row->link($this->table, $this[$id]);
152
153
            if (!isset($relations[$id])) {
154
                $relations[$id] = [];
155
            }
156
157
            $relations[$id][] = $row;
158
        }
159
160
        foreach ($this as $id => $row) {
161
            $row->link($table, $table->createCollection($relations[$id] ?? []));
162
        }
163
164
        $this->data[$table->getName()] = $rows;
165
    }
166
167
    private function linkThrough(RowCollection $rows, RowCollection $relations)
168
    {
169
        $table = $rows->getTable();
170
        $relTable = $relations->getTable();
171
        $this_fk = $this->table->getForeignKey();
172
        $rows_fk = $table->getForeignKey();
173
        $this_in_rows = [];
174
        $rows_in_this = [];
175
176
        foreach ($relations as $relation) {
177
            $this_id = $relation->{$this_fk};
178
            $rows_id = $relation->{$rows_fk};
179
180
            if (!isset($rows_in_this[$this_id])) {
181
                $rows_in_this[$this_id] = [];
182
            }
183
184
            $rows_in_this[$this_id][] = $rows[$rows_id];
185
186
            if (!isset($this_in_rows[$rows_id])) {
187
                $this_in_rows[$rows_id] = [];
188
            }
189
190
            $this_in_rows[$rows_id][] = $this[$this_id];
191
        }
192
193
        foreach ($this as $id => $row) {
194
            $row->link($table, $table->createCollection($rows_in_this[$id] ?? []));
195
        }
196
197
        foreach ($rows as $id => $row) {
198
            $row->link($this->table, $this->table->createCollection($this_in_rows[$id] ?? []));
199
        }
200
201
        $this->data[$table->getName()] = $rows;
202
203
        $rows->link($relTable, $relations);
204
        $this->link($relTable, $relations);
205
    }
206
207
    /**
208
     * Change a property of all rows
209
     * @param mixed $value
210
     */
211
    public function __set(string $name, $value)
212
    {
213
        foreach ($this->rows as $row) {
214
            $row->$name = $value;
215
        }
216
    }
217
218
    /**
219
     * Check whether a value is set or not
220
     * @param mixed $name
221
     */
222
    public function __isset($name)
223
    {
224
        return isset($this->table->{$name});
225
    }
226
227
    /**
228
     * @see ArrayAccess
229
     * @param mixed $offset
230
     * @param mixed $value
231
     */
232
    public function offsetSet($offset, $value)
233
    {
234
        throw new RuntimeException('RowCollection are read-only');
235
    }
236
237
    /**
238
     * @see ArrayAccess
239
     * @param mixed $offset
240
     */
241
    public function offsetExists($offset)
242
    {
243
        return isset($this->rows[$offset]);
244
    }
245
246
    /**
247
     * @see ArrayAccess
248
     * @param mixed $offset
249
     */
250
    public function offsetUnset($offset)
251
    {
252
        throw new RuntimeException('RowCollection are read-only');
253
    }
254
255
    /**
256
     * @see ArrayAccess
257
     * @param mixed $offset
258
     */
259
    public function offsetGet($offset)
260
    {
261
        return $this->rows[$offset] ?? null;
262
    }
263
264
    /**
265
     * @see Iterator
266
     */
267
    public function rewind()
268
    {
269
        return reset($this->rows);
270
    }
271
272
    /**
273
     * @see Iterator
274
     */
275
    public function current()
276
    {
277
        return current($this->rows);
278
    }
279
280
    /**
281
     * @see Iterator
282
     */
283
    public function key()
284
    {
285
        return key($this->rows);
286
    }
287
288
    /**
289
     * @see Iterator
290
     */
291
    public function next()
292
    {
293
        return next($this->rows);
294
    }
295
296
    /**
297
     * @see Iterator
298
     */
299
    public function valid()
300
    {
301
        return key($this->rows) !== null;
302
    }
303
304
    /**
305
     * @see Countable
306
     */
307
    public function count()
308
    {
309
        return count($this->rows);
310
    }
311
312
    /**
313
     * Returns an array with all fields of all rows
314
     */
315
    public function toArray(): array
316
    {
317
        return array_map(function ($row) {
318
            return $row->toArray();
319
        }, $this->rows);
320
    }
321
322
    /**
323
     * Save all rows in the database
324
     */
325
    public function save(): self
326
    {
327
        foreach ($this->rows as $row) {
328
            $row->save();
329
        }
330
331
        return $this;
332
    }
333
334
    /**
335
     * Delete all rows in the database
336
     */
337
    public function delete(): self
338
    {
339
        $ids = array_values($this->id);
340
341
        if (count($ids)) {
342
            $this->table->delete()
0 ignored issues
show
Documentation Bug introduced by
The method delete does not exist on object<SimpleCrud\Table>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
343
                ->where('id IN ', $ids)
344
                ->run();
345
346
            $this->id = null;
347
        }
348
349
        return $this;
350
    }
351
352
    /**
353
     * Creates a select query of a table related with this row collection
354
     */
355
    public function select(Table $table): Select
356
    {
357
        return $table->select()->relatedWith($this);
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<SimpleCrud\Table>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
358
    }
359
}
360