Completed
Pull Request — master (#1753)
by
unknown
02:25 queued 01:04
created

Plan::forgetRemoveColumn()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 22

Duplication

Lines 22
Ratio 100 %

Importance

Changes 0
Metric Value
dl 22
loc 22
rs 9.2568
c 0
b 0
f 0
cc 5
nc 1
nop 3
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Db\Plan;
9
10
use ArrayObject;
11
use Phinx\Db\Action\AddColumn;
12
use Phinx\Db\Action\AddForeignKey;
13
use Phinx\Db\Action\AddIndex;
14
use Phinx\Db\Action\ChangeColumn;
15
use Phinx\Db\Action\ChangeComment;
16
use Phinx\Db\Action\ChangePrimaryKey;
17
use Phinx\Db\Action\CreateTable;
18
use Phinx\Db\Action\DropForeignKey;
19
use Phinx\Db\Action\DropIndex;
20
use Phinx\Db\Action\DropTable;
21
use Phinx\Db\Action\RemoveColumn;
22
use Phinx\Db\Action\RenameColumn;
23
use Phinx\Db\Action\RenameTable;
24
use Phinx\Db\Adapter\AdapterInterface;
25
use Phinx\Db\Plan\Solver\ActionSplitter;
26
use Phinx\Db\Table\Table;
27
28
/**
29
 * A Plan takes an Intent and transforms int into a sequence of
30
 * instructions that can be correctly executed by an AdapterInterface.
31
 *
32
 * The main focus of Plan is to arrange the actions in the most efficient
33
 * way possible for the database.
34
 */
35
class Plan
36
{
37
    /**
38
     * List of tables to be created
39
     *
40
     * @var \Phinx\Db\Plan\NewTable[]
41
     */
42
    protected $tableCreates = [];
43
44
    /**
45
     * List of table updates
46
     *
47
     * @var \Phinx\Db\Plan\AlterTable[]
48
     */
49
    protected $tableUpdates = [];
50
51
    /**
52
     * List of table removals or renames
53
     *
54
     * @var \Phinx\Db\Plan\AlterTable[]
55
     */
56
    protected $tableMoves = [];
57
58
    /**
59
     * List of index additions or removals
60
     *
61
     * @var \Phinx\Db\Plan\AlterTable[]
62
     */
63
    protected $indexes = [];
64
65
    /**
66
     * List of constraint additions or removals
67
     *
68
     * @var \Phinx\Db\Plan\AlterTable[]
69
     */
70
    protected $constraints = [];
71
72
    /**
73
     * List of dropped columns
74
     *
75
     * @var \Phinx\Db\Plan\AlterTable[]
76
     */
77
    protected $columnRemoves = [];
78
79
    /**
80
     * Constructor
81
     *
82
     * @param \Phinx\Db\Plan\Intent $intent All the actions that should be executed
83
     */
84
    public function __construct(Intent $intent)
85
    {
86
        $this->createPlan($intent->getActions());
87
    }
88
89
    /**
90
     * Parses the given Intent and creates the separate steps to execute
91
     *
92
     * @param \Phinx\Db\Action\Action[] $actions The actions to use for the plan
93
     *
94
     * @return void
95
     */
96
    protected function createPlan($actions)
97
    {
98
        $this->gatherCreates($actions);
99
        $this->gatherUpdates($actions);
100
        $this->gatherTableMoves($actions);
101
        $this->gatherIndexes($actions);
102
        $this->gatherConstraints($actions);
103
        $this->resolveConflicts();
104
    }
105
106
    /**
107
     * Returns a nested list of all the steps to execute
108
     *
109
     * @return \Phinx\Db\Plan\AlterTable[][]
110
     */
111 View Code Duplication
    protected function updatesSequence()
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...
112
    {
113
        return [
114
            $this->tableUpdates,
115
            $this->constraints,
116
            $this->indexes,
117
            $this->columnRemoves,
118
            $this->tableMoves,
119
        ];
120
    }
121
122
    /**
123
     * Returns a nested list of all the steps to execute in inverse order
124
     *
125
     * @return \Phinx\Db\Plan\AlterTable[][]
126
     */
127 View Code Duplication
    protected function inverseUpdatesSequence()
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...
128
    {
129
        return [
130
            $this->constraints,
131
            $this->tableMoves,
132
            $this->indexes,
133
            $this->columnRemoves,
134
            $this->tableUpdates,
135
        ];
136
    }
137
138
    /**
139
     * Executes this plan using the given AdapterInterface
140
     *
141
     * @param \Phinx\Db\Adapter\AdapterInterface $executor The executor object for the plan
142
     *
143
     * @return void
144
     */
145 View Code Duplication
    public function execute(AdapterInterface $executor)
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...
146
    {
147
        foreach ($this->tableCreates as $newTable) {
148
            $executor->createTable($newTable->getTable(), $newTable->getColumns(), $newTable->getIndexes());
149
        }
150
151
        foreach ($this->updatesSequence() as $updates) {
152
            foreach ($updates as $update) {
153
                $executor->executeActions($update->getTable(), $update->getActions());
154
            }
155
        }
156
    }
157
158
    /**
159
     * Executes the inverse plan (rollback the actions) with the given AdapterInterface:w
160
     *
161
     * @param \Phinx\Db\Adapter\AdapterInterface $executor The executor object for the plan
162
     *
163
     * @return void
164
     */
165 View Code Duplication
    public function executeInverse(AdapterInterface $executor)
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...
166
    {
167
        foreach ($this->inverseUpdatesSequence() as $updates) {
168
            foreach ($updates as $update) {
169
                $executor->executeActions($update->getTable(), $update->getActions());
170
            }
171
        }
172
173
        foreach ($this->tableCreates as $newTable) {
174
            $executor->createTable($newTable->getTable(), $newTable->getColumns(), $newTable->getIndexes());
175
        }
176
    }
177
178
    /**
179
     * Deletes certain actions from the plan if they are found to be conflicting or redundant.
180
     *
181
     * @return void
182
     */
183
    protected function resolveConflicts()
184
    {
185
        foreach ($this->tableMoves as $alterTable) {
186
            foreach ($alterTable->getActions() as $action) {
187
                if ($action instanceof DropTable) {
188
                    $this->tableUpdates = $this->forgetTable($action->getTable(), $this->tableUpdates);
189
                    $this->constraints = $this->forgetTable($action->getTable(), $this->constraints);
190
                    $this->indexes = $this->forgetTable($action->getTable(), $this->indexes);
191
                    $this->columnRemoves = $this->forgetTable($action->getTable(), $this->columnRemoves);
192
                }
193
            }
194
        }
195
196
        // Columns that are dropped will automatically cause the indexes to be dropped as well
197
        foreach ($this->columnRemoves as $columnRemove) {
198
            foreach ($columnRemove->getActions() as $action) {
199
                if ($action instanceof RemoveColumn) {
200
                    list($this->indexes) = $this->forgetDropIndex(
201
                        $action->getTable(),
202
                        [$action->getColumn()->getName()],
203
                        $this->indexes
204
                    );
205
                }
206
            }
207
        }
208
209
        $splitter = new ActionSplitter(
210
            RenameColumn::class,
211
            ChangeColumn::class,
212
            function (RenameColumn $a, ChangeColumn $b) {
213
                return $a->getNewName() === $b->getColumnName();
214
            }
215
        );
216
        $tableUpdates = [];
217
        foreach ($this->tableUpdates as $tableUpdate) {
218
            array_push($tableUpdates, ...$splitter->split($tableUpdate));
219
        }
220
        $this->tableUpdates = $tableUpdates;
221
222
        $splitter = new ActionSplitter(
223
            DropForeignKey::class,
224
            AddForeignKey::class,
225
            function (DropForeignKey $a, AddForeignKey $b) {
226
                return $a->getForeignKey()->getColumns() === $b->getForeignKey()->getColumns();
227
            }
228
        );
229
        $constraints = [];
230
        foreach ($this->constraints as $constraint) {
231
            $constraint = $this->remapContraintAndIndexConflicts($constraint);
232
            array_push($constraints, ...$splitter->split($constraint));
233
        }
234
        $this->constraints = $constraints;
235
    }
236
237
    /**
238
     * Deletes all actions related to the given table and keeps the
239
     * rest
240
     *
241
     * @param \Phinx\Db\Table\Table $table The table to find in the list of actions
242
     * @param \Phinx\Db\Plan\AlterTable[] $actions The actions to transform
243
     *
244
     * @return \Phinx\Db\Plan\AlterTable[] The list of actions without actions for the given table
245
     */
246
    protected function forgetTable(Table $table, $actions)
247
    {
248
        $result = [];
249
        foreach ($actions as $action) {
250
            if ($action->getTable()->getName() === $table->getName()) {
251
                continue;
252
            }
253
            $result[] = $action;
254
        }
255
256
        return $result;
257
    }
258
259
    /**
260
     * Finds all DropForeignKey actions in an AlterTable and moves
261
     * all conflicting DropIndex action in `$this->indexes` into the
262
     * given AlterTable.
263
     *
264
     * @param \Phinx\Db\Plan\AlterTable $alter The collection of actions to inspect
265
     *
266
     * @return \Phinx\Db\Plan\AlterTable The updated AlterTable object. This function
267
     * has the side effect of changing the `$this->indexes` property.
268
     */
269
    protected function remapContraintAndIndexConflicts(AlterTable $alter)
270
    {
271
        $newAlter = new AlterTable($alter->getTable());
272
273
        foreach ($alter->getActions() as $action) {
274
            $newAlter->addAction($action);
275
            if ($action instanceof DropForeignKey) {
276
                list($this->indexes, $dropIndexActions) = $this->forgetDropIndex(
277
                    $action->getTable(),
278
                    $action->getForeignKey()->getColumns(),
279
                    $this->indexes
280
                );
281
                foreach ($dropIndexActions as $dropIndexAction) {
282
                    $newAlter->addAction($dropIndexAction);
283
                }
284
            }
285
        }
286
287
        return $newAlter;
288
    }
289
290
    /**
291
     * Deletes any DropIndex actions for the given table and exact columns
292
     *
293
     * @param \Phinx\Db\Table\Table $table The table to find in the list of actions
294
     * @param string[] $columns The column names to match
295
     * @param \Phinx\Db\Plan\AlterTable[] $actions The actions to transform
296
     *
297
     * @return array A tuple containing the list of actions without actions for dropping the index
298
     * and a list of drop index actions that were removed.
299
     */
300 View Code Duplication
    protected function forgetDropIndex(Table $table, array $columns, array $actions)
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...
301
    {
302
        $dropIndexActions = new ArrayObject();
303
        $indexes = array_map(function ($alter) use ($table, $columns, $dropIndexActions) {
304
            if ($alter->getTable()->getName() !== $table->getName()) {
305
                return $alter;
306
            }
307
308
            $newAlter = new AlterTable($table);
309
            foreach ($alter->getActions() as $action) {
310
                if ($action instanceof DropIndex && $action->getIndex()->getColumns() === $columns) {
311
                    $dropIndexActions->append($action);
312
                } else {
313
                    $newAlter->addAction($action);
314
                }
315
            }
316
317
            return $newAlter;
318
        }, $actions);
319
320
        return [$indexes, $dropIndexActions->getArrayCopy()];
321
    }
322
323
    /**
324
     * Deletes any RemoveColumn actions for the given table and exact columns
325
     *
326
     * @param \Phinx\Db\Table\Table $table The table to find in the list of actions
327
     * @param string[] $columns The column names to match
328
     * @param \Phinx\Db\Plan\AlterTable[] $actions The actions to transform
329
     *
330
     * @return array A tuple containing the list of actions without actions for removing the column
331
     * and a list of remove column actions that were removed.
332
     */
333 View Code Duplication
    protected function forgetRemoveColumn(Table $table, array $columns, array $actions)
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...
334
    {
335
        $removeColumnActions = new ArrayObject();
336
        $indexes = array_map(function ($alter) use ($table, $columns, $removeColumnActions) {
337
            if ($alter->getTable()->getName() !== $table->getName()) {
338
                return $alter;
339
            }
340
341
            $newAlter = new AlterTable($table);
342
            foreach ($alter->getActions() as $action) {
343
                if ($action instanceof RemoveColumn && in_array($action->getColumn()->getName(), $columns, true)) {
344
                    $removeColumnActions->append($action);
345
                } else {
346
                    $newAlter->addAction($action);
347
                }
348
            }
349
350
            return $newAlter;
351
        }, $actions);
352
353
        return [$indexes, $removeColumnActions->getArrayCopy()];
354
    }
355
356
    /**
357
     * Collects all table creation actions from the given intent
358
     *
359
     * @param \Phinx\Db\Action\Action[] $actions The actions to parse
360
     *
361
     * @return void
362
     */
363
    protected function gatherCreates($actions)
364
    {
365
        foreach ($actions as $action) {
366
            if ($action instanceof CreateTable) {
367
                $this->tableCreates[$action->getTable()->getName()] = new NewTable($action->getTable());
368
            }
369
        }
370
371
        foreach ($actions as $action) {
372
            if (
373
                ($action instanceof AddColumn || $action instanceof AddIndex)
374
                && isset($this->tableCreates[$action->getTable()->getName()])
375
            ) {
376
                $table = $action->getTable();
377
378
                if ($action instanceof AddColumn) {
379
                    $this->tableCreates[$table->getName()]->addColumn($action->getColumn());
380
                }
381
382
                if ($action instanceof AddIndex) {
383
                    $this->tableCreates[$table->getName()]->addIndex($action->getIndex());
384
                }
385
            }
386
        }
387
    }
388
389
    /**
390
     * Collects all alter table actions from the given intent
391
     *
392
     * @param \Phinx\Db\Action\Action[] $actions The actions to parse
393
     *
394
     * @return void
395
     */
396
    protected function gatherUpdates($actions)
397
    {
398
        foreach ($actions as $action) {
399
            if (
400
                !(
401
                    $action instanceof AddColumn
402
                    || $action instanceof ChangeColumn
403
                    || $action instanceof RemoveColumn
404
                    || $action instanceof RenameColumn
405
                )
406
                || isset($this->tableCreates[$action->getTable()->getName()])
407
            ) {
408
                 continue;
409
            }
410
            $table = $action->getTable();
411
            $name = $table->getName();
412
413
            if ($action instanceof RemoveColumn) {
414
                if (!isset($this->columnRemoves[$name])) {
415
                    $this->columnRemoves[$name] = new AlterTable($table);
416
                }
417
                $this->columnRemoves[$name]->addAction($action);
418
            } else {
419
                if (!isset($this->tableUpdates[$name])) {
420
                    $this->tableUpdates[$name] = new AlterTable($table);
421
                }
422
                $this->tableUpdates[$name]->addAction($action);
423
            }
424
        }
425
    }
426
427
    /**
428
     * Collects all alter table drop and renames from the given intent
429
     *
430
     * @param \Phinx\Db\Action\Action[] $actions The actions to parse
431
     * @return void
432
     */
433
    protected function gatherTableMoves($actions)
434
    {
435
        foreach ($actions as $action) {
436
            if (
437
                !(
438
                $action instanceof DropTable
439
                || $action instanceof RenameTable
440
                || $action instanceof ChangePrimaryKey
441
                || $action instanceof ChangeComment)
442
            ) {
443
                continue;
444
            }
445
            $table = $action->getTable();
446
            $name = $table->getName();
447
448
            if (!isset($this->tableMoves[$name])) {
449
                $this->tableMoves[$name] = new AlterTable($table);
450
            }
451
452
            $this->tableMoves[$name]->addAction($action);
453
        }
454
    }
455
456
    /**
457
     * Collects all index creation and drops from the given intent
458
     *
459
     * @param \Phinx\Db\Action\Action[] $actions The actions to parse
460
     *
461
     * @return void
462
     */
463
    protected function gatherIndexes($actions)
464
    {
465
        foreach ($actions as $action) {
466
            if (
467
                !(
468
                    $action instanceof AddIndex
469
                    || $action instanceof DropIndex
470
                )
471
                || isset($this->tableCreates[$action->getTable()->getName()])
472
            ) {
473
                continue;
474
            }
475
476
            $table = $action->getTable();
477
            $name = $table->getName();
478
479
            if (!isset($this->indexes[$name])) {
480
                $this->indexes[$name] = new AlterTable($table);
481
            }
482
483
            $this->indexes[$name]->addAction($action);
484
        }
485
    }
486
487
    /**
488
     * Collects all foreign key creation and drops from the given intent
489
     *
490
     * @param \Phinx\Db\Action\Action[] $actions The actions to parse
491
     *
492
     * @return void
493
     */
494
    protected function gatherConstraints($actions)
495
    {
496
        foreach ($actions as $action) {
497
            if (!($action instanceof AddForeignKey || $action instanceof DropForeignKey)) {
498
                continue;
499
            }
500
            $table = $action->getTable();
501
            $name = $table->getName();
502
503
            if (!isset($this->constraints[$name])) {
504
                $this->constraints[$name] = new AlterTable($table);
505
            }
506
507
            $this->constraints[$name]->addAction($action);
508
        }
509
    }
510
}
511