Completed
Push — master ( 4fb1d6...a24b8d )
by Oscar
01:43
created

Table   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 16

Importance

Changes 0
Metric Value
wmc 58
lcom 3
cbo 16
dl 0
loc 387
rs 4.5599
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 2
A __debugInfo() 0 7 1
A __toString() 0 4 1
A setEventDispatcher() 0 6 1
A getEventDispatcher() 0 4 1
A getDefaults() 0 16 3
A cache() 0 8 2
A clearCache() 0 6 1
A isCached() 0 4 1
A getCached() 0 14 4
A delete() 0 10 2
A insert() 0 10 2
A select() 0 10 2
A selectAggregate() 0 10 2
A update() 0 10 2
A __get() 0 10 2
A __isset() 0 4 1
A count() 0 4 1
A offsetExists() 0 11 2
A offsetGet() 0 11 2
A offsetSet() 0 24 4
A offsetUnset() 0 8 1
A getName() 0 4 1
A getForeignKey() 0 4 1
A getJoinField() 0 6 1
A getJoinTable() 0 14 5
A getDatabase() 0 4 1
A getFields() 0 4 1
A create() 0 17 4
A createCollection() 0 5 1
A createField() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

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\Field;
15
use SimpleCrud\Queries\Delete;
16
use SimpleCrud\Queries\Insert;
17
use SimpleCrud\Queries\Select;
18
use SimpleCrud\Queries\SelectAggregate;
19
use SimpleCrud\Queries\Update;
20
21
/**
22
 * Manages a database table.
23
 *
24
 * @property Field $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
    private $fieldFactories;
35
36
    protected const ROW_CLASS = Row::class;
37
    protected const ROWCOLLECTION_CLASS = RowCollection::class;
38
39
    final public function __construct(Database $db, string $name)
40
    {
41
        $this->db = $db;
42
        $this->name = $name;
43
44
        $this->fieldFactories = $db->getFieldFactories();
45
        $fields = $db->getScheme()->getTableFields($name);
46
47
        foreach ($fields as $info) {
48
            $field = $this->createField($info);
49
50
            $this->fields[$field->getName()] = $field;
51
            $this->defaults[$field->getName()] = null;
52
        }
53
    }
54
55
    public function __debugInfo(): array
56
    {
57
        return [
58
            'name' => $this->name,
59
            'fields' => $this->fields,
60
        ];
61
    }
62
63
    public function __toString()
64
    {
65
        return "`{$this->name}`";
66
    }
67
68
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
69
    {
70
        $this->eventDispatcher = $eventDispatcher;
71
72
        return $this;
73
    }
74
75
    public function getEventDispatcher(): ?EventDispatcherInterface
76
    {
77
        return $this->eventDispatcher;
78
    }
79
80
    /**
81
     * Get the devault values used in new rows
82
     */
83
    public function getDefaults(array $overrides = null): array
84
    {
85
        if (empty($overrides)) {
86
            return $this->defaults;
87
        }
88
89
        $diff = array_diff_key($overrides, $this->fields);
90
91
        if (!empty($diff)) {
92
            throw new SimpleCrudException(
93
                sprintf('The field %s does not exist in the table %s', implode(array_keys($diff)), $this)
94
            );
95
        }
96
97
        return $overrides + $this->defaults;
98
    }
99
100
    /**
101
     * Store a row in the cache.
102
     */
103
    public function cache(Row $row): Row
104
    {
105
        if ($row->id) {
106
            $this->cache[$row->id] = $row;
107
        }
108
109
        return $row;
110
    }
111
112
    /**
113
     * Clear the current cache.
114
     */
115
    public function clearCache(): self
116
    {
117
        $this->cache = [];
118
119
        return $this;
120
    }
121
122
    /**
123
     * Returns whether the id is cached or not
124
     * @param mixed $id
125
     */
126
    public function isCached($id): bool
127
    {
128
        return array_key_exists($id, $this->cache);
129
    }
130
131
    /**
132
     * Returns a row from the cache.
133
     * @param mixed $id
134
     */
135
    public function getCached($id): ?Row
136
    {
137
        if (!$this->isCached($id)) {
138
            return null;
139
        }
140
141
        $row = $this->cache[$id];
142
143
        if ($row && !$row->id) {
144
            return $this->cache[$id] = null;
145
        }
146
147
        return $row;
148
    }
149
150
    public function delete(): Delete
151
    {
152
        $query = new Delete($this);
153
154
        if ($eventDispatcher = $this->getEventDispatcher()) {
155
            $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...
156
        }
157
158
        return $query;
159
    }
160
161
    public function insert(array $data = []): Insert
162
    {
163
        $query = new Insert($this, $data);
164
165
        if ($eventDispatcher = $this->getEventDispatcher()) {
166
            $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...
167
        }
168
169
        return $query;
170
    }
171
172
    public function select(): Select
173
    {
174
        $query = new Select($this);
175
176
        if ($eventDispatcher = $this->getEventDispatcher()) {
177
            $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...
178
        }
179
180
        return $query;
181
    }
182
183
    public function selectAggregate(string $function, string $field = 'id'): SelectAggregate
184
    {
185
        $query = new SelectAggregate($this, $function, $field);
186
187
        if ($eventDispatcher = $this->getEventDispatcher()) {
188
            $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...
189
        }
190
191
        return $query;
192
    }
193
194
    public function update(array $data = []): Update
195
    {
196
        $query = new Update($this, $data);
197
198
        if ($eventDispatcher = $this->getEventDispatcher()) {
199
            $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...
200
        }
201
202
        return $query;
203
    }
