Issues (90)

src/commands/Migrate.php (1 issue)

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

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