Completed
Push — master ( 7c9586...c8ce39 )
by Oscar
01:44
created

Table::offsetUnset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace SimpleCrud;
5
6
use ArrayAccess;
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use SimpleCrud\Events\BeforeCreateRow;
9
use SimpleCrud\Fields\FieldInterface;
10
use SimpleCrud\Query\QueryInterface;
11
12
/**
13
 * Manages a database table.
14
 *
15
 * @property FieldInterface $id
16
 */
17
class Table implements ArrayAccess
18
{
19
    private $name;
20
    private $db;
21
    private $cache = [];
22
    private $fields = [];
23
    private $defaults = [];
24
    private $eventDispatcher;
25
26
    protected const ROW_CLASS = Row::class;
27
    protected const ROWCOLLECTION_CLASS = RowCollection::class;
28
29
    final public function __construct(Database $db, string $name)
30
    {
31
        $this->db = $db;
32
        $this->name = $name;
33
34
        $fieldFactory = $db->getFieldFactory();
35
        $fields = $db->getScheme()->getTableFields($name);
36
37
        foreach ($fields as $info) {
38
            $field = $fieldFactory->get($this, $info);
39
40
            $this->fields[$field->getName()] = $field;
41
            $this->defaults[$field->getName()] = null;
42
        }
43
    }
44
45
    public function __debugInfo(): array
46
    {
47
        return [
48
            'name' => $this->name,
49
            'fields' => $this->fields,
50
        ];
51
    }
52
53
    public function __toString()
54
    {
55
        return "`{$this->name}`";
56
    }
57
58
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
59
    {
60
        $this->eventDispatcher = $eventDispatcher;
61
62
        return $this;
63
    }
64
65
    public function getEventDispatcher(): ?EventDispatcherInterface
66
    {
67
        return $this->eventDispatcher;
68
    }
69
70
    /**
71
     * Get the devault values used in new rows
72
     */
73
    public function getDefaults(array $overrides = null): array
74
    {
75
        if (empty($overrides)) {
76
            return $this->defaults;
77
        }
78
79
        foreach ($overrides as $name => &$value) {
80
            if (!isset($this->fields[$name])) {
81
                throw new SimpleCrudException(
82
                    sprintf('The field "%s" does not exist in the table %s', $name, $this)
83
                );
84
            }
85
86
            $value = $this->fields[$name]->format($value);
87
        }
88
89
        return $overrides + $this->defaults;
90
    }
91
92
    /**
93
     * Store a row in the cache.
94
     */
95
    public function cache(Row $row): Row
96
    {
97
        if ($row->id) {
98
            $this->cache[$row->id] = $row;
99
        }
100
101
        return $row;
102
    }
103
104
    /**
105
     * Clear the current cache.
106
     */
107
    public function clearCache(): self
108
    {
109
        $this->cache = [];
110
111
        return $this;
112
    }
113
114
    /**
115
     * Returns whether the id is cached or not
116
     * @param mixed $id
117
     */
118
    public function isCached($id): bool
119
    {
120
        return array_key_exists($id, $this->cache);
121
    }
122
123
    /**
124
     * Returns a row from the cache.
125
     * @param mixed $id
126
     */
127
    public function getCached($id): ?Row
128
    {
129
        if (!$this->isCached($id)) {
130
            return null;
131
        }
132
133
        $row = $this->cache[$id];
134
135
        if ($row && !$row->id) {
136
            return $this->cache[$id] = null;
137
        }
138
139
        return $row;
140
    }
141
142
    /**
143
     * Magic method to create queries related with this table.
144
     */
145
    public function __call(string $name, array $arguments): QueryInterface
146
    {
147
        $class = sprintf('SimpleCrud\\Query\\%s', ucfirst($name));
148
149
        return $class::create($this, $arguments);
150
    }
151
152
    /**
153
     * Magic method to get the Field instance of a table field
154
     */
155
    public function __get(string $name): FieldInterface
156
    {
157
        if (!isset($this->fields[$name])) {
158
            throw new SimpleCrudException(
159
                sprintf('The field `%s` does not exist in the table %s', $name, $this)
160
            );
161
        }
162
163
        return $this->fields[$name];
164
    }
165
166
    /**
167
     * Magic method to check if a field exists or not.
168
     */
169
    public function __isset(string $name): bool
170
    {
171
        return isset($this->fields[$name]);
172
    }
173
174
    /**
175
     * Check if a row with a specific id exists.
176
     *
177
     * @see ArrayAccess
178
     * @param mixed $offset
179
     */
180
    public function offsetExists($offset): bool
181
    {
182
        if ($this->isCached($offset)) {
183
            return $this->getCached($offset) !== null;
184
        }
185
186
        return $this->count()
0 ignored issues
show
Documentation Bug introduced by
The method count 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...
187
            ->where('id = ', $offset)
188
            ->limit(1)
189
            ->run() === 1;
190
    }
191
192
    /**
193
     * Returns a row with a specific id.
194
     *
195
     * @see ArrayAccess
196
     *
197
     * @param mixed $offset
198
     */
199
    public function offsetGet($offset): ?Row
200
    {
201
        if ($this->isCached($offset)) {
202
            return $this->getCached($offset);
203
        }
204
205
        return $this->cache[$offset] = $this->select()
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...
206
            ->one()
207
            ->where('id = ', $offset)
208
            ->run();
209
    }
210
211
    /**
212
     * Store a row with a specific id.
213
     *
214
     * @see ArrayAccess
215
     * @param mixed $offset
216
     * @param mixed $value
217
     */
218
    public function offsetSet($offset, $value): Row
219
    {
220
        //Insert on missing offset
221
        if ($offset === null) {
222
            $value['id'] = null;
223
224
            return $this->create($value)->save();
225
        }
226
227
        //Update if the element is cached and exists
228
        $row = $this->getCached($offset);
229
230
        if ($row) {
231
            return $row->edit($value)->save();
232
        }
233
234
        //Update if the element it's not cached
235
        if (!$this->isCached($row)) {
236
            $this->update()
0 ignored issues
show
Documentation Bug introduced by
The method update 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...
237
                ->columns($value)
238
                ->where('id = ', $offset)
239
                ->run();
240
        }
241
    }
242
243
    /**
244
     * Remove a row with a specific id.
245
     *
246
     * @see ArrayAccess
247
     * @param mixed $offset
248
     */
249
    public function offsetUnset($offset)
250
    {
251
        $this->cache[$offset] = null;
252
253
        $this->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...
254
            ->where('id = ', $offset)
255
            ->run();
256
    }
257
258
    /**
259
     * Returns the table name.
260
     */
261
    public function getName(): string
262
    {
263
        return $this->name;
264
    }
265
266
    /**
267
     * Returns the foreign key name.
268
     */
269
    public function getForeignKey(): string
270
    {
271
        return "{$this->name}_id";
272
    }
273
274
    /**
275
     * Returns the foreign key.
276
     */
277
    public function getJoinField(Table $table): ?FieldInterface
278
    {
279
        $field = $table->getForeignKey();
280
281
        return $this->fields[$field] ?? null;
282
    }
283
284
    public function getJoinTable(Table $table): ?Table
285
    {
286
        $name1 = $this->getName();
287
        $name2 = $table->getName();
288
        $name = $name1 < $name2 ? "{$name1}_{$name2}" : "{$name2}_{$name1}";
289
290
        $joinTable = $this->db->{$name} ?? null;
291
292
        if ($joinTable && $joinTable->getJoinField($this) && $joinTable->getJoinField($table)) {
293
            return $joinTable;
294
        }
295
296
        return null;
297
    }
298
299
    /**
300
     * Returns the Database instance associated with this table.
301
     */
302
    public function getDatabase(): Database
303
    {
304
        return $this->db;
305
    }
306
307
    /**
308
     * Returns all fields.
309
     *
310
     * @return FieldInterface[]
311
     */
312
    public function getFields()
313
    {
314
        return $this->fields;
315
    }
316
317
    public function create(array $data = [], bool $fromDatabase = false): Row
0 ignored issues
show
Unused Code introduced by
The parameter $fromDatabase is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
318
    {
319
        if (isset($data['id']) && ($row = $this->getCached($data['id']))) {
320
            return $row;
321
        }
322
323
        $eventDispatcher = $this->getEventDispatcher();
324
325
        if ($eventDispatcher) {
326
            $event = new BeforeCreateRow($data);
327
            $eventDispatcher->dispatch($event);
0 ignored issues
show
Documentation introduced by
$event is of type object<SimpleCrud\Events\BeforeCreateRow>, but the function expects a object<Psr\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
328
            $data = $event->getData();
329
        }
330
331
        $class = self::ROW_CLASS;
332
        return $this->cache(new $class($this, $data));
333
    }
334
335
    public function createCollection(array $rows = [], bool $fromDatabase = false): RowCollection
336
    {
337
        if ($fromDatabase) {
338
            $rows = $this->createCollection(
339
                array_map(
340
                    function ($data): Row {
341
                        return $this->create($data, true);
342
                    },
343
                    $rows
344
                )
345
            );
346
        }
347
348
        $class = self::ROWCOLLECTION_CLASS;
349
        return new $class($this, ...$rows);
350
    }
351
}
352