Completed
Pull Request — master (#6)
by James Ekow Abaka
20:00 queued 18:35
created

Migrate::reverseActions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 14
ccs 0
cts 10
cp 0
crap 12
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * The MIT License
5
 *
6
 * Copyright 2014 James Ekow Abaka Ainooson
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
namespace yentu\commands;
28
29
use clearice\io\Io;
30
use yentu\database\Begin;
31
use yentu\database\DatabaseItem;
32
use yentu\ChangeLogger;
33
use yentu\database\ForeignKey;
34
use yentu\factories\DatabaseManipulatorFactory;
35
use yentu\Migrations;
36
37
/**
38
 * The migrate command for the yentu database migration system. This class is
39
 * responsible for creating and updating items 
40
 */
41
class Migrate extends Command implements Reversible
42
{
43
44
    const FILTER_UNRUN = 'unrun';
45
    const FILTER_LAST_SESSION = 'lastSession';
46
47
    private $driver;
48
    private $dryDriver;
49
    private $defaultSchema = false;
50
    private $lastSession;
51
    private $currentPath;
52
    private $rollbackCommand;
53
    private $manipulatorFactory;
54
    private $migrations;
55
    private $io;
56
57 10
    public function __construct(Migrations $migrations, DatabaseManipulatorFactory $manipulatorFactory, Io $io)
58
    {
59 10
        $this->manipulatorFactory = $manipulatorFactory;
60 10
        $this->migrations = $migrations;
61 10
        $this->io = $io;
62 10
    }
63
64 10
    public function setupOptions($options, &$filter)
65
    {
66 10
        if (isset($options['no-foreign-keys'])) {
67 3
            $this->io->output("Ignoring all foreign key constraints ...\n");
68 3
            $this->driver->skip('ForeignKey');
69
        }
70
71 10 View Code Duplication
        if (isset($options['only-foreign-keys'])) {
72 3
            $this->io->output("Applying only foreign keys ...\n");
73 3
            $this->lastSession = $this->driver->getLastSession();
0 ignored issues
show
Documentation Bug introduced by
The method getLastSession does not exist on object<yentu\ChangeLogger>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
74 3
            $this->driver->allowOnly('ForeignKey');
75 3
            $filter = self::FILTER_LAST_SESSION;
76
        }
77
78 10 View Code Duplication
        if (isset($options['force-foreign-keys'])) {
79
            $this->io->output("Applying only foreign keys and skipping on errors ...\n");
80
            $this->lastSession = $this->driver->getLastSession();
0 ignored issues
show
Documentation Bug introduced by
The method getLastSession does not exist on object<yentu\ChangeLogger>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
81
            $this->driver->setSkipOnErrors($options['force-foreign-keys']);
82
            $this->driver->allowOnly('ForeignKey');
83
            $filter = self::FILTER_LAST_SESSION;
84
        }
85
86 10
        if (isset($options['default-ondelete-action'])) {
87
            ForeignKey::$defaultOnDelete = $options['default-ondelete-action'];
88
        }
89
90 10
        if (isset($options['default-onupdate-action'])) {
91
            ForeignKey::$defaultOnUpdate = $options['default-onupdate-action'];
92
        }
93
94 10
        $this->setDefaultSchema($options);
95 10
    }
96
97 10
    private function setDefaultSchema($options)
98
    {
99 10
        global $defaultSchema;
100 10
        if (isset($options['default-schema'])) {
101
            $this->driver->setDefaultSchema($options['default-schema']);
102
            $this->defaultSchema = $options['default-schema'];
103
            $defaultSchema = $this->defaultSchema;
104
        }
105 10
    }
106
107 10
    private function announceMigration($migrations, $path)
108
    {
109 10
        $size = count($migrations);
110 10
        $defaultSchema = null;
111 10
        if ($size > 0) {
112 10
            if (isset($path['default-schema'])) {
113
                $defaultSchema = $path['default-schema'];
114
            }
115 10
            $this->io->output("Running $size migration(s) from '{$path['home']}'");
116 10
            if ($defaultSchema != '') {
117 10
                $this->io->output(" with '$defaultSchema' as the default schema.\n");
118
            }
119
        } else {
120
            $this->io->output("No migrations to run from '{$path['home']}'\n");
121
        }
122 10
    }
123
124 10
    public function getBegin()
125
    {
126 10
        return new Begin($this->defaultSchema);
127
    }
128
129 10
    private static function fillOptions(&$options)
130
    {
131 10
        if (!isset($options['dump-queries'])) {
132 10
            $options['dump-queries'] = false;
133
        }
134 10
        if (!isset($options['dry'])) {
135 10
            $options['dry'] = false;
136
        }
137 10
    }
138
139 10
    public function run()
140
    {
141 10
        global $migrateCommand;
142 10
        global $migrateVariables;
143
144 10
        self::fillOptions($this->options);
145
146 10
        $migrateCommand = $this;
147
148 10
        $this->driver = ChangeLogger::wrap($this->manipulatorFactory->createManipulator(), $this->migrations, $this->io);
149 10
        $this->driver->setDumpQueriesOnly($this->options['dump-queries']);
150 10
        $this->driver->setDryRun($this->options['dry']);
151
152 10
        $totalOperations = 0;
153
154 10
        $filter = self::FILTER_UNRUN;
155 10
        $this->setupOptions($this->options, $filter);
156 10
        DatabaseItem::setDriver($this->driver);
157
158 10
        \yentu\Timer::start();
159 10
        $migrationPaths = $this->migrations->getAllPaths();
160
        //$migrationsToBeRun = [];
161 10
        foreach ($migrationPaths as $path) {
162 10
            $this->setDefaultSchema($path);
163 10
            $migrateVariables = $path['variables'] ?? [];
164 10
            $migrations = $this->filter($this->migrations->getMigrationFiles($path['home']), $filter);
165 10
            $this->announceMigration($migrations, $path);
166 10
            $this->currentPath = $path;
167
168 10
            foreach ($migrations as $migration) {
169 10
                $this->countOperations("{$path['home']}/{$migration['file']}");
170 10
                $this->driver->setVersion($migration['timestamp']);
171 10
                $this->driver->setMigration($migration['migration']);
172 10
                $this->io->output("\nApplying '{$migration['migration']}' migration\n");
173 10
                require "{$path['home']}/{$migration['file']}";
174 10
                DatabaseItem::purge();
175 10
                $this->io->output("\n");
176 10
                $totalOperations += $this->driver->resetOperations();
177
            }
178
        }
179
180 10
        if ($this->driver->getChanges()) {
181 10
            $elapsed = \yentu\Timer::stop();
182 10
            $this->io->output("\nMigration took " . \yentu\Timer::pretty($elapsed) . "\n");
183 10
            $this->io->output($this->driver->getChanges() . " operations performed\n");
184 10
            $this->io->output($totalOperations - $this->driver->getChanges() . " operations skipped\n");
185
        }
186
187 10
        $this->driver->disconnect();
0 ignored issues
show
Documentation Bug introduced by
The method disconnect does not exist on object<yentu\ChangeLogger>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
188 10
    }
189
190 10
    private function filter($migrations, $type = self::FILTER_UNRUN)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
191
    {
192 10
        $filterMethod = "{$type}Filter";
193 10
        return $this->$filterMethod($migrations);
194
    }
195
196 10
    private function countOperations($migrationFile)
197
    {
198 10
        if ($this->dryDriver === null) {
199 10
            $this->dryDriver = clone $this->driver;
200 10
            $this->dryDriver->setDryRun(true);
201
        }
202 10
        $this->io->pushOutputLevel(Io::OUTPUT_LEVEL_0);
203 10
        DatabaseItem::setDriver($this->dryDriver);
204 10
        require "$migrationFile";
205 10
        DatabaseItem::purge();
206 10
        DatabaseItem::setDriver($this->driver);
207 10
        $this->io->popOutputLevel();
208 10
        $this->driver->setExpectedOperations($this->dryDriver->resetOperations());
209 10
    }
210
211
    public function getCurrentPath()
212
    {
213
        return $this->currentPath;
214
    }
215
216 10
    private function unrunFilter($input)
217
    {
218 10
        $output = array();
219 10
        foreach ($input as $migration) {
220 10
            $run = $this->driver->query(
0 ignored issues
show
Documentation Bug introduced by
The method query does not exist on object<yentu\ChangeLogger>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
221 10
                "SELECT count(*) as number_run FROM yentu_history WHERE migration = ? and version = ? and default_schema = ?", 
222 10
                array($migration['migration'], $migration['timestamp'], (string) $this->defaultSchema)
223
            );
224
225 10
            if ($run[0]['number_run'] == 0) {
226 10
                $output[] = $migration;
227
            }
228
        }
229 10
        return $output;
230
    }
231
232 3
    private function lastSessionFilter($input)
233
    {
234 3
        $versions = $this->driver->getSessionVersions($this->lastSession);
0 ignored issues
show
Documentation Bug introduced by
The method getSessionVersions does not exist on object<yentu\ChangeLogger>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
235 3
        $output = array();
236 3
        foreach ($input as $migration) {
237 3
            if (array_search($migration['timestamp'], $versions) !== false) {
238 3
                $output[] = $migration;
239
            }
240
        }
241 3
        return $output;
242
    }
243
244
    public function getChanges()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
245
    {
246
        return $this->driver->getChanges();
247
    }
248
249
    public function setRollbackCommand(Rollback $rollbackCommand)
250
    {
251
        $this->rollbackCommand = $rollbackCommand;
252
    }
253
254
    public function reverseActions()
255
    {
256
        if ($this->driver === null) {
257
            return;
258
        }
259
260
        $this->io->output("Attempting to reverse all changes ... ");
261
        if ($this->getChanges() > 0) {
262
            $this->io->pushOutputLevel(0);
263
            $this->rollbackCommand->run();
264
            $this->io->popOutputLevel();
265
        }
266
        $this->io->output("OK\n");
267
    }
268
269
}
270