Completed
Branch feature/pre-split (d89158)
by Anton
04:43
created

AbstractTable::fetchReferences()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
nc 1
dl 0
loc 1
c 0
b 0
f 0
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\LoggerAwareInterface;
11
use Psr\Log\LoggerInterface;
12
use Spiral\Core\Component;
13
use Spiral\Database\Entities\AbstractHandler;
14
use Spiral\Database\Entities\Driver;
15
use Spiral\Database\Exceptions\HandlerException;
16
use Spiral\Database\Exceptions\SchemaException;
17
use Spiral\Database\Schemas\ColumnInterface;
18
use Spiral\Database\Schemas\IndexInterface;
19
use Spiral\Database\Schemas\ReferenceInterface;
20
use Spiral\Database\Schemas\StateComparator;
21
use Spiral\Database\Schemas\Syncronizer;
22
use Spiral\Database\Schemas\TableInterface;
23
use Spiral\Database\Schemas\TableState;
24
use Spiral\Debug\Traits\LoggerTrait;
25
26
/**
27
 * AbstractTable class used to describe and manage state of specified table. It provides ability to
28
 * get table introspection, update table schema and automatically generate set of diff operations.
29
 *
30
 * Most of table operation like column, index or foreign key creation/altering will be applied when
31
 * save() method will be called.
32
 *
33
 * Column configuration shortcuts:
34
 *
35
 * @method AbstractColumn primary($column)
36
 * @method AbstractColumn bigPrimary($column)
37
 * @method AbstractColumn enum($column, array $values)
38
 * @method AbstractColumn string($column, $length = 255)
39
 * @method AbstractColumn decimal($column, $precision, $scale)
40
 * @method AbstractColumn boolean($column)
41
 * @method AbstractColumn integer($column)
42
 * @method AbstractColumn tinyInteger($column)
43
 * @method AbstractColumn bigInteger($column)
44
 * @method AbstractColumn text($column)
45
 * @method AbstractColumn tinyText($column)
46
 * @method AbstractColumn longText($column)
47
 * @method AbstractColumn double($column)
48
 * @method AbstractColumn float($column)
49
 * @method AbstractColumn datetime($column)
50
 * @method AbstractColumn date($column)
51
 * @method AbstractColumn time($column)
52
 * @method AbstractColumn timestamp($column)
53
 * @method AbstractColumn binary($column)
54
 * @method AbstractColumn tinyBinary($column)
55
 * @method AbstractColumn longBinary($column)
56
 */
