MigrationTrait   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 831
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 65
lcom 1
cbo 10
dl 0
loc 831
ccs 274
cts 274
cp 1
rs 2.969
c 0
b 0
f 0

45 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 9 1
A getContainer() 0 4 1
A getConnection() 0 6 1
A getModelSchemas() 0 6 1
A getSchemaManager() 0 4 1
A createTable() 0 18 2
A dropTableIfExists() 0 13 2
A createEnum() 0 27 4
A dropEnumIfExists() 0 11 2
A useEnum() 0 22 4
A primaryInt() 0 7 1
A primaryString() 0 8 1
A int() 0 7 2
A nullableInt() 0 6 1
A unsignedInt() 0 4 1
A nullableUnsignedInt() 0 4 1
A float() 0 8 1
A string() 0 7 1
A nullableString() 0 7 1
A text() 0 6 1
A nullableText() 0 6 1
A bool() 0 9 2
A binary() 0 6 1
A enum() 0 6 1
A nullableEnum() 0 6 1
A timestamps() 0 26 5
A datetime() 0 6 1
A nullableDatetime() 0 6 1
A date() 0 6 1
A nullableDate() 0 6 1
A unique() 0 6 1
A searchable() 0 6 1
A foreignRelationship() 0 31 2
A nullableForeignRelationship() 0 25 2
A foreignColumn() 0 18 1
A nullableForeignColumn() 0 18 1
A nullableRelationship() 0 6 1
A relationship() 0 6 1
A getTableNameForClass() 0 11 1
A defaultValue() 0 7 1
A nullableValue() 0 7 1
A notNullableValue() 0 7 1
A unsignedIntImpl() 0 7 2
A foreignColumnImpl() 0 28 2
A relationshipImpl() 0 47 2

How to fix   Complexity   

Complex Class

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

1
<?php declare (strict_types = 1);
2
3
namespace Limoncello\Data\Migrations;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Closure;
22
use Doctrine\DBAL\Connection;
23
use Doctrine\DBAL\DBALException;
24
use Doctrine\DBAL\Schema\AbstractSchemaManager;
25
use Doctrine\DBAL\Schema\Table;
26
use Doctrine\DBAL\Types\Type;
27
use Limoncello\Contracts\Data\MigrationInterface;
28
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
29
use Limoncello\Contracts\Data\RelationshipTypes;
30
use Limoncello\Contracts\Data\TimestampFields;
31
use Limoncello\Data\Contracts\MigrationContextInterface;
32
use Psr\Container\ContainerInterface;
33
use function array_key_exists;
34
use function assert;
35
use function call_user_func;
36
use function implode;
37
use function is_string;
38
39
/**
40
 * @package Limoncello\Data
41
 */
42
trait MigrationTrait
43
{
44
    /**
45
     * @var ContainerInterface
46
     */
47
    private $container;
48
49
    /**
50
     * @var array
51
     */
52
    private $enumerations = [];
53
54
    /**
55
     * @inheritdoc
56
     */
57 6
    public function init(ContainerInterface $container): MigrationInterface
58
    {
59 6
        $this->container = $container;
60
61
        /** @var MigrationInterface $self */
62 6
        $self = $this;
63
64 6
        return $self;
65
    }
66
67
    /**
68
     * @return ContainerInterface
69
     */
70 6
    protected function getContainer(): ContainerInterface
71
    {
72 6
        return $this->container;
73
    }
74
75
    /**
76
     * @return Connection
77
     */
78 6
    protected function getConnection(): Connection
79
    {
80 6
        assert($this->getContainer()->has(Connection::class) === true);
81
82 6
        return $this->getContainer()->get(Connection::class);
83
    }
84
85
    /**
86
     * @return ModelSchemaInfoInterface
87
     */
88 2
    protected function getModelSchemas(): ModelSchemaInfoInterface
89
    {
90 2
        assert($this->getContainer()->has(ModelSchemaInfoInterface::class) === true);
91
92 2
        return $this->getContainer()->get(ModelSchemaInfoInterface::class);
93
    }
94
95
    /**
96
     * @return AbstractSchemaManager
97
     */
98 2
    protected function getSchemaManager(): AbstractSchemaManager
99
    {
100 2
        return $this->getConnection()->getSchemaManager();
101
    }
102
103
    /**
104
     * @param string    $modelClass
105
     * @param Closure[] $expressions
106
     *
107
     * @return Table
108
     *
109
     * @throws DBALException
110
     */
111 2
    protected function createTable(string $modelClass, array $expressions = []): Table
112
    {
113 2
        $context = new MigrationContext($modelClass, $this->getModelSchemas());
114 2
        assert(
115 2
            $this->getModelSchemas()->hasClass($modelClass),
116 2
            "Class `$modelClass` is not found in model Schemas."
117
        );
118 2
        $tableName = $this->getModelSchemas()->getTable($modelClass);
119 2
        $table     = new Table($tableName);
120 2
        foreach ($expressions as $expression) {
121
            /** @var Closure $expression */
122 2
            $expression($table, $context);
123
        }
124
125 2
        $this->getSchemaManager()->dropAndCreateTable($table);
126
127 2
        return $table;
128
    }
129
130
    /**
131
     * @param string $modelClass
132
     *
133
     * @return void
134
     */
135 1
    protected function dropTableIfExists(string $modelClass): void
136
    {
137 1
        assert(
138 1
            $this->getModelSchemas()->hasClass($modelClass),
139 1
            "Class `$modelClass` is not found in model Schemas."
140
        );
141 1
        $tableName     = $this->getModelSchemas()->getTable($modelClass);
142 1
        $schemaManager = $this->getSchemaManager();
143
144 1
        if ($schemaManager->tablesExist([$tableName]) === true) {
145 1
            $schemaManager->dropTable($tableName);
146
        }
147
    }
148
149
    /**
150
     * @param string $name
151
     * @param array  $values
152
     *
153
     * @return void
154
     *
155
     * @throws DBALException
156
     */
157 2
    protected function createEnum(string $name, array $values): void
158
    {
159 2
        assert(empty($name) === false);
160
161
        // check all values are strings
162 2
        assert(
163
            call_user_func(function () use ($values): bool {
164 2
                $allAreStrings = true;
165 2
                foreach ($values as $value) {
166 2
                    $allAreStrings = $allAreStrings && is_string($value);
167
                }
168
169 2
                return $allAreStrings;
170 2
            }) === true,
171 2
            'All enum values should be strings.'
172
        );
173
174 2
        assert(array_key_exists($name, $this->enumerations) === false, "Enum name `$name` has already been used.");
175 2
        $this->enumerations[$name] = $values;
176
177 2
        $connection = $this->getConnection();
178 2
        if ($connection->getDriver()->getName() === 'pdo_pgsql') {
179 1
            $valueList = implode("', '", $values);
180 1
            $sql       = "CREATE TYPE $name AS ENUM ('$valueList');";
181 1
            $connection->exec($sql);
182
        }
183
    }
184
185
    /**
186
     * @param string $name
187
     *
188
     * @return void
189
     *
190
     * @throws DBALException
191
     */
192 1
    protected function dropEnumIfExists(string $name): void
193
    {
194 1
        unset($this->enumerations[$name]);
195
196 1
        $connection = $this->getConnection();
197 1
        if ($connection->getDriver()->getName() === 'pdo_pgsql') {
198 1
            $name = $connection->quoteIdentifier($name);
199 1
            $sql  = "DROP TYPE IF EXISTS $name;";
200 1
            $connection->exec($sql);
201
        }
202
    }
203
204
    /**
205
     * @param string $columnName
206
     * @param string $enumName
207
     * @param bool   $notNullable
208
     *
209
     * @return Closure
210
     *
211
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
212
     * @SuppressWarnings(PHPMD.ElseExpression)
213
     * @SuppressWarnings(PHPMD.StaticAccess)
214
     */
215 2
    protected function useEnum(string $columnName, string $enumName, bool $notNullable = true): Closure
216
    {
217 2
        if ($this->getConnection()->getDriver()->getName() === 'pdo_pgsql') {
218
            return function (Table $table) use ($columnName, $enumName): void {
219 1
                $typeName = RawNameType::TYPE_NAME;
220 1
                Type::hasType($typeName) === true ?: Type::addType($typeName, RawNameType::class);
221
                $table
222 1
                    ->addColumn($columnName, $typeName)
223 1
                    ->setCustomSchemaOption($typeName, $enumName);
224 1
            };
225
        } else {
226 1
            $enumValues = $this->enumerations[$enumName];
227
228
            return function (Table $table) use ($columnName, $enumValues, $notNullable) {
229 1
                Type::hasType(EnumType::TYPE_NAME) === true ?: Type::addType(EnumType::TYPE_NAME, EnumType::class);
230
                $table
231 1
                    ->addColumn($columnName, EnumType::TYPE_NAME)
232 1
                    ->setCustomSchemaOption(EnumType::TYPE_NAME, $enumValues)
233 1
                    ->setNotnull($notNullable);
234 1
            };
235
        }
236
    }
237
238
    /**
239
     * @param string $name
240
     *
241
     * @return Closure
242
     */
243 2
    protected function primaryInt(string $name): Closure
244
    {
245
        return function (Table $table) use ($name) {
246 2
            $table->addColumn($name, Type::INTEGER)->setAutoincrement(true)->setUnsigned(true)->setNotnull(true);
247 2
            $table->setPrimaryKey([$name]);
248 2
        };
249
    }
250
251
    /**
252
     * @param string $name
253
     *
254
     * @return Closure
255
     */
256 1
    protected function primaryString(string $name): Closure
257
    {
258
        return function (Table $table, MigrationContextInterface $context) use ($name) {
259 1
            $length = $context->getModelSchemas()->getAttributeLength($context->getModelClass(), $name);
260 1
            $table->addColumn($name, Type::STRING)->setLength($length)->setNotnull(true);
261 1
            $table->setPrimaryKey([$name]);
262 1
        };
263
    }
264
265
    /**
266
     * @param string   $name
267
     * @param null|int $default
268
     *
269
     * @return Closure
270
     */
271 1
    protected function int(string $name, int $default = null): Closure
272
    {
273
        return function (Table $table) use ($name, $default) {
274 1
            $column = $table->addColumn($name, Type::INTEGER)->setUnsigned(false)->setNotnull(true);
275 1
            $default === null ?: $column->setDefault($default);
276 1
        };
277
    }
278
279
    /**
280
     * @param string   $name
281
     * @param null|int $default
282
     *
283
     * @return Closure
284
     */
285 1
    protected function nullableInt(string $name, int $default = null): Closure
286
    {
287
        return function (Table $table) use ($name, $default) {
288 1
            $table->addColumn($name, Type::INTEGER)->setUnsigned(false)->setNotnull(false)->setDefault($default);
289 1
        };
290
    }
291
292
    /**
293
     * @param string   $name
294
     * @param null|int $default
295
     *
296
     * @return Closure
297
     */
298 1
    protected function unsignedInt(string $name, int $default = null): Closure
299
    {
300 1
        return $this->unsignedIntImpl($name, true, $default);
301
    }
302
303
    /**
304
     * @param string   $name
305
     * @param null|int $default
306
     *
307
     * @return Closure
308
     */
309 1
    protected function nullableUnsignedInt(string $name, int $default = null): Closure
310
    {
311 1
        return $this->unsignedIntImpl($name, false, $default);
312
    }
313
314
    /**
315
     * @param string $name
316
     *
317
     * @return Closure
318
     */
319 1
    protected function float(string $name): Closure
320
    {
321
        // precision and scale both seems to be ignored in Doctrine so not much sense to have them as inputs
322
323
        return function (Table $table) use ($name) {
324 1
            $table->addColumn($name, Type::FLOAT)->setNotnull(true);
325 1
        };
326
    }
327
328
    /**
329
     * @param string $name
330
     *
331
     * @return Closure
332
     */
333 1
    protected function string(string $name): Closure
334
    {
335
        return function (Table $table, MigrationContextInterface $context) use ($name) {
336 1
            $length = $context->getModelSchemas()->getAttributeLength($context->getModelClass(), $name);
337 1
            $table->addColumn($name, Type::STRING)->setLength($length)->setNotnull(true);
338 1
        };
339
    }
340
341
    /**
342
     * @param string $name
343
     *
344
     * @return Closure
345
     */
346 1
    protected function nullableString(string $name): Closure
347
    {
348
        return function (Table $table, MigrationContextInterface $context) use ($name) {
349 1
            $length = $context->getModelSchemas()->getAttributeLength($context->getModelClass(), $name);
350 1
            $table->addColumn($name, Type::STRING)->setLength($length)->setNotnull(false);
351 1
        };
352
    }
353
354
    /**
355
     * @param string $name
356
     *
357
     * @return Closure
358
     */
359 1
    protected function text(string $name): Closure
360
    {
361
        return function (Table $table) use ($name) {
362 1
            $table->addColumn($name, Type::TEXT)->setNotnull(true);
363 1
        };
364
    }
365
366
    /**
367
     * @param string $name
368
     *
369
     * @return Closure
370
     */
371 1
    protected function nullableText(string $name): Closure
372
    {
373
        return function (Table $table) use ($name) {
374 1
            $table->addColumn($name, Type::TEXT)->setNotnull(false);
375 1
        };
376
    }
377
378
    /**
379
     * @param string    $name
380
     * @param null|bool $default
381
     *
382
     * @return Closure
383
     */
384 1
    protected function bool(string $name, bool $default = null): Closure
385
    {
386
        return function (Table $table) use ($name, $default) {
387 1
            $column = $table->addColumn($name, Type::BOOLEAN)->setNotnull(true);
388 1
            if ($default !== null) {
389 1
                $column->setDefault($default);
390
            }
391 1
        };
392
    }
393
394
    /**
395
     * @param string $name
396
     *
397
     * @return Closure
398
     */
399 1
    protected function binary(string $name): Closure
400
    {
401
        return function (Table $table) use ($name) {
402 1
            $table->addColumn($name, Type::BINARY)->setNotnull(true);
403 1
        };
404
    }
405
406
    /**
407
     * @param string $name
408
     * @param array  $values
409
     *
410
     * @return Closure
411
     *
412
     * @throws DBALException
413
     */
414 1
    protected function enum(string $name, array $values): Closure
415
    {
416 1
        $this->createEnum($name, $values);
417
418 1
        return $this->useEnum($name, $name, true);
419
    }
420
421
    /**
422
     * @param string $name
423
     * @param array  $values
424
     *
425
     * @return Closure
426
     *
427
     * @throws DBALException
428
     */
429 1
    protected function nullableEnum(string $name, array $values): Closure
430
    {
431 1
        $this->createEnum($name, $values);
432
433 1
        return $this->useEnum($name, $name, false);
434
    }
435
436
    /**
437
     * @return Closure
438
     */
439 1
    protected function timestamps(): Closure
440
    {
441
        return function (Table $table, MigrationContextInterface $context) {
442 1
            $modelClass = $context->getModelClass();
443
444 1
            $createdAt = TimestampFields::FIELD_CREATED_AT;
445 1
            $updatedAt = TimestampFields::FIELD_UPDATED_AT;
446 1
            $deletedAt = TimestampFields::FIELD_DELETED_AT;
447
448
            // a list of data columns and `nullable` flag
449 1
            $datesToAdd = [];
450 1
            if ($this->getModelSchemas()->hasAttributeType($modelClass, $createdAt) === true) {
451 1
                $datesToAdd[$createdAt] = true;
452
            }
453 1
            if ($this->getModelSchemas()->hasAttributeType($modelClass, $updatedAt) === true) {
454 1
                $datesToAdd[$updatedAt] = false;
455
            }
456 1
            if ($this->getModelSchemas()->hasAttributeType($modelClass, $deletedAt) === true) {
457 1
                $datesToAdd[$deletedAt] = false;
458
            }
459
460 1
            foreach ($datesToAdd as $column => $isNullable) {
461 1
                $table->addColumn($column, Type::DATETIME)->setNotnull($isNullable);
462
            }
463 1
        };
464
    }
465
466
    /**
467
     * @param string $name
468
     *
469
     * @return Closure
470
     */
471 1
    protected function datetime(string $name): Closure
472
    {
473
        return function (Table $table) use ($name) {
474 1
            $table->addColumn($name, Type::DATETIME)->setNotnull(true);
475 1
        };
476
    }
477
478
    /**
479
     * @param string $name
480
     *
481
     * @return Closure
482
     */
483 1
    protected function nullableDatetime(string $name): Closure
484
    {
485
        return function (Table $table) use ($name) {
486 1
            $table->addColumn($name, Type::DATETIME)->setNotnull(false);
487 1
        };
488
    }
489
490
    /**
491
     * @param string $name
492
     *
493
     * @return Closure
494
     */
495 1
    protected function date(string $name): Closure
496
    {
497
        return function (Table $table) use ($name) {
498 1
            $table->addColumn($name, Type::DATE)->setNotnull(true);
499 1
        };
500
    }
501
502
    /**
503
     * @param string $name
504
     *
505
     * @return Closure
506
     */
507 1
    protected function nullableDate(string $name): Closure
508
    {
509
        return function (Table $table) use ($name) {
510 1
            $table->addColumn($name, Type::DATE)->setNotnull(false);
511 1
        };
512
    }
513
514
    /**
515
     * @param string[] $names
516
     *
517
     * @return Closure
518
     */
519 1
    protected function unique(array $names): Closure
520
    {
521
        return function (Table $table) use ($names) {
522 1
            $table->addUniqueIndex($names);
523 1
        };
524
    }
525
526
    /**
527
     * @param string[] $names
528
     *
529
     * @return Closure
530
     */
531 1
    protected function searchable(array $names): Closure
532
    {
533
        return function (Table $table) use ($names) {
534 1
            $table->addIndex($names, null, ['fulltext']);
535 1
        };
536
    }
537
538
    /**
539
     * @param string $column
540
     * @param string $referredClass
541
     * @param string $onDeleteRestriction
542
     *
543
     * @return Closure
544
     *
545
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
546
     */
547 1
    protected function foreignRelationship(
548
        string $column,
549
        string $referredClass,
550
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
551
    ): Closure {
552
        return function (
553
            Table $table,
554
            MigrationContextInterface $context
555
        ) use (
556 1
            $column,
557 1
            $referredClass,
558 1
            $onDeleteRestriction
559
        ) {
560 1
            $tableName    = $this->getTableNameForClass($referredClass);
561 1
            $pkName       = $this->getModelSchemas()->getPrimaryKey($referredClass);
562 1
            $columnType   = $this->getModelSchemas()->getAttributeType($context->getModelClass(), $column);
563 1
            $columnLength = $columnType === Type::STRING ?
564 1
                $this->getModelSchemas()->getAttributeLength($context->getModelClass(), $column) : null;
565
566 1
            $closure = $this->foreignColumn(
567 1
                $column,
568 1
                $tableName,
569 1
                $pkName,
570 1
                $columnType,
571 1
                $columnLength,
572 1
                $onDeleteRestriction
573
            );
574
575 1
            return $closure($table, $context);
576 1
        };
577
    }
578
579
    /**
580
     * @param string $column
581
     * @param string $referredClass
582
     * @param string $onDeleteRestriction
583
     *
584
     * @return Closure
585
     *
586
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
587
     */
588 1
    protected function nullableForeignRelationship(
589
        string $column,
590
        string $referredClass,
591
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
592
    ): Closure {
593
        return function (
594
            Table $table,
595
            MigrationContextInterface $context
596
        ) use (
597 1
            $column,
598 1
            $referredClass,
599 1
            $onDeleteRestriction
600
        ) {
601 1
            $tableName    = $this->getTableNameForClass($referredClass);
602 1
            $pkName       = $this->getModelSchemas()->getPrimaryKey($referredClass);
603 1
            $columnType   = $this->getModelSchemas()->getAttributeType($context->getModelClass(), $column);
604 1
            $columnLength = $columnType === Type::STRING ?
605 1
                $this->getModelSchemas()->getAttributeLength($context->getModelClass(), $column) : null;
606
607
            $closure = $this
608 1
                ->nullableForeignColumn($column, $tableName, $pkName, $columnType, $columnLength, $onDeleteRestriction);
609
610 1
            return $closure($table, $context);
611 1
        };
612
    }
613
614
    /** @noinspection PhpTooManyParametersInspection
615
     * @param string   $localKey
616
     * @param string   $foreignTable
617
     * @param string   $foreignKey
618
     * @param string   $type
619
     * @param int|null $length
620
     * @param string   $onDeleteRestriction
621
     *
622
     * @return Closure
623
     *
624
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
625
     */
626 1
    protected function foreignColumn(
627
        string $localKey,
628
        string $foreignTable,
629
        string $foreignKey,
630
        string $type,
631
        ?int $length = null,
632
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
633
    ): Closure {
634 1
        return $this->foreignColumnImpl(
635 1
            $localKey,
636 1
            $foreignTable,
637 1
            $foreignKey,
638 1
            $type,
639 1
            $length,
640 1
            true,
641 1
            $onDeleteRestriction
642
        );
643
    }
644
645
    /** @noinspection PhpTooManyParametersInspection
646
     * @param string   $localKey
647
     * @param string   $foreignTable
648
     * @param string   $foreignKey
649
     * @param string   $type
650
     * @param int|null $length
651
     * @param string   $onDeleteRestriction
652
     *
653
     * @return Closure
654
     *
655
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
656
     */
657 1
    protected function nullableForeignColumn(
658
        string $localKey,
659
        string $foreignTable,
660
        string $foreignKey,
661
        string $type,
662
        ?int $length = null,
663
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
664
    ): Closure {
665 1
        return $this->foreignColumnImpl(
666 1
            $localKey,
667 1
            $foreignTable,
668 1
            $foreignKey,
669 1
            $type,
670 1
            $length,
671 1
            false,
672 1
            $onDeleteRestriction
673
        );
674
    }
675
676
    /**
677
     * @param string $name
678
     * @param string $onDeleteRestriction
679
     *
680
     * @return Closure
681
     *
682
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
683
     */
684 1
    protected function nullableRelationship(
685
        string $name,
686
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
687
    ): Closure {
688 1
        return $this->relationshipImpl($name, false, $onDeleteRestriction);
689
    }
690
691
    /**
692
     * @param string $name
693
     * @param string $onDeleteRestriction
694
     *
695
     * @return Closure
696
     *
697
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
698
     */
699 1
    protected function relationship(
700
        string $name,
701
        string $onDeleteRestriction = RelationshipRestrictions::RESTRICT
702
    ): Closure {
703 1
        return $this->relationshipImpl($name, true, $onDeleteRestriction);
704
    }
705
706
    /**
707
     * @param string $modelClass
708
     *
709
     * @return string
710
     */
711 1
    protected function getTableNameForClass(string $modelClass): string
712
    {
713 1
        assert(
714 1
            $this->getModelSchemas()->hasClass($modelClass),
715 1
            "Class `$modelClass` is not found in model Schemas."
716
        );
717
718 1
        $tableName = $this->getModelSchemas()->getTable($modelClass);
719
720 1
        return $tableName;
721
    }
722
723
    /**
724
     * @param string $name
725
     * @param mixed  $value
726
     *
727
     * @return Closure
728
     */
729 1
    protected function defaultValue(string $name, $value): Closure
730
    {
731
        return function (Table $table) use ($name, $value) {
732 1
            assert($table->hasColumn($name));
733 1
            $table->getColumn($name)->setDefault($value);
734 1
        };
735
    }
736
737
    /**
738
     * @param string $name
739
     *
740
     * @return Closure
741
     */
742 1
    protected function nullableValue(string $name): Closure
743
    {
744
        return function (Table $table) use ($name) {
745 1
            assert($table->hasColumn($name));
746 1
            $table->getColumn($name)->setNotnull(false);
747 1
        };
748
    }
749
750
    /**
751
     * @param string $name
752
     *
753
     * @return Closure
754
     */
755 1
    protected function notNullableValue(string $name): Closure
756
    {
757
        return function (Table $table) use ($name) {
758 1
            assert($table->hasColumn($name));
759 1
            $table->getColumn($name)->setNotnull(true);
760 1
        };
761
    }
762
763
    /**
764
     * @param string     $name
765
     * @param bool       $notNullable
766
     * @param null|mixed $default
767
     *
768
     * @return Closure
769
     */
770 1
    private function unsignedIntImpl(string $name, bool $notNullable, $default = null): Closure
771
    {
772
        return function (Table $table) use ($name, $notNullable, $default) {
773 1
            $column = $table->addColumn($name, Type::INTEGER)->setUnsigned(true)->setNotnull($notNullable);
774 1
            $default === null ?: $column->setDefault($default);
775 1
        };
776
    }
777
778
    /** @noinspection PhpTooManyParametersInspection
779
     * @param string   $localKey
780
     * @param string   $foreignTable
781
     * @param string   $foreignKey
782
     * @param string   $type
783
     * @param int|null $length
784
     * @param bool     $notNullable
785
     * @param string   $onDeleteRestriction
786
     *
787
     * @return Closure
788
     */
789 1
    private function foreignColumnImpl(
790
        string $localKey,
791
        string $foreignTable,
792
        string $foreignKey,
793
        string $type,
794
        ?int $length,
795
        bool $notNullable,
796
        string $onDeleteRestriction
797
    ): Closure {
798
        return function (Table $table) use (
799 1
            $localKey,
800 1
            $foreignTable,
801 1
            $foreignKey,
802 1
            $notNullable,
803 1
            $onDeleteRestriction,
804 1
            $type,
805 1
            $length
806
        ) {
807 1
            $column = $table->addColumn($localKey, $type)->setNotnull($notNullable);
808 1
            $length === null ? $column->setUnsigned(true) : $column->setLength($length);
809 1
            $table->addForeignKeyConstraint(
810 1
                $foreignTable,
811 1
                [$localKey],
812 1
                [$foreignKey],
813 1
                ['onDelete' => $onDeleteRestriction]
814
            );
815 1
        };
816
    }
817
818
    /**
819
     * @param string $name
820
     * @param bool   $notNullable
821
     * @param string $onDeleteRestriction
822
     *
823
     * @return Closure
824
     */
825 1
    private function relationshipImpl(string $name, bool $notNullable, string $onDeleteRestriction): Closure
826
    {
827
        return function (
828
            Table $table,
829
            MigrationContextInterface $context
830
        ) use (
831 1
            $name,
832 1
            $notNullable,
833 1
            $onDeleteRestriction
834
        ) {
835 1
            $modelClass = $context->getModelClass();
836
837 1
            assert(
838 1
                $this->getModelSchemas()->hasRelationship($modelClass, $name),
839 1
                "Relationship `$name` not found for model `$modelClass`."
840
            );
841 1
            assert(
842 1
                $this->getModelSchemas()->getRelationshipType($modelClass, $name) === RelationshipTypes::BELONGS_TO,
843 1
                "Relationship `$name` for model `$modelClass` must be `belongsTo`."
844
            );
845
846 1
            $localKey     = $this->getModelSchemas()->getForeignKey($modelClass, $name);
847 1
            $columnType   = $this->getModelSchemas()->getAttributeType($modelClass, $localKey);
848 1
            $columnLength = $columnType === Type::STRING ?
849 1
                $this->getModelSchemas()->getAttributeLength($modelClass, $localKey) : null;
850
851 1
            $otherModelClass = $this->getModelSchemas()->getReverseModelClass($modelClass, $name);
852 1
            assert(
853 1
                $this->getModelSchemas()->hasClass($otherModelClass),
854 1
                "Class `$otherModelClass` is not found in model Schemas."
855
            );
856 1
            $foreignTable = $this->getModelSchemas()->getTable($otherModelClass);
857 1
            $foreignKey   = $this->getModelSchemas()->getPrimaryKey($otherModelClass);
858
859 1
            $fkClosure = $this->foreignColumnImpl(
860 1
                $localKey,
861 1
                $foreignTable,
862 1
                $foreignKey,
863 1
                $columnType,
864 1
                $columnLength,
865 1
                $notNullable,
866 1
                $onDeleteRestriction
867
            );
868
869 1
            return $fkClosure($table);
870 1
        };
871
    }
872
}
873