Passed
Push — master ( 10a192...087136 )
by Aleksei
07:38 queued 05:42
created

Renderer::columnOptions()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 21
c 0
b 0
f 0
rs 9.9
cc 4
nc 8
nop 2
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license MIT
7
 * @author  Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Migrations\Atomizer;
13
14
use Cycle\Database\Schema\AbstractColumn;
15
use Cycle\Database\Schema\AbstractForeignKey;
16
use Cycle\Database\Schema\AbstractIndex;
17
use Cycle\Database\Schema\AbstractTable;
18
use Cycle\Database\Schema\Comparator;
19
use Spiral\Reactor\Partial\Source;
20
use Spiral\Reactor\Serializer;
21
use Spiral\Reactor\Traits\SerializerTrait;
22
23
final class Renderer implements RendererInterface
24
{
25
    use SerializerTrait;
26
27
    /**
28
     * Comparator alteration states.
29
     */
30
    public const NEW_STATE = 0;
31
    public const ORIGINAL_STATE = 1;
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function createTable(Source $source, AbstractTable $table): void
37
    {
38
        $this->render(
39
            $source,
40
            '$this->table(%s)',
41
            $table
42
        );
43
        $comparator = $table->getComparator();
44
45
        $this->declareColumns($source, $comparator);
46
        $this->declareIndexes($source, $comparator);
47
        $this->declareForeignKeys($source, $comparator, $table->getPrefix());
48
49
        if (count($table->getPrimaryKeys())) {
50
            $this->render(
51
                $source,
52
                '    ->setPrimaryKeys(%s)',
53
                $table->getPrimaryKeys()
54
            );
55
        }
56
57
        //Finalization
58
        $source->addLine('    ->create();');
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function updateTable(Source $source, AbstractTable $table): void
65
    {
66
        $this->render(
67
            $source,
68
            '$this->table(%s)',
69
            $table
70
        );
71
        $comparator = $table->getComparator();
72
73
        if ($comparator->isPrimaryChanged()) {
74
            $this->render(
75
                $source,
76
                '    ->setPrimaryKeys(%s)',
77
                $table->getPrimaryKeys()
78
            );
79
        }
80
81
        $this->declareColumns($source, $comparator);
82
        $this->declareIndexes($source, $comparator);
83
        $this->declareForeignKeys($source, $comparator, $table->getPrefix());
84
85
        //Finalization
86
        $source->addLine('    ->update();');
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function revertTable(Source $source, AbstractTable $table): void
93
    {
94
        //Get table blueprint
95
        $this->render(
96
            $source,
97
            '$this->table(%s)',
98
            $table
99
        );
100
        $comparator = $table->getComparator();
101
102
        $this->revertForeignKeys($source, $comparator, $table->getPrefix());
103
        $this->revertIndexes($source, $comparator);
104
        $this->revertColumns($source, $comparator);
105
106
        //Finalization
107
        $source->addLine('    ->update();');
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function dropTable(Source $source, AbstractTable $table): void
114
    {
115
        $this->render(
116
            $source,
117
            '$this->table(%s)->drop();',
118
            $table
119
        );
120
    }
121
122
    /**
123
     * @param Source     $source
124
     * @param Comparator $comparator
125
     */
126
    private function declareColumns(Source $source, Comparator $comparator): void
127
    {
128
        foreach ($comparator->addedColumns() as $column) {
129
            $this->render(
130
                $source,
131
                '    ->addColumn(%s, %s, %s)',
132
                $column->getName(),
133
                $column->getDeclaredType() ?? $column->getAbstractType(),
134
                $column
135
            );
136
        }
137
138
        foreach ($comparator->alteredColumns() as $pair) {
139
            $this->alterColumn(
140
                $source,
141
                $pair[self::NEW_STATE],
142
                $pair[self::ORIGINAL_STATE]
143
            );
144
        }
145
146
        foreach ($comparator->droppedColumns() as $column) {
147
            $this->render(
148
                $source,
149
                '    ->dropColumn(%s)',
150
                $column->getName()
151
            );
152
        }
153
    }
154
155
    /**
156
     * @param Source     $source
157
     * @param Comparator $comparator
158
     */
159
    private function declareIndexes(Source $source, Comparator $comparator): void
160
    {
161
        foreach ($comparator->addedIndexes() as $index) {
162
            $this->render(
163
                $source,
164
                '    ->addIndex(%s, %s)',
165
                $index->getColumns(),
166
                $index
167
            );
168
        }
169
170
        foreach ($comparator->alteredIndexes() as $pair) {
171
            /** @var AbstractIndex $index */
172
            $index = $pair[self::NEW_STATE];
173
            $this->render(
174
                $source,
175
                '    ->alterIndex(%s, %s)',
176
                $index->getColumns(),
177
                $index
178
            );
179
        }
180
181
        foreach ($comparator->droppedIndexes() as $index) {
182
            $this->render(
183
                $source,
184
                '    ->dropIndex(%s)',
185
                $index->getColumns()
186
            );
187
        }
188
    }
189
190
    /**
191
     * @param Source     $source
192
     * @param Comparator $comparator
193
     * @param string     $prefix Database isolation prefix
194
     */
195
    private function declareForeignKeys(Source $source, Comparator $comparator, string $prefix = ''): void
196
    {
197
        foreach ($comparator->addedForeignKeys() as $key) {
198
            $this->render(
199
                $source,
200
                '    ->addForeignKey(%s, %s, %s, %s)',
201
                $key->getColumns(),
202
                substr($key->getForeignTable(), strlen($prefix)),
203
                $key->getForeignKeys(),
204
                $key
205
            );
206
        }
207
208
        foreach ($comparator->alteredForeignKeys() as $pair) {
209
            /** @var AbstractForeignKey $key */
210
            $key = $pair[self::NEW_STATE];
211
            $this->render(
212
                $source,
213
                '    ->alterForeignKey(%s, %s, %s, %s)',
214
                $key->getColumns(),
215
                substr($key->getForeignTable(), strlen($prefix)),
216
                $key->getForeignKeys(),
217
                $key
218
            );
219
        }
220
221
        foreach ($comparator->droppedForeignKeys() as $key) {
222
            $this->render(
223
                $source,
224
                '    ->dropForeignKey(%s)',
225
                $key->getColumns()
226
            );
227
        }
228
    }
229
230
    /**
231
     * @param Source     $source
232
     * @param Comparator $comparator
233
     */
234
    private function revertColumns(Source $source, Comparator $comparator): void
235
    {
236
        foreach ($comparator->droppedColumns() as $column) {
237
            $this->render(
238
                $source,
239
                '    ->addColumn(%s, %s, %s)',
240
                $column->getName(),
241
                $column->getDeclaredType() ?? $column->getAbstractType(),
242
                $column
243
            );
244
        }
245
246
        foreach ($comparator->alteredColumns() as $pair) {
247
            $this->alterColumn(
248
                $source,
249
                $pair[self::ORIGINAL_STATE],
250
                $pair[self::NEW_STATE]
251
            );
252
        }
253
254
        foreach ($comparator->addedColumns() as $column) {
255
            $this->render(
256
                $source,
257
                '    ->dropColumn(%s)',
258
                $column->getName()
259
            );
260
        }
261
    }
262
263
    /**
264
     * @param Source     $source
265
     * @param Comparator $comparator
266
     */
267
    private function revertIndexes(Source $source, Comparator $comparator): void
268
    {
269
        foreach ($comparator->droppedIndexes() as $index) {
270
            $this->render(
271
                $source,
272
                '    ->addIndex(%s, %s)',
273
                $index->getColumns(),
274
                $index
275
            );
276
        }
277
278
        foreach ($comparator->alteredIndexes() as $pair) {
279
            /** @var AbstractIndex $index */
280
            $index = $pair[self::ORIGINAL_STATE];
281
            $this->render(
282
                $source,
283
                '    ->alterIndex(%s, %s)',
284
                $index->getColumns(),
285
                $index
286
            );
287
        }
288
289
        foreach ($comparator->addedIndexes() as $index) {
290
            $this->render(
291
                $source,
292
                '    ->dropIndex(%s)',
293
                $index->getColumns()
294
            );
295
        }
296
    }
297
298
    /**
299
     * @param Source     $source
300
     * @param Comparator $comparator
301
     * @param string     $prefix Database isolation prefix.
302
     */
303
    private function revertForeignKeys(Source $source, Comparator $comparator, string $prefix = ''): void
304
    {
305
        foreach ($comparator->droppedForeignKeys() as $key) {
306
            $this->render(
307
                $source,
308
                '    ->addForeignKey(%s, %s, %s, %s)',
309
                $key->getColumns(),
310
                substr($key->getForeignTable(), strlen($prefix)),
311
                $key->getForeignKeys(),
312
                $key
313
            );
314
        }
315
316
        foreach ($comparator->alteredForeignKeys() as $pair) {
317
            /** @var AbstractForeignKey $key */
318
            $key = $pair[self::ORIGINAL_STATE];
319
            $this->render(
320
                $source,
321
                '    ->alterForeignKey(%s, %s, %s, %s)',
322
                $key->getColumns(),
323
                substr($key->getForeignTable(), strlen($prefix)),
324
                $key->getForeignKeys(),
325
                $key
326
            );
327
        }
328
329
        foreach ($comparator->addedForeignKeys() as $key) {
330
            $this->render($source, '    ->dropForeignKey(%s)', $key->getColumns());
331
        }
332
    }
333
334
    /**
335
     * @param Source         $source
336
     * @param AbstractColumn $column
337
     * @param AbstractColumn $original
338
     */
339
    protected function alterColumn(
340
        Source $source,
341
        AbstractColumn $column,
342
        AbstractColumn $original
343
    ): void {
344
        if ($column->getName() !== $original->getName()) {
345
            $name = $original->getName();
346
        } else {
347
            $name = $column->getName();
348
        }
349
350
        $this->render(
351
            $source,
352
            '    ->alterColumn(%s, %s, %s)',
353
            $name,
354
            $column->getDeclaredType() ?? $column->getAbstractType(),
355
            $column
356
        );
357
358
        if ($column->getName() !== $original->getName()) {
359
            $this->render(
360
                $source,
361
                '    ->renameColumn(%s, %s)',
362
                $name,
363
                $column->getName()
364
            );
365
        }
366
    }
367
368
    /**
369
     * Render values and options into source.
370
     *
371
     * @param Source $source
372
     * @param string $format
373
     * @param array  ...$values
374
     */
375
    protected function render(Source $source, string $format, ...$values): void
376
    {
377
        $serializer = $this->getSerializer();
378
379
        $rendered = [];
380
        foreach ($values as $value) {
381
            if ($value instanceof AbstractTable) {
382
                $rendered[] = $serializer->serialize(
383
                    substr($value->getName(), strlen($value->getPrefix()))
384
                );
385
                continue;
386
            }
387
388
            if ($value instanceof AbstractColumn) {
389
                $rendered[] = $this->columnOptions($serializer, $value);
390
                continue;
391
            }
392
393
            if ($value instanceof AbstractIndex) {
394
                $rendered[] = $this->indexOptions($serializer, $value);
395
                continue;
396
            }
397
398
            if ($value instanceof AbstractForeignKey) {
399
                $rendered[] = $this->foreignKeyOptions($serializer, $value);
400
                continue;
401
            }
402
403
            // numeric array
404
            if (is_array($value) && count($value) > 0 && is_numeric(array_keys($value)[0])) {
405
                $rendered[] = '["' . implode('", "', $value) . '"]';
406
                continue;
407
            }
408
409
            $rendered[] = $serializer->serialize($value);
410
        }
411
412
        $lines = sprintf($format, ...$rendered);
413
        foreach (explode("\n", $lines) as $line) {
414
            $source->addLine($line);
415
        }
416
    }
417
418
    /**
419
     * @param Serializer     $serializer
420
     * @param AbstractColumn $column
421
     *
422
     * @return string
423
     */
424
    private function columnOptions(Serializer $serializer, AbstractColumn $column): string
425
    {
426
        $options = [
427
            'nullable' => $column->isNullable(),
428
            'default' => $column->getDefaultValue(),
429
        ];
430
431
        if ($column->getAbstractType() === 'enum') {
432
            $options['values'] = $column->getEnumValues();
433
        }
434
435
        if ($column->getAbstractType() === 'string') {
436
            $options['size'] = $column->getSize();
437
        }
438
439
        if ($column->getAbstractType() === 'decimal') {
440
            $options['scale'] = $column->getScale();
441
            $options['precision'] = $column->getPrecision();
442
        }
443
444
        return $this->mountIndents($serializer->serialize($options));
445
    }
446
447
    /**
448
     * @param Serializer    $serializer
449
     * @param AbstractIndex $index
450
     *
451
     * @return string
452
     */
453
    private function indexOptions(Serializer $serializer, AbstractIndex $index): string
454
    {
455
        return $this->mountIndents(
456
            $serializer->serialize(
457
                [
458
                    'name' => $index->getName(),
459
                    'unique' => $index->isUnique(),
460
                ]
461
            )
462
        );
463
    }
464
465
    /**
466
     * @param Serializer         $serializer
467
     * @param AbstractForeignKey $reference
468
     *
469
     * @return string
470
     */
471
    private function foreignKeyOptions(
472
        Serializer $serializer,
473
        AbstractForeignKey $reference
474
    ): string {
475
        return $this->mountIndents(
476
            $serializer->serialize(
477
                [
478
                    'name' => $reference->getName(),
479
                    'delete' => $reference->getDeleteRule(),
480
                    'update' => $reference->getUpdateRule(),
481
                ]
482
            )
483
        );
484
    }
485
486
    /**
487
     * Mount indents for column and index options.
488
     *
489
     * @param $serialized
490
     *
491
     * @return string
492
     */
493
    private function mountIndents(string $serialized): string
494
    {
495
        $lines = explode("\n", $serialized);
496
        foreach ($lines as &$line) {
497
            $line = '    ' . $line;
498
            unset($line);
499
        }
500
501
        return ltrim(implode("\n", $lines));
502
    }
503
}
504