Completed
Push — master ( d5ed8a...45b64e )
by Oscar
01:36
created

Table::insert()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace SimpleCrud;
5
6
use ArrayAccess;
7
use Countable;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use SimpleCrud\Events\BeforeCreateRow;
10
use SimpleCrud\Events\CreateDeleteQuery;
11
use SimpleCrud\Events\CreateInsertQuery;
12
use SimpleCrud\Events\CreateSelectQuery;
13
use SimpleCrud\Events\CreateUpdateQuery;
14
use SimpleCrud\Fields\FieldInterface;
15
use SimpleCrud\Query\Delete;
16
use SimpleCrud\Query\Insert;
17
use SimpleCrud\Query\Select;
18
use SimpleCrud\Query\SelectAggregate;
19
use SimpleCrud\Query\Update;
20
21
/**
22
 * Manages a database table.
23
 *
24
 * @property FieldInterface $id
25
 */
26
class Table implements ArrayAccess, Countable
27
{
28
    private $name;
29
    private $db;
30
    private $cache = [];
31
    private $fields = [];
32
    private $defaults = [];
33
    private $eventDispatcher;
34
35
    protected const ROW_CLASS = Row::class;
36
    protected const ROWCOLLECTION_CLASS = RowCollection::class;
37
38
    final public function __construct(Database $db, string $name)
39
    {
40
        $this->db = $db;
41
        $this->name = $name;
42
43
        $fieldFactory = $db->getFieldFactory();
44
        $fields = $db->getScheme()->getTableFields($name);
45
46
        foreach ($fields as $info) {
47
            $field = $fieldFactory->get($this, $info);
48
49
            $this->fields[$field->getName()] = $field;
50
            $this->defaults[$field->getName()] = null;
51
        }
52
    }
53
54
    public function __debugInfo(): array
55
    {
56
        return [
57
            'name' => $this->name,
58
            'fields' => $this->fields,
59
        ];
60
    }
61
62
    public function __toString()
63
    {
64
        return "`{$this->name}`";
65
    }
66
67
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
68
    {
69
        $this->eventDispatcher = $eventDispatcher;
70
71
        return $this;
72
    }
73
74
    public function getEventDispatcher(): ?EventDispatcherInterface
75
    {
76
        return $this->eventDispatcher;
77
    }
78
79
    /**
80
     * Get the devault values used in new rows
81
     */
82
    public function getDefaults(array $overrides = null): array
83
    {
84
        if (empty($overrides)) {
85
            return $this->defaults;
86
        }
87
88
        $diff = array_diff_key($overrides, $this->fields);
89
90
        if (!empty($diff)) {
91
            throw new SimpleCrudException(
92
                sprintf('The field %s does not exist in the table %s', implode(array_keys($diff)), $this)
93
            );
94
        }
95
96
        return $overrides + $this->defaults;
97
    }
98
99
    /**
100
     * Store a row in the cache.
101
     */
102
    public function cache(Row $row): Row
103
    {
104
        if ($row->id) {
105
            $this->cache[$row->id] = $row;
106
        }
107
108
        return $row;
109
    }
110
111
    /**
112
     * Clear the current cache.
113
     */
114
    public function clearCache(): self
115
    {
116
        $this->cache = [];
117
118
        return $this;
119
    }
120
121
    /**
122
     * Returns whether the id is cached or not
123
     * @param mixed $id
124
     */
125
    public function isCached($id): bool
126
    {
127
        return array_key_exists($id, $this->cache);
128
    }
129
130
    /**
131
     * Returns a row from the cache.
132
     * @param mixed $id
133
     */
134
    public function getCached($id): ?Row
135
    {
136
        if (!$this->isCached($id)) {
137
            return null;
138
        }
139
140
        $row = $this->cache[$id];
141
142
        if ($row && !$row->id) {
143
            return $this->cache[$id] = null;
144
        }
145
146
        return $row;
147
    }
148
149
    public function delete(): Delete
150
    {
151
        $query = new Delete($this);
152
153
        if ($eventDispatcher = $this->getEventDispatcher()) {
154
            $eventDispatcher->dispatch(new CreateDeleteQuery($query));
0 ignored issues
show
Documentation introduced by
new \SimpleCrud\Events\CreateDeleteQuery($query) is of type object<SimpleCrud\Events\CreateDeleteQuery>, 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...
155
        }
156
157
        return $query;
158
    }
159
160
    public function insert(array $data = []): Insert
161
    {
162
        $query = new Insert($this, $data);
163
164
        if ($eventDispatcher = $this->getEventDispatcher()) {
165
            $eventDispatcher->dispatch(new CreateInsertQuery($query));
0 ignored issues
show
Documentation introduced by
new \SimpleCrud\Events\CreateInsertQuery($query) is of type object<SimpleCrud\Events\CreateInsertQuery>, 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...
166
        }
167
168
        return $query;
169
    }
170
171
    public function select(): Select
172
    {
173
        $query = new Select($this);
174
175
        if ($eventDispatcher = $this->getEventDispatcher()) {
176
            $eventDispatcher->dispatch(new CreateSelectQuery($query));
0 ignored issues
show
Documentation introduced by
new \SimpleCrud\Events\CreateSelectQuery($query) is of type object<SimpleCrud\Events\CreateSelectQuery>, 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...
177
        }
178
179
        return $query;
180
    }
181
182
    public function selectAggregate(string $function, string $field = 'id'): SelectAggregate
183
    {
184
        $query = new SelectAggregate($this, $function, $field);
185
186
        if ($eventDispatcher = $this->getEventDispatcher()) {
187
            $eventDispatcher->dispatch(new CreateSelectQuery($query));
0 ignored issues
show
Documentation introduced by
$query is of type object<SimpleCrud\Query\SelectAggregate>, but the function expects a object<SimpleCrud\Query\Select>.

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...
Documentation introduced by
new \SimpleCrud\Events\CreateSelectQuery($query) is of type object<SimpleCrud\Events\CreateSelectQuery>, 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...
188
        }
189
190
        return $query;
191
    }
192
193
    public function update(array $data = []): Update
194
    {
195
        $query = new Update($this, $data);
196
197
        if ($eventDispatcher = $this->getEventDispatcher()) {
198
            $eventDispatcher->dispatch(new CreateUpdateQuery($query));
0 ignored issues
show
Documentation introduced by
new \SimpleCrud\Events\CreateUpdateQuery($query) is of type object<SimpleCrud\Events\CreateUpdateQuery>, 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...
199
        }
200
201
        return $query;
202
    }
203
204
    /**
205
     * Magic method to get the Field instance of a table field
206
     */
207
    public function __get(string $name): FieldInterface
208
    {
209
        if (!isset($this->fields[$name])) {
210
            throw new SimpleCrudException(
211
                sprintf('The field `%s` does not exist in the table %s', $name, $this)
212
            );
213
        }
214
215
        return $this->fields[$name];
216
    }
217
218
    /**
219
     * Magic method to check if a field exists or not.
220
     */
221
    public function __isset(string $name): bool
222
    {
223
        return isset($this->fields[$name]);
224
    }
225
226
    /**
227
     * @see Countable
228
     */
229
    public function count(): int
230
    {
231
        return $this->selectAggregate('COUNT')->run();
232
    }
233
234
    /**
235
     * Check if a row with a specific id exists.
236
     *
237
     * @see ArrayAccess
238
     * @param mixed $offset
239
     */
240
    public function offsetExists($offset): bool
241
    {
242
        if ($this->isCached($offset)) {
243
            return $this->getCached($offset) !== null;
244
        }
245
246
        return $this->selectAggregate('COUNT')
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Query\SelectAggregate>? 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...
247
            ->where('id = ', $offset)
248
            ->limit(1)
249
            ->run() === 1;
250
    }
251
252
    /**
253
     * Returns a row with a specific id.
254
     *
255
     * @see ArrayAccess
256
     *
257
     * @param mixed $offset
258
     */
259
    public function offsetGet($offset): ?Row
260
    {
261
        if ($this->isCached($offset)) {
262
            return $this->getCached($offset);
263
        }
264
265
        return $this->cache[$offset] = $this->select()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Query\Select>? 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...
266
            ->one()
267
            ->where('id = ', $offset)
268
            ->run();
269
    }
270
271
    /**
272
     * Store a row with a specific id.
273
     *
274
     * @see ArrayAccess
275
     * @param mixed $offset
276
     * @param mixed $value
277
     */
278
    public function offsetSet($offset, $value): Row
279
    {
280
        //Insert on missing offset
281
        if ($offset === null) {
282
            $value['id'] = null;
283
284
            return $this->create($value)->save();
285
        }
286
287
        //Update if the element is cached and exists
288
        $row = $this->getCached($offset);
289
290
        if ($row) {
291
            return $row->edit($value)->save();
292
        }
293
294
        //Update if the element it's not cached
295
        if (!$this->isCached($row)) {
296
            $this->update()
0 ignored issues
show
Documentation Bug introduced by
The method columns does not exist on object<SimpleCrud\Query\Update>? 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...
297
                ->columns($value)
298
                ->where('id = ', $offset)
299
                ->run();
300
        }
301
    }
302
303
    /**
304
     * Remove a row with a specific id.
305
     *
306
     * @see ArrayAccess
307
     * @param mixed $offset
308
     */
309
    public function offsetUnset($offset)
310
    {
311
        $this->cache[$offset] = null;
312
313
        $this->delete()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Query\Delete>? 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...
314
            ->where('id = ', $offset)
315
            ->run();
316
    }
317
318
    /**
319
     * Returns the table name.
320
     */
321
    public function getName(): string
322
    {
323
        return $this->name;
324
    }
325
326
    /**
327
     * Returns the foreign key name.
328
     */
329
    public function getForeignKey(): string
330
    {
331
        return "{$this->name}_id";
332
    }
333
334
    /**
335
     * Returns the foreign key.
336
     */
337
    public function getJoinField(Table $table): ?FieldInterface
338
    {
339
        $field = $table->getForeignKey();
340
341
        return $this->fields[$field] ?? null;
342
    }
343
344
    public function getJoinTable(Table $table): ?Table
345
    {
346
        $name1 = $this->getName();
347
        $name2 = $table->getName();
348
        $name = $name1 < $name2 ? "{$name1}_{$name2}" : "{$name2}_{$name1}";
349
350
        $joinTable = $this->db->{$name} ?? null;
351
352
        if ($joinTable && $joinTable->getJoinField($this) && $joinTable->getJoinField($table)) {
353
            return $joinTable;
354
        }
355
356
        return null;
357
    }
358
359
    /**
360
     * Returns the Database instance associated with this table.
361
     */
362
    public function getDatabase(): Database
363
    {
364
        return $this->db;
365
    }
366
367
    /**
368
     * Returns all fields.
369
     *
370
     * @return FieldInterface[]
371
     */
372
    public function getFields()
373
    {
374
        return $this->fields;
375
    }
376
377
    public function create(array $data = []): Row
378
    {
379
        if (isset($data['id']) && ($row = $this->getCached($data['id']))) {
380
            return $row;
381
        }
382
383
        $eventDispatcher = $this->getEventDispatcher();
384
385
        if ($eventDispatcher) {
386
            $event = new BeforeCreateRow($data);
387
            $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...
388
            $data = $event->getData();
389
        }
390
391
        $class = self::ROW_CLASS;
392
        return $this->cache(new $class($this, $data));
393
    }
394
395
    public function createCollection(array $rows = []): RowCollection
396
    {
397
        $rows = array_map(
398
            function ($data): Row {
399
                return is_array($data) ? $this->create($data) : $data;
400
            },
401
            $rows
402
        );
403
404
        $class = self::ROWCOLLECTION_CLASS;
405
        return new $class($this, ...$rows);
406
    }
407
}
408