Completed
Branch feature/pre-split (5ee41a)
by Anton
20:46
created

AbstractTable   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 779
Duplicated Lines 3.08 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 24
loc 779
rs 5
c 1
b 0
f 0
wmc 76
lcom 2
cbo 8

46 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 3
A getDriver() 0 4 1
A getComparator() 0 4 1
A hasChanges() 0 4 1
A exists() 0 4 2
A getStatus() 0 4 1
A getPrefix() 0 4 1
A setName() 0 6 1
A getName() 0 4 1
A getInitialName() 0 4 1
A declareDropped() 0 9 2
A setPrimaryKeys() 0 10 1
A getPrimaryKeys() 0 4 1
A hasColumn() 0 4 1
A getColumns() 0 4 1
A hasIndex() 0 4 1
A getIndexes() 0 4 1
A hasForeign() 0 4 1
A getForeigns() 0 4 1
A getDependencies() 0 9 2
A column() 0 12 2
A __get() 0 4 1
A __call() 0 7 1
B index() 0 20 5
A foreign() 0 20 3
A renameColumn() 0 11 2
A renameIndex() 0 13 2
A dropColumn() 11 11 2
A dropIndex() 0 13 2
A dropForeign() 13 13 2
A setStatus() 0 11 2
A resetState() 0 6 1
B save() 0 34 4
C prepareSchema() 0 57 13
A __toString() 0 4 1
A __clone() 0 5 1
A __debugInfo() 0 10 1
A initSchema() 0 18 4
fetchColumns() 0 1 ?
fetchIndexes() 0 1 ?
fetchReferences() 0 1 ?
fetchPrimaryKeys() 0 1 ?
createColumn() 0 1 ?
createIndex() 0 1 ?
createForeign() 0 1 ?
A createIdentifier() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractTable 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 AbstractTable, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Database\Schemas\Prototypes;
8
9
use Interop\Container\ContainerInterface;
10
use Psr\Log\LoggerInterface;
11
use Spiral\Database\Entities\AbstractHandler;
12
use Spiral\Database\Entities\Driver;
13
use Spiral\Database\Exceptions\HandlerException;
14
use Spiral\Database\Exceptions\SchemaException;
15
use Spiral\Database\Schemas\ColumnInterface;
16
use Spiral\Database\Schemas\IndexInterface;
17
use Spiral\Database\Schemas\ReferenceInterface;
18
use Spiral\Database\Schemas\StateComparator;
19
use Spiral\Database\Schemas\TableInterface;
20
use Spiral\Database\Schemas\TableState;
21
22
/**
23
 * AbstractTable class used to describe and manage state of specified table. It provides ability to
24
 * get table introspection, update table schema and automatically generate set of diff operations.
25
 *
26
 * Most of table operation like column, index or foreign key creation/altering will be applied when
27
 * save() method will be called.
28
 *
29
 * Column configuration shortcuts:
30
 *
31
 * @method AbstractColumn primary($column)
32
 * @method AbstractColumn bigPrimary($column)
33
 * @method AbstractColumn enum($column, array $values)
34
 * @method AbstractColumn string($column, $length = 255)
35
 * @method AbstractColumn decimal($column, $precision, $scale)
36
 * @method AbstractColumn boolean($column)
37
 * @method AbstractColumn integer($column)
38
 * @method AbstractColumn tinyInteger($column)
39
 * @method AbstractColumn bigInteger($column)
40
 * @method AbstractColumn text($column)
41
 * @method AbstractColumn tinyText($column)
42
 * @method AbstractColumn longText($column)
43
 * @method AbstractColumn double($column)
44
 * @method AbstractColumn float($column)
45
 * @method AbstractColumn datetime($column)
46
 * @method AbstractColumn date($column)
47
 * @method AbstractColumn time($column)
48
 * @method AbstractColumn timestamp($column)
49
 * @method AbstractColumn binary($column)
50
 * @method AbstractColumn tinyBinary($column)
51
 * @method AbstractColumn longBinary($column)
52
 */