57
abstract class AbstractTable extends Component implements TableInterface, LoggerAwareInterface
58
{
59
    use LoggerTrait;
60
61
    /**
62
     * Table states.
63
     */
64
    const STATUS_NEW     = 0;
65
    const STATUS_EXISTS  = 1;
66
    const STATUS_DROPPED = 2;
67
68
    /**
69
     * Indication that table is exists and current schema is fetched from database.
70
     *
71
     * @var int
72
     */
73
    private $status = self::STATUS_NEW;
74
75
    /**
76
     * Database specific tablePrefix. Required for table renames.
77
     *
78
     * @var string
79
     */
80
    private $prefix = '';
81
82
    /**
83
     * @invisible
84
     *
85
     * @var Driver
86
     */
87
    protected $driver = null;
88
89
    /**
90
     * Initial table state.
91
     *
92
     * @invisible
93
     * @var TableState
94
     */
95
    protected $initial = null;
96
97
    /**
98
     * Currently defined table state.
99
     *
100
     * @invisible
101
     * @var TableState
102
     */
103
    protected $current = null;
104
105
    /**
106
     * @param Driver $driver Parent driver.
107
     * @param string $name   Table name, must include table prefix.
108
     * @param string $prefix Database specific table prefix.
109
     */
110
    public function __construct(Driver $driver, string $name, string $prefix)
111
    {
112
        $this->driver = $driver;
113
        $this->prefix = $prefix;
114
115
        //Initializing states
116
        $this->initial = new TableState($this->prefix . $name);
117
        $this->current = new TableState($this->prefix . $name);
118
119
        if ($this->driver->hasTable($this->getName())) {
120
            $this->status = self::STATUS_EXISTS;
121
        }
122
123
        if ($this->exists()) {
124
            //Initiating table schema
125
            $this->initSchema($this->initial);
126
        }
127
128
        $this->setStatus($this->initial);
129
    }
130
131
    /**
132
     * Get instance of associated driver.
133
     *
134
     * @return Driver
135
     */
136
    public function getDriver(): Driver
137
    {
138
        return $this->driver;
139
    }
140
141
    /**
142
     * @return StateComparator
143
     */
144
    public function getComparator(): StateComparator
145
    {
146
        return new StateComparator($this->initial, $this->current);
147
    }
148
149
    /**
150
     * Check if table schema has been modified since synchronization.
151
     *
152
     * @return bool
153
     */
154
    protected function hasChanges(): bool
155
    {
156
        return $this->getComparator()->hasChanges();
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function exists(): bool
163
    {
164
        return $this->status == self::STATUS_EXISTS || $this->status == self::STATUS_DROPPED;
165
    }
166
167
    /**
168
     * Table status (see codes above).
169
     *
170
     * @return int
171
     */
172
    public function getStatus(): int
173
    {
174
        return $this->status;
175
    }
176
177
    /**
178
     * Return database specific table prefix.
179
     *
180
     * @return string
181
     */
182
    public function getPrefix(): string
183
    {
184
        return $this->prefix;
185
    }
186
187
    /**
188
     * Sets table name. Use this function in combination with save to rename table.
189
     *
190
     * @param string $name
191
     *
192
     * @return string Prefixed table name.
193
     */
194
    public function setName(string $name): string
195
    {
196
        $this->current->setName($this->prefix . $name);
197
198
        return $this->getName();
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204
    public function getName(): string
205
    {
206
        return $this->current->getName();
207
    }
208
209
    /**
210
     * Table name before rename.
211
     *
212
     * @return string
213
     */
214
    public function getInitialName(): string
215
    {
216
        return $this->initial->getName();
217
    }
218
219
    /**
220
     * Declare table as dropped, you have to sync table using "save" method in order to apply this
221
     * change.
222
     */
223
    public function declareDropped()
224
    {
225
        if ($this->status == self::STATUS_NEW) {
226
            throw new SchemaException("Unable to drop non existed table");
227
        }
228
229
        //Declaring as dropper
230
        $this->status = self::STATUS_DROPPED;
231
    }
232
233
    /**
234
     * Set table primary keys. Operation can only be applied for newly created tables. Now every
235
     * database might support compound indexes.
236
     *
237
     * @param array $columns
238
     *
239
     * @return self
240
     */
241
    public function setPrimaryKeys(array $columns): AbstractTable
242
    {
243
        //Originally we were forcing an exception when primary key were changed, now we should
244
        //force it when table will be synced
245
246
        //Updating primary keys in current state
247
        $this->current->setPrimaryKeys($columns);
248
249
        return $this;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function getPrimaryKeys(): array
256
    {
257
        return $this->current->getPrimaryKeys();
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function hasColumn(string $name): bool
264
    {
265
        return $this->current->hasColumn($name);
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     *
271
     * @return ColumnInterface[]|AbstractColumn[]
272
     */
273
    public function getColumns(): array
274
    {
275
        return $this->current->getColumns();
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281
    public function hasIndex(array $columns = []): bool
282
    {
283
        return $this->current->hasIndex($columns);
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     *
289
     * @return IndexInterface[]|AbstractIndex[]
290
     */
291
    public function getIndexes(): array
292
    {
293
        return $this->current->getIndexes();
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function hasForeign(string $column): bool
300
    {
301
        return $this->current->hasForeign($column);
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     *
307
     * @return ReferenceInterface[]|AbstractReference[]
308
     */
309
    public function getForeigns(): array
310
    {
311
        return $this->current->getForeigns();
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function getDependencies(): array
318
    {
319
        $tables = [];
320
        foreach ($this->current->getForeigns() as $foreign) {
321
            $tables[] = $foreign->getForeignTable();
322
        }
323
324
        return $tables;
325
    }
326
327
    /**
328
     * Get/create instance of AbstractColumn associated with current table.
329
     *
330
     * Attention, renamed column will be available by it's old name until being synced!
331
     *
332
     * Examples:
333
     * $table->column('name')->string();
334
     *
335
     * @param string $name
336
     *
337
     * @return AbstractColumn
338
     */
339
    public function column(string $name): AbstractColumn
340
    {
341
        if ($this->current->hasColumn($name)) {
342
            //Column already exists
343
            return $this->current->findColumn($name);
344
        }
345
346
        $column = $this->createColumn($name);
347
        $this->current->registerColumn($column);
348
349
        return $column;
350
    }
351
352
    /**
353
     * Shortcut for column() method.
354
     *
355
     * @param string $column
356
     *
357
     * @return AbstractColumn
358
     */
359
    public function __get(string $column)
360
    {
361
        return $this->column($column);
362
    }
363
364
    /**
365
     * Column creation/altering shortcut, call chain is identical to:
366
     * AbstractTable->column($name)->$type($arguments).
367
     *
368
     * Example:
369
     * $table->string("name");
370
     * $table->text("some_column");
371
     *
372
     * @param string $type
373
     * @param array  $arguments Type specific parameters.
374
     *
375
     * @return AbstractColumn
376
     */
377
    public function __call(string $type, array $arguments)
378
    {
379
        return call_user_func_array(
380
            [$this->column($arguments[0]), $type],
381
            array_slice($arguments, 1)
382
        );
383
    }
384
385
    /**
386
     * Get/create instance of AbstractIndex associated with current table based on list of forming
387
     * column names.
388
     *
389
     * Example:
390
     * $table->index('key');
391
     * $table->index('key', 'key2');
392
     * $table->index(['key', 'key2']);
393
     *
394
     * @param mixed $columns Column name, or array of columns.
395
     *
396
     * @return AbstractIndex
397
     *
398
     * @throws SchemaException
399
     */
400
    public function index($columns): AbstractIndex
401
    {
402
        $columns = is_array($columns) ? $columns : func_get_args();
403
404
        foreach ($columns as $column) {
405
            if (!$this->hasColumn($column)) {
406
                throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
407
            }
408
        }
409
410
        if ($this->hasIndex($columns)) {
411
            return $this->current->findIndex($columns);
412
        }
413
414
        $index = $this->createIndex($this->createIdentifier('index', $columns));
415
        $index->columns($columns);
416
        $this->current->registerIndex($index);
417
418
        return $index;
419
    }
420
421
    /**
422
     * Get/create instance of AbstractReference associated with current table based on local column
423
     * name.
424
     *
425
     * @param string $column
426
     *
427
     * @return AbstractReference
428
     *
429
     * @throws SchemaException
430
     */
431
    public function foreign(string $column): AbstractReference
432
    {
433
        if (!$this->hasColumn($column)) {
434
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
435
        }
436
437
        if ($this->hasForeign($column)) {
438
            return $this->current->findForeign($column);
439
        }
440
441
        $foreign = $this->createForeign($this->createIdentifier('foreign', [$column]));
442
        $foreign->column($column);
443
444
        $this->current->registerReference($foreign);
445
446
        //Let's ensure index existence to performance and compatibility reasons
447
        $this->index($column);
448
449
        return $foreign;
450
    }
451
452
    /**
453
     * Rename column (only if column exists).
454
     *
455
     * @param string $column
456
     * @param string $name New column name.
457
     *
458
     * @return self
459
     *
460
     * @throws SchemaException
461
     */
462
    public function renameColumn(string $column, string $name): AbstractTable
463
    {
464
        if (!$this->hasColumn($column)) {
465
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
466
        }
467
468
        //Rename operation is simple about declaring new name
469
        $this->column($column)->setName($name);
470
471
        return $this;
472
    }
473
474
    /**
475
     * Rename index (only if index exists).
476
     *
477
     * @param array  $columns Index forming columns.
478
     * @param string $name    New index name.
479
     *
480
     * @return self
481
     *
482
     * @throws SchemaException
483
     */
484
    public function renameIndex(array $columns, string $name): AbstractTable
485
    {
486
        if ($this->hasIndex($columns)) {
487
            throw new SchemaException(
488
                "Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'"
489
            );
490
        }
491
492
        //Declaring new index name
493
        $this->index($columns)->setName($name);
494
495
        return $this;
496
    }
497
498
    /**
499
     * Drop column by it's name.
500
     *
501
     * @param string $column
502
     *
503
     * @return self
504
     *
505
     * @throws SchemaException
506
     */
507 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...
508
    {
509
        if (empty($schema = $this->current->findColumn($column))) {
510
            throw new SchemaException("Undefined column '{$column}' in '{$this->getName()}'");
511
        }
512
513
        //Dropping column from current schema
514
        $this->current->forgetColumn($schema);
515
516
        return $this;
517
    }
518
519
    /**
520
     * Drop index by it's forming columns.
521
     *
522
     * @param array $columns
523
     *
524
     * @return self
525
     *
526
     * @throws SchemaException
527
     */
528
    public function dropIndex(array $columns): AbstractTable
529
    {
530
        if (empty($schema = $this->current->findIndex($columns))) {
531
            throw new SchemaException(
532
                "Undefined index ['" . join("', '", $columns) . "'] in '{$this->getName()}'"
533
            );
534
        }
535
536
        //Dropping index from current schema
537
        $this->current->forgetIndex($schema);
538
539
        return $this;
540
    }
541
542
    /**
543
     * Drop foreign key by it's name.
544
     *
545
     * @param string $column
546
     *
547
     * @return self
548
     *
549
     * @throws SchemaException
550
     */
551 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...
552
    {
553
        if (!empty($schema = $this->current->findForeign($column))) {
554
            throw new SchemaException(
555
                "Undefined FK on '{$column}' in '{$this->getName()}'"
556
            );
557
        }
558
559
        //Dropping foreign from current schema
560
        $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...
561
562
        return $this;
563
    }
564
565
    /**
566
     * Reset table state to new form.
567
     *
568
     * @param TableState $status Use null to flush table schema.
569
     *
570
     * @return self|$this
571
     */
572
    public function setStatus(TableState $status = null): AbstractTable
573
    {
574
        $this->current = new TableState($this->initial->getName());
575
576
        if (!empty($status)) {
577
            $this->current->setName($status->getName());
578
            $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...
579
        }
580
581
        return $this;
582
    }
583
584
    /**
585
     * Reset table state to it initial form.
586
     *
587
     * @return self|$this
588
     */
589
    public function resetState(): AbstractTable
590
    {
591
        $this->setStatus($this->initial);
592
593
        return $this;
594
    }
595
596
    /**
597
     * Save table schema including every column, index, foreign key creation/altering. If table
598
     * does not exist it must be created. If table declared as dropped it will be removed from
599
     * the database.
600
     *
601
     * @param int             $behaviour Operation to be performed while table being saved. In some
602
     *                                   cases (when multiple tables are being updated) it is
603
     *                                   reasonable to drop foreing keys and indexes prior to
604
     *                                   dropping related columns. See sync bus class to get more
605
     *                                   details.
606
     * @param LoggerInterface $logger    Optional, aggregates messages for data syncing.
607
     *
608
     * @throws HandlerException
609
     */
610
    public function save(int $behaviour = AbstractHandler::DO_ALL, LoggerInterface $logger = null)
611
    {
612
        //We need an instance of Handler of dbal operations
613
        $handler = $this->driver->getHandler($logger);
614
615
        if ($this->status == self::STATUS_DROPPED) {
616
            //We don't need syncer for this operation
617
            $handler->dropTable($this);
618
619
            //Flushing status
620
            $this->status = self::STATUS_NEW;
621
622
            return;
623
        }
624
625
        //Ensure that columns references to valid indexes and et
626
        $prepared = $this->prepareSchema();
627
628
        if ($this->status == self::STATUS_NEW) {
629
            //Executing table creation
630
            $handler->createTable($prepared);
631
            $this->status = self::STATUS_EXISTS;
632
        } else {
633
            //Executing table syncing
634
            if ($this->hasChanges()) {
635
                $handler->syncTable($prepared, $behaviour);
636
            }
637
638
            $prepared->status = self::STATUS_EXISTS;
639
        }
640
641
        //Syncing our schemas
642
        $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...
643
    }
644
645
    /**
646
     * Ensure that no wrong indexes left in table.
647
     *
648
     * @return AbstractTable
649
     */
650
    protected function prepareSchema()
651
    {
652
        //To make sure that no pre-sync modifications will be reflected on current table
653
        $target = clone $this;
654
655
        /*
656
         * In cases where columns are removed we have to automatically remove related indexes and
657
         * foreign keys.
658
         */
659
        foreach ($this->getComparator()->droppedColumns() as $column) {
660
            foreach ($target->getIndexes() as $index) {
661
                if (in_array($column->getName(), $index->getColumns())) {
662
                    $target->current->forgetIndex($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...
663
                }
664
            }
665
666
            foreach ($target->getForeigns() as $foreign) {
667
                if ($column->getName() == $foreign->getColumn()) {
668
                    $target->current->forgetForeign($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...
669
                }
670
            }
671
        }
672
673
        //We also have to adjusts indexes and foreign keys
674
        foreach ($this->getComparator()->alteredColumns() as $pair) {
675
            /**
676
             * @var AbstractColumn $initial
677
             * @var AbstractColumn $name
678
             */
679
            list($name, $initial) = $pair;
680
681
            foreach ($target->getIndexes() as $index) {
682
                if (in_array($initial->getName(), $index->getColumns())) {
683
                    $columns = $index->getColumns();
684
685
                    //Replacing column name
686
                    foreach ($columns as &$column) {
687
                        if ($column == $initial->getName()) {
688
                            $column = $name->getName();
689
                        }
690
691
                        unset($column);
692
                    }
693
694
                    $index->columns($columns);
0 ignored issues
show
Bug introduced by
The method columns() does not exist on Spiral\Database\Schemas\IndexInterface. Did you maybe mean getColumns()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
695
                }
696
            }
697
698
            foreach ($target->getForeigns() as $foreign) {
699
                if ($initial->getName() == $foreign->getColumn()) {
700
                    $foreign->column($name->getName());
0 ignored issues
show
Bug introduced by
The method column() does not exist on Spiral\Database\Schemas\ReferenceInterface. Did you maybe mean getColumn()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
701
                }
702
            }
703
        }
704
705
        return $target;
706
    }
707
708
    /**
709
     * @return AbstractColumn|string
710
     */
711
    public function __toString(): string
712
    {
713
        return $this->getName();
714
    }
715
716
    /**
717
     * Cloning schemas as well.
718
     */
719
    public function __clone()
720
    {
721
        $this->initial = clone $this->initial;
722
        $this->current = clone $this->current;
723
    }
724
725
    /**
726
     * @return array
727
     */
728
    public function __debugInfo()
729
    {
730
        return [
731
            'name'        => $this->getName(),
732
            'primaryKeys' => $this->getPrimaryKeys(),
733
            'columns'     => array_values($this->getColumns()),
734
            'indexes'     => array_values($this->getIndexes()),
735
            'references'  => array_values($this->getForeigns()),
736
        ];
737
    }
738
739
    /**
740
     * Populate table schema with values from database.
741
     *
742
     * @param TableState $state
743
     */
744
    protected function initSchema(TableState $state)
745
    {
746
        foreach ($this->fetchColumns() as $column) {
747
            $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...
748
        }
749
750
        foreach ($this->fetchIndexes() as $index) {
751
            $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...
752
        }
753
754
        foreach ($this->fetchReferences() as $foreign) {
755
            $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...
756
        }
757
758
        $state->setPrimaryKeys($this->fetchPrimaryKeys());
759
760
        //DBMS specific initialization can be placed here
761
    }
762
763
    /**
764
     * Fetch index declarations from database.
765
     *
766
     * @return ColumnInterface[]
767
     */
768
    abstract protected function fetchColumns(): array;
769
770
    /**
771
     * Fetch index declarations from database.
772
     *
773
     * @return IndexInterface[]
774
     */
775
    abstract protected function fetchIndexes(): array;
776
777
    /**
778
     * Fetch references declaration from database.
779
     *
780
     * @return ReferenceInterface[]
781
     */
782
    abstract protected function fetchReferences(): array;
783
784
    /**
785
     * Fetch names of primary keys from table.
786
     *
787
     * @return array
788
     */
789
    abstract protected function fetchPrimaryKeys(): array;
790
791
    /**
792
     * Create column with a given name.
793
     *
794
     * @param string $name
795
     *
796
     * @return AbstractColumn
797
     */
798
    abstract protected function createColumn(string $name): AbstractColumn;
799
800
    /**
801
     * Create index for a given set of columns.
802
     *
803
     * @param string $name
804
     *
805
     * @return AbstractIndex
806
     */
807
    abstract protected function createIndex(string $name): AbstractIndex;
808
809
    /**
810
     * Create reference on a given column set.
811
     *
812
     * @param string $name
813
     *
814
     * @return AbstractReference
815
     */
816
    abstract protected function createForeign(string $name): AbstractReference;
817
818
    /**
819
     * Generate unique name for indexes and foreign keys.
820
     *
821
     * @param string $type
822
     * @param array  $columns
823
     *
824
     * @return string
825
     */
826
    protected function createIdentifier(string $type, array $columns): string
827
    {
828
        $name = $this->getName() . '_' . $type . '_' . join('_', $columns) . '_' . uniqid();
829
830
        if (strlen($name) > 64) {
831
            //Many DBMS has limitations on identifier length
832
            $name = md5($name);
833
        }
834
835
        return $name;
836
    }
837
838
    /**
839
     * @return ContainerInterface
840
     */
841
    protected function iocContainer()
842
    {
843
        //Falling back to driver specific container
844
        return $this->driver->iocContainer();
0 ignored issues
show
Bug introduced by
The method iocContainer() cannot be called from this context as it is declared protected in class Spiral\Core\Component.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
845
    }
846
}