204
205
    /**
206
     * Magic method to get the Field instance of a table field
207
     */
208
    public function __get(string $name): Field
209
    {
210
        if (!isset($this->fields[$name])) {
211
            throw new SimpleCrudException(
212
                sprintf('The field `%s` does not exist in the table %s', $name, $this)
213
            );
214
        }
215
216
        return $this->fields[$name];
217
    }
218
219
    /**
220
     * Magic method to check if a field exists or not.
221
     */
222
    public function __isset(string $name): bool
223
    {
224
        return isset($this->fields[$name]);
225
    }
226
227
    /**
228
     * @see Countable
229
     */
230
    public function count(): int
231
    {
232
        return $this->selectAggregate('COUNT')->run();
233
    }
234
235
    /**
236
     * Check if a row with a specific id exists.
237
     *
238
     * @see ArrayAccess
239
     * @param mixed $offset
240
     */
241
    public function offsetExists($offset): bool
242
    {
243
        if ($this->isCached($offset)) {
244
            return $this->getCached($offset) !== null;
245
        }
246
247
        return $this->selectAggregate('COUNT')
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Queries\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...
248
            ->where('id = ', $offset)
249
            ->limit(1)
250
            ->run() === 1;
251
    }
252
253
    /**
254
     * Returns a row with a specific id.
255
     *
256
     * @see ArrayAccess
257
     *
258
     * @param mixed $offset
259
     */
260
    public function offsetGet($offset): ?Row
261
    {
262
        if ($this->isCached($offset)) {
263
            return $this->getCached($offset);
264
        }
265
266
        return $this->cache[$offset] = $this->select()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Queries\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...
267
            ->one()
268
            ->where('id = ', $offset)
269
            ->run();
270
    }
271
272
    /**
273
     * Store a row with a specific id.
274
     *
275
     * @see ArrayAccess
276
     * @param mixed $offset
277
     * @param mixed $value
278
     */
279
    public function offsetSet($offset, $value): Row
280
    {
281
        //Insert on missing offset
282
        if ($offset === null) {
283
            $value['id'] = null;
284
285
            return $this->create($value)->save();
286
        }
287
288
        //Update if the element is cached and exists
289
        $row = $this->getCached($offset);
290
291
        if ($row) {
292
            return $row->edit($value)->save();
293
        }
294
295
        //Update if the element it's not cached
296
        if (!$this->isCached($row)) {
297
            $this->update()
0 ignored issues
show
Documentation Bug introduced by
The method columns does not exist on object<SimpleCrud\Queries\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...
298
                ->columns($value)
299
                ->where('id = ', $offset)
300
                ->run();
301
        }
302
    }
303
304
    /**
305
     * Remove a row with a specific id.
306
     *
307
     * @see ArrayAccess
308
     * @param mixed $offset
309
     */
310
    public function offsetUnset($offset)
311
    {
312
        $this->cache[$offset] = null;
313
314
        $this->delete()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<SimpleCrud\Queries\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...
315
            ->where('id = ', $offset)
316
            ->run();
317
    }
318
319
    /**
320
     * Returns the table name.
321
     */
322
    public function getName(): string
323
    {
324
        return $this->name;
325
    }
326
327
    /**
328
     * Returns the foreign key name.
329
     */
330
    public function getForeignKey(): string
331
    {
332
        return "{$this->name}_id";
333
    }
334
335
    /**
336
     * Returns the foreign key.
337
     */
338
    public function getJoinField(Table $table): ?Field
339
    {
340
        $field = $table->getForeignKey();
341
342
        return $this->fields[$field] ?? null;
343
    }
344
345
    public function getJoinTable(Table $table): ?Table
346
    {
347
        $name1 = $this->getName();
348
        $name2 = $table->getName();
349
        $name = $name1 < $name2 ? "{$name1}_{$name2}" : "{$name2}_{$name1}";
350
351
        $joinTable = $this->db->{$name} ?? null;
352
353
        if ($joinTable && $joinTable->getJoinField($this) && $joinTable->getJoinField($table)) {
354
            return $joinTable;
355
        }
356
357
        return null;
358
    }
359
360
    /**
361
     * Returns the Database instance associated with this table.
362
     */
363
    public function getDatabase(): Database
364
    {
365
        return $this->db;
366
    }
367
368
    /**
369
     * Returns all fields.
370
     *
371
     * @return Field[]
372
     */
373
    public function getFields()
374
    {
375
        return $this->fields;
376
    }
377
378
    public function create(array $data = []): Row
379
    {
380
        if (isset($data['id']) && ($row = $this->getCached($data['id']))) {
381
            return $row;
382
        }
383
384
        $eventDispatcher = $this->getEventDispatcher();
385
386
        if ($eventDispatcher) {
387
            $event = new BeforeCreateRow($data);
388
            $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...
389
            $data = $event->getData();
390
        }
391
392
        $class = self::ROW_CLASS;
393
        return $this->cache(new $class($this, $data));
394
    }
395
396
    public function createCollection(array $rows = []): RowCollection
397
    {
398
        $class = self::ROWCOLLECTION_CLASS;
399
        return new $class($this, ...$rows);
400
    }
401
402
    public function createField(array $info): Field
403
    {
404
        foreach ($this->fieldFactories as $fieldFactory) {
405
            if ($field = $fieldFactory->create($this, $info)) {
406
                return $field;
407
            }
408
        }
409
410
        return new Field($this, $info);
411
    }
412
}
413