Passed
Push — main ( 3f5d4e...df4575 )
by James Ekow Abaka
01:53
created

Migrate::getBegin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
namespace yentu\commands;
4
5
use clearice\io\Io;
6
use yentu\database\DatabaseItem;
7
use yentu\ChangeLogger;
8
use yentu\database\ForeignKey;
9
use yentu\factories\DatabaseManipulatorFactory;
10
use yentu\factories\DatabaseItemFactory;
11
use yentu\Migrations;
12
use yentu\Yentu;
13
14
15
/**
16
 * The migrate class runs the command that creates database items.
17
 */
18
class Migrate extends Command implements Reversible
19
{
20
21
    const FILTER_UNRUN = 'unrun';
22
    const FILTER_LAST_SESSION = 'lastSession';
23
24
    private $driver;
25
    private $dryDriver;
26
    private $lastSession;
27
    private $currentPath;
28
    private $rollbackCommand;
29
    private $manipulatorFactory;
30
    private $migrations;
31
    private $io;
32
    private $itemFactory;
33
34
    public function __construct(Migrations $migrations, DatabaseManipulatorFactory $manipulatorFactory, Io $io, DatabaseItemFactory $itemFactory)
35
    {
36
        $this->manipulatorFactory = $manipulatorFactory;
37
        $this->migrations = $migrations;
38
        $this->io = $io;
39
        $this->itemFactory = $itemFactory;
40
    }
41
42
    public function setupOptions($options, &$filter)
43
    {
44
        if (isset($options['no-foreign-keys'])) {
45
            $this->io->output("Ignoring all foreign key constraints ...\n");
46
            $this->driver->skip('ForeignKey');
47
        }
48
49
        if (isset($options['only-foreign-keys'])) {
50
            $this->io->output("Applying only foreign keys ...\n");
51
            $this->lastSession = $this->driver->getLastSession();
52
            $this->driver->allowOnly('ForeignKey');
53
            $filter = self::FILTER_LAST_SESSION;
54
        }
55
56
        if (isset($options['force-foreign-keys'])) {
57
            $this->io->output("Applying only foreign keys and skipping on errors ...\n");
58
            $this->lastSession = $this->driver->getLastSession();
59
            $this->driver->setSkipOnErrors($options['force-foreign-keys']);
60
            $this->driver->allowOnly('ForeignKey');
61
            $filter = self::FILTER_LAST_SESSION;
62
        }
63
64
        if (isset($options['default-ondelete-action'])) {
65
            ForeignKey::$defaultOnDelete = $options['default-ondelete-action'];
66
        }
67
68
        if (isset($options['default-onupdate-action'])) {
69
            ForeignKey::$defaultOnUpdate = $options['default-onupdate-action'];
70
        }
71
    }
72
73
    private function announceMigration($migrations, $path)
74
    {
75
        $size = count($migrations);
76
        if ($size > 0) {
77
            $this->io->output("Running $size migration(s) from '{$path['home']}'");
78
        } else {
79
            $this->io->output("No migrations to run from '{$path['home']}'\n");
80
        }
81
    }
82
83
    private static function fillOptions(&$options)
84
    {
85
        if (!isset($options['dump-queries'])) {
86
            $options['dump-queries'] = false;
87
        }
88
        if (!isset($options['dry'])) {
89
            $options['dry'] = false;
90
        }
91
    }
92
93
    public function run()
94
    {
95
        self::fillOptions($this->options);
96
97
        $this->driver = ChangeLogger::wrap($this->manipulatorFactory->createManipulator(), $this->migrations, $this->io);
98
        $this->driver->setDumpQueriesOnly($this->options['dump-queries']);
99
        $this->driver->setDryRun($this->options['dry']);
100
        $this->itemFactory->setDriver($this->driver);
101
        Yentu::setup($this->itemFactory, $this->driver->getDefaultSchema());
102
103
        $totalOperations = 0;
104
105
        $filter = self::FILTER_UNRUN;
106
        $this->setupOptions($this->options, $filter);
107
108
        \yentu\Timer::start();
109
        $migrationPaths = $this->migrations->getAllPaths();
110
        
111
        foreach ($migrationPaths as $path) {
112
            $migrations = $this->filter($this->migrations->getMigrationFiles($path['home']), $filter);
113
            $this->announceMigration($migrations, $path);
114
            $this->currentPath = $path;
115
116
            foreach ($migrations as $migration) {
117
                //$this->countOperations("{$path['home']}/{$migration['file']}");
118
                $this->driver->setVersion($migration['timestamp']);
119
                $this->driver->setMigration($migration['migration']);
120
                $this->io->output("\nApplying '{$migration['migration']}' migration\n");
121
                require "{$path['home']}/{$migration['file']}";
122
                $this->itemFactory->getEncapsulatedStack()->purge();
123
                $this->io->output("\n");
124
                $totalOperations += $this->driver->resetOperations();
125
            }
126
        }
127
128
        if ($this->driver->getChanges()) {
129
            $elapsed = \yentu\Timer::stop();
130
            $this->io->output("\nMigration took " . \yentu\Timer::pretty($elapsed) . "\n");
131
            $this->io->output($this->driver->getChanges() . " operations performed\n");
132
            $this->io->output($totalOperations - $this->driver->getChanges() . " operations skipped\n");
133
        }
134
135
        $this->driver->disconnect();
0 ignored issues
show
Bug introduced by
The method disconnect() does not exist on yentu\ChangeLogger. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        $this->driver->/** @scrutinizer ignore-call */ 
136
                       disconnect();
Loading history...
136
    }
137
138
    private function filter($migrations, $type = self::FILTER_UNRUN)
139
    {
140
        $filterMethod = "{$type}Filter";
141
        return $this->$filterMethod($migrations);
142
    }
143
144
    private function countOperations($migrationFile)
145
    {
146
        if ($this->dryDriver === null) {
147
            $this->dryDriver = clone $this->driver;
148
            $this->dryDriver->setDryRun(true);
149
        }
150
        $this->io->pushOutputLevel(Io::OUTPUT_LEVEL_0);
151
        $this->itemFactory->setDriver($this->dryDriver);
152
        require "$migrationFile";
153
        $this->itemFactory->getEncapsulatedStack()->purge();
154
        $this->itemFactory->setDriver($this->driver);
155
        $this->io->popOutputLevel();
156
        $this->driver->setExpectedOperations($this->dryDriver->resetOperations());
157
    }
158
159
    public function getCurrentPath()
160
    {
161
        return $this->currentPath;
162
    }
163
164
    private function unrunFilter($input)
165
    {
166
        $output = array();
167
        foreach ($input as $migration) {
168
            $run = $this->driver->query(
169
                "SELECT count(*) as number_run FROM yentu_history WHERE migration = ? and version = ?", 
170
                array($migration['migration'], $migration['timestamp'])
171
            );
172
173
            if ($run[0]['number_run'] == 0) {
174
                $output[] = $migration;
175
            }
176
        }
177
        return $output;
178
    }
179
180
    private function lastSessionFilter($input)
181
    {
182
        $versions = $this->driver->getSessionVersions($this->lastSession);
183
        $output = array();
184
        foreach ($input as $migration) {
185
            if (array_search($migration['timestamp'], $versions) !== false) {
186
                $output[] = $migration;
187
            }
188
        }
189
        return $output;
190
    }
191
192
    public function getChanges()
193
    {
194
        return $this->driver->getChanges();
195
    }
196
    
197
    public function getDefaultSchema()
198
    {
199
        return $this->driver->getDefaultSchema();
200
    }
201
202
    #[\ntentan\panie\Inject]
203
    public function setRollbackCommand(Rollback $rollbackCommand)
204
    {
205
        $this->rollbackCommand = $rollbackCommand;
206
    }
207
208
    #[\Override]
209
    public function reverseActions()
210
    {
211
        if ($this->driver === null) {
212
            return;
213
        }
214
215
        $this->io->output("Attempting to reverse all changes ... ");
216
        if ($this->getChanges() > 0) {
217
            $this->io->pushOutputLevel(0);
218
            $this->rollbackCommand->run();
219
            $this->io->popOutputLevel();
220
        }
221
        $this->io->output("OK\n");
222
    }
223
}
224
225