53
abstract class AbstractTable implements TableInterface
54
{
55
    /**
56
     * Table states.
57
     */
58
    const STATUS_NEW     = 0;
59
    const STATUS_EXISTS  = 1;
60
    const STATUS_DROPPED = 2;
61
62
    /**
63
     * Indication that table is exists and current schema is fetched from database.
64
     *
65
     * @var int
66
     */
67
    private $status = self::STATUS_NEW;
68
69
    /**
70
     * Database specific tablePrefix. Required for table renames.
71
     *
72
     * @var string
73
     */
74
    private $prefix = '';
75
76
    /**
77
     * @invisible
78
     *
79
     * @var Driver
80
     */
81
    protected $driver = null;
82
83
    /**
84
     * Initial table state.
85
     *
86
     * @invisible
87
     * @var TableState
88
     */
89
    protected $initial = null;
90
91
    /**
92
     * Currently defined table state.
93
     *
94
     * @invisible
95
     * @var TableState
96
     */
97
    protected $current = null;
98
99
    /**
100
     * @param Driver $driver Parent driver.
101
     * @param string $name   Table name, must include table prefix.
102
     * @param string $prefix Database specific table prefix.
103
     */
104
    public function __construct(Driver $driver, string $name, string $prefix)
105
    {
106
        $this->driver = $driver;
107
        $this->prefix = $prefix;
108
109
        //Initializing states
110
        $this->initial = new TableState($this->prefix . $name);
111
        $this->current = new TableState($this->prefix . $name);
112
113
        if ($this->driver->hasTable($this->getName())) {
114
            $this->status = self::STATUS_EXISTS;
115
        }
116
117
        if ($this->exists()) {
118
            //Initiating table schema
119
            $this->initSchema($this->initial);
120
        }
121
122
        $this->setStatus($this->initial);
123
    }
124
125
    /**
126
     * Get instance of associated driver.
127
     *
128
     * @return Driver
129
     */
130
    public function getDriver(): Driver
131
    {
132
        return $this->driver;
133
    }
134
135
    /**
136
     * @return StateComparator
137
     */
138
    public function getComparator(): StateComparator
139
    {
140
        return new StateComparator($this->initial, $this->current);
141
    }
142
143
    /**
144
     * Check if table schema has been modified since synchronization.
145
     *
146
     * @return bool
147
     */
148
    protected function hasChanges(): bool
149
    {
150
        return $this->getComparator()->hasChanges();
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function exists(): bool
157
    {
158
        return $this->status == self::STATUS_EXISTS || $this->status == self::STATUS_DROPPED;
159
    }
160
161
    /**
162
     * Table status (see codes above).
163
     *
164
     * @return int
165
     */
166
    public function getStatus(): int
167
    {
168
        return $this->status;
169
    }
170
171
    /**
172
     * Return database specific table prefix.
173
     *
174
     * @return string
175
     */
176
    public function getPrefix(): string
177
    {
178
        return $this->prefix;
179
    }
180
181
    /**
182
     * Sets table name. Use this function in combination with save to rename table.
183
     *
184
     * @param string $name
185
     *
186
     * @return string Prefixed table name.
187
     */
188
    public function setName(string $name): string
189
    {
190
        $this->current->setName($this->prefix . $name);
191
192
        return $this->getName();
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function getName(): string
199
    {
200
        return $this->current->getName();
201
    }
202
203
    /**
204
     * Table name before rename.
205
     *
206
     * @return string
207
     */
208
    public function getInitialName(): string
209
    {
210
        return $this->initial->getName();
211
    }
212
213
    /**
214
     * Declare table as dropped, you have to sync table using "save" method in order to apply this
215
     * change.
216
     */
217
    public function declareDropped()
218
    {
219
        if ($this->status == self::STATUS_NEW) {
220
            throw new SchemaException("Unable to drop non existed table");
221
        }
222
223
        //Declaring as dropper
224
        $this->status = self::STATUS_DROPPED;
225
    }
226
227
    /**
228
     * Set table primary keys. Operation can only be applied for newly created tables. Now every
229
     * database might support compound indexes.
230
     *
231
     * @param array $columns
232
     *
233
     * @return self
234
     */
235
    public function setPrimaryKeys(array $columns): AbstractTable
236
    {
237
        //Originally i were forcing an exception when primary key were changed, now we should
238
        //force it when table will be synced
239
240
        //Updating primary keys in current state
241
        $this->current->setPrimaryKeys($columns);
242
243
        return $this;
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function getPrimaryKeys(): array
250
    {
251
        return $this->current->getPrimaryKeys();
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function hasColumn(string $name): bool
258
    {
259
        return $this->current->hasColumn($name);
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     *
265
     * @return AbstractColumn[]
266
     */
267
    public function getColumns(): array
268
    {
269
        return $this->current->getColumns();
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    public function hasIndex(array $columns = []): bool
276
    {
277
        return $this->current->hasIndex($columns);
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     *
283
     * @return AbstractIndex[]
284
     */
285
    public function getIndexes(): array
286
    {
287
        return $this->current->getIndexes();
288
    }
289
290
    /**
291
     * {@inheritdoc}
292
     */
293
    public function hasForeign(string $column): bool
294
    {
295
        return $this->current->hasForeign($column);
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     *
301
     * @return AbstractReference[]
302
     */
303
    public function getForeigns(): array
304
    {
305
        return $this->current->getForeigns();
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311
    public function getDependencies(): array
312
    {
313
        $tables = [];
314
        foreach ($this->current->getForeigns() as $foreign) {
315
            $tables[] = $foreign->getForeignTable();
316
        }
317
318
        return $tables;
319
    }
320
321
    /**
322
     * Get/create instance of AbstractColumn associated with current table.
323
     *
324
     * Attention, renamed column will be available by it's old name until being synced!
325
     *
326
     * Examples:
327
     * $table->column('name')->string();
328
     *
329
     * @param string $name
330
     *
331
     * @return AbstractColumn
332
     */
333
    public function column(string $name): AbstractColumn
334
    {
335
        if ($this->current->hasColumn($name)) {
336
            //Column already exists
337
            return $this->current->findColumn($name);
338
        }
339
340
        $column = $this->createColumn($name);
341
        $this->current->registerColumn($column);
342
343
        return $column;
344
    }
345
346
    /**
347
     * Shortcut for column() method.
348
     *
349
     * @param string $column
350
     *
351
     * @return AbstractColumn
352
     */
353
    public function __get(string $column)
354
    {
355
        return $this->column($column);
356
    }
357
358
    /**
359
     * Column creation/altering shortcut, call chain is identical to:
360
     * AbstractTable->column($name)->$type($arguments).
361
     *
362
     * Example:
363
     * $table->string("name");
364
     * $table->text("some_column");
365
     *
366
     * @param string $type
367
     * @param array  $arguments Type specific parameters.
368
     *
369
     * @return AbstractColumn
370
     */
371
    public function __call(string $type, array $arguments)
372
    {
373
        return call_user_func_array(
374
            [$this->column($arguments[0]), $type],
375
            array_slice($arguments, 1)
376
        );
377
    }
378
379
    /**
380
     * Get/create instance of AbstractIndex associated with current table based on list of forming
381
     * column names.
382
     *
383
     * Example:
384
     * $table->index('key');
385
     * $table->index('key', 'key2');
386
     * $table->index(['key', 'key2']);
387
     *
388
     * @param mixed $columns Column name, or array of columns.
389
     *
390
     * @return AbstractIndex
391
     *
392
     * @throws SchemaException
393
     */
394
    public function index($columns): AbstractIndex
395
    {
396
        $columns = is_array($columns) ? $columns : func_get_args();
397
398
        foreach ($columns as $column) {
399
            if (!$this->hasColumn($column)) {
400
                throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
401
            }
402
        }
403
404
        if ($this->hasIndex($columns)) {
405
            return $this->current->findIndex($columns);
406
        }
407
408
        $index = $this->createIndex($this->createIdentifier('index', $columns));
409
        $index->columns($columns);
410
        $this->current->registerIndex($index);
411
412
        return $index;
413
    }
414
415
    /**
416
     * Get/create instance of AbstractReference associated with current table based on local column
417
     * name.
418
     *
419
     * @param string $column
420
     *
421
     * @return AbstractReference
422
     *
423
     * @throws SchemaException
424
     */
425
    public function foreign(string $column): AbstractReference
426
    {
427
        if (!$this->hasColumn($column)) {
428
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
429
        }
430
431
        if ($this->hasForeign($column)) {
432
            return $this->current->findForeign($column);
433
        }
434
435
        $foreign = $this->createForeign($this->createIdentifier('foreign', [$column]));
436
        $foreign->column($column);
437
438
        $this->current->registerReference($foreign);
439
440
        //Let's ensure index existence to performance and compatibility reasons
441
        $this->index($column);
442
443
        return $foreign;
444
    }
445
446
    /**
447
     * Rename column (only if column exists).
448
     *
449
     * @param string $column
450
     * @param string $name New column name.
451
     *
452
     * @return self
453
     *
454
     * @throws SchemaException
455
     */
456
    public function renameColumn(string $column, string $name): AbstractTable
457
    {
458
        if (!$this->hasColumn($column)) {
459
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
460
        }
461
462
        //Rename operation is simple about declaring new name
463
        $this->column($column)->setName($name);
464
465
        return $this;
466
    }
467
468
    /**
469
     * Rename index (only if index exists).
470
     *
471
     * @param array  $columns Index forming columns.
472
     * @param string $name    New index name.
473
     *
474
     * @return self
475
     *
476
     * @throws SchemaException
477
     */
478
    public function renameIndex(array $columns, string $name): AbstractTable
479
    {
480
        if ($this->hasIndex($columns)) {
481
            throw new SchemaException(
482
                "Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'"
483
            );
484
        }
485
486
        //Declaring new index name
487
        $this->index($columns)->setName($name);
488
489
        return $this;
490
    }
491
492
    /**
493
     * Drop column by it's name.
494
     *
495
     * @param string $column
496
     *
497
     * @return self
498
     *
499
     * @throws SchemaException
500
     */
501 View Code Duplication
    public function dropColumn(string $column): AbstractTable
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
502
    {
503
        if (empty($schema = $this->current->findColumn($column))) {
504
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
505
        }
506
507
        //Dropping column from current schema
508
        $this->current->forgetColumn($schema);
509
510
        return $this;
511
    }
512
513
    /**
514
     * Drop index by it's forming columns.
515
     *
516
     * @param array $columns
517
     *
518
     * @return self
519
     *
520
     * @throws SchemaException
521
     */
522
    public function dropIndex(array $columns): AbstractTable
523
    {
524
        if (empty($schema = $this->current->findIndex($columns))) {
525
            throw new SchemaException(
526
                "Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'"
527
            );
528
        }
529
530
        //Dropping index from current schema
531
        $this->current->forgetIndex($schema);
532
533
        return $this;
534
    }
535
536
    /**
537
     * Drop foreign key by it's name.
538
     *
539
     * @param string $column
540
     *
541
     * @return self
542
     *
543
     * @throws SchemaException
544
     */
545 View Code Duplication
    public function dropForeign($column): AbstractTable
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
546
    {
547
        if (!empty($schema = $this->current->findForeign($column))) {
548
            throw new SchemaException(
549
                "Undefined FK on '{$column}' in '{$this->getName()}'"
550
            );
551
        }
552
553
        //Dropping foreign from current schema
554
        $this->current->forgetForeign($schema);
0 ignored issues
show
Documentation introduced by
$schema is of type null, but the function expects a object<Spiral\Database\S...ypes\AbstractReference>.

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...
555
556
        return $this;
557
    }
558
559
    /**
560
     * Reset table state to new form.
561
     *
562
     * @param TableState $status Use null to flush table schema.
563
     *
564
     * @return self|$this
565
     */
566
    public function setStatus(TableState $status = null): AbstractTable
567
    {
568
        $this->current = new TableState($this->initial->getName());
569
570
        if (!empty($status)) {
571
            $this->current->setName($status->getName());
572
            $this->current->syncState($status);
0 ignored issues
show
Documentation introduced by
$status is of type object<Spiral\Database\Schemas\TableState>, but the function expects a object<self>.

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...
573
        }
574
575
        return $this;
576
    }
577
578
    /**
579
     * Reset table state to it initial form.
580
     *
581
     * @return self|$this
582
     */
583
    public function resetState(): AbstractTable
584
    {
585
        $this->setStatus($this->initial);
586
587
        return $this;
588
    }
589
590
    /**
591
     * Save table schema including every column, index, foreign key creation/altering. If table
592
     * does not exist it must be created. If table declared as dropped it will be removed from
593
     * the database.
594
     *
595
     * @param int             $behaviour Operation to be performed while table being saved. In some
596
     *                                   cases (when multiple tables are being updated) it is
597
     *                                   reasonable to drop foreing keys and indexes prior to
598
     *                                   dropping related columns. See sync bus class to get more
599
     *                                   details.
600
     * @param LoggerInterface $logger    Optional, aggregates messages for data syncing.
601
     *
602
     * @throws HandlerException
603
     */
604
    public function save(int $behaviour = AbstractHandler::DO_ALL, LoggerInterface $logger = null)
605
    {
606
        //We need an instance of Handler of dbal operations
607
        $handler = $this->driver->getHandler($logger);
608
609
        if ($this->status == self::STATUS_DROPPED) {
610
            //We don't need syncer for this operation
611
            $handler->dropTable($this);
612
613
            //Flushing status
614
            $this->status = self::STATUS_NEW;
615
616
            return;
617
        }
618
619
        //Ensure that columns references to valid indexes and et
620
        $prepared = $this->prepareSchema();
621
622
        if ($this->status == self::STATUS_NEW) {
623
            //Executing table creation
624
            $handler->createTable($prepared);
625
            $this->status = self::STATUS_EXISTS;
626
        } else {
627
            //Executing table syncing
628
            if ($this->hasChanges()) {
629
                $handler->syncTable($prepared, $behaviour);
630
            }
631
632
            $prepared->status = self::STATUS_EXISTS;
633
        }
634
635
        //Syncing our schemas
636
        $this->initial->syncState($prepared->current);
0 ignored issues
show
Documentation introduced by
$prepared->current is of type object<Spiral\Database\Schemas\TableState>, but the function expects a object<self>.

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...
637
    }
638
639
    /**
640
     * Ensure that no wrong indexes left in table.
641
     *
642
     * @return AbstractTable
643
     */
644
    protected function prepareSchema()
645
    {
646
        //To make sure that no pre-sync modifications will be reflected on current table
647
        $target = clone $this;
648
649
        /*
650
         * In cases where columns are removed we have to automatically remove related indexes and
651
         * foreign keys.
652
         */
653
        foreach ($this->getComparator()->droppedColumns() as $column) {
654
            foreach ($target->getIndexes() as $index) {
655
                if (in_array($column->getName(), $index->getColumns())) {
656
                    $target->current->forgetIndex($index);
657
                }
658
            }
659
660
            foreach ($target->getForeigns() as $foreign) {
661
                if ($column->getName() == $foreign->getColumn()) {
662
                    $target->current->forgetForeign($foreign);
663
                }
664
            }
665
        }
666
667
        //We also have to adjusts indexes and foreign keys
668
        foreach ($this->getComparator()->alteredColumns() as $pair) {
669
            /**
670
             * @var AbstractColumn $initial
671
             * @var AbstractColumn $name
672
             */
673
            list($name, $initial) = $pair;
674
675
            foreach ($target->getIndexes() as $index) {
676
                if (in_array($initial->getName(), $index->getColumns())) {
677
                    $columns = $index->getColumns();
678
679
                    //Replacing column name
680
                    foreach ($columns as &$column) {
681
                        if ($column == $initial->getName()) {
682
                            $column = $name->getName();
683
                        }
684
685
                        unset($column);
686
                    }
687
688
                    $index->columns($columns);
689
                }
690
            }
691
692
            foreach ($target->getForeigns() as $foreign) {
693
                if ($initial->getName() == $foreign->getColumn()) {
694
                    $foreign->column($name->getName());
695
                }
696
            }
697
        }
698
699
        return $target;
700
    }
701
702
    /**
703
     * @return AbstractColumn|string
704
     */
705
    public function __toString(): string
706
    {
707
        return $this->getName();
708
    }
709
710
    /**
711
     * Cloning schemas as well.
712
     */
713
    public function __clone()
714
    {
715
        $this->initial = clone $this->initial;
716
        $this->current = clone $this->current;
717
    }
718
719
    /**
720
     * @return array
721
     */
722
    public function __debugInfo()
723
    {
724
        return [
725
            'name'        => $this->getName(),
726
            'primaryKeys' => $this->getPrimaryKeys(),
727
            'columns'     => array_values($this->getColumns()),
728
            'indexes'     => array_values($this->getIndexes()),
729
            'references'  => array_values($this->getForeigns()),
730
        ];
731
    }
732
733
    /**
734
     * Populate table schema with values from database.
735
     *
736
     * @param TableState $state
737
     */
738
    protected function initSchema(TableState $state)
739
    {
740
        foreach ($this->fetchColumns() as $column) {
741
            $state->registerColumn($column);
0 ignored issues
show
Compatibility introduced by
$column of type object<Spiral\Database\Schemas\ColumnInterface> is not a sub-type of object<Spiral\Database\S...totypes\AbstractColumn>. It seems like you assume a concrete implementation of the interface Spiral\Database\Schemas\ColumnInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
742
        }
743
744
        foreach ($this->fetchIndexes() as $index) {
745
            $state->registerIndex($index);
0 ignored issues
show
Compatibility introduced by
$index of type object<Spiral\Database\Schemas\IndexInterface> is not a sub-type of object<Spiral\Database\S...ototypes\AbstractIndex>. It seems like you assume a concrete implementation of the interface Spiral\Database\Schemas\IndexInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
746
        }
747
748
        foreach ($this->fetchReferences() as $foreign) {
749
            $state->registerReference($foreign);
0 ignored issues
show
Compatibility introduced by
$foreign of type object<Spiral\Database\S...mas\ReferenceInterface> is not a sub-type of object<Spiral\Database\S...ypes\AbstractReference>. It seems like you assume a concrete implementation of the interface Spiral\Database\Schemas\ReferenceInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
750
        }
751
752
        $state->setPrimaryKeys($this->fetchPrimaryKeys());
753
754
        //DBMS specific initialization can be placed here
755
    }
756
757
    /**
758
     * Fetch index declarations from database.
759
     *
760
     * @return ColumnInterface[]
761
     */
762
    abstract protected function fetchColumns(): array;
763
764
    /**
765
     * Fetch index declarations from database.
766
     *
767
     * @return IndexInterface[]
768
     */
769
    abstract protected function fetchIndexes(): array;
770
771
    /**
772
     * Fetch references declaration from database.
773
     *
774
     * @return ReferenceInterface[]
775
     */
776
    abstract protected function fetchReferences(): array;
777
778
    /**
779
     * Fetch names of primary keys from table.
780
     *
781
     * @return array
782
     */
783
    abstract protected function fetchPrimaryKeys(): array;
784
785
    /**
786
     * Create column with a given name.
787
     *
788
     * @param string $name
789
     *
790
     * @return AbstractColumn
791
     */
792
    abstract protected function createColumn(string $name): AbstractColumn;
793
794
    /**
795
     * Create index for a given set of columns.
796
     *
797
     * @param string $name
798
     *
799
     * @return AbstractIndex
800
     */
801
    abstract protected function createIndex(string $name): AbstractIndex;
802
803
    /**
804
     * Create reference on a given column set.
805
     *
806
     * @param string $name
807
     *
808
     * @return AbstractReference
809
     */
810
    abstract protected function createForeign(string $name): AbstractReference;
811
812
    /**
813
     * Generate unique name for indexes and foreign keys.
814
     *
815
     * @param string $type
816
     * @param array  $columns
817
     *
818
     * @return string
819
     */
820
    protected function createIdentifier(string $type, array $columns): string
821
    {
822
        $name = $this->getName() . '_' . $type . '_' . join('_', $columns) . '_' . uniqid();
823
824
        if (strlen($name) > 64) {
825
            //Many DBMS has limitations on identifier length
826
            $name = md5($name);
827
        }
828
829
        return $name;
830
    }
831
}