Completed
Push — master ( e7caf5...c1db53 )
by Neomerx
01:41
created

MigrationTrait::notNullableValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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