Completed
Push — master ( 0a2d22...d0642e )
by James Ekow Abaka
02:53
created

Migrate::getMigrationPathsInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 14
ccs 7
cts 7
cp 1
crap 1
rs 9.4285
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 yentu\database\DatabaseItem;
30
use yentu\DatabaseManipulatorFactory;
31
use yentu\ChangeLogger;
32
use yentu\Yentu;
33
use yentu\database\ForeignKey;
34
use yentu\Reversible;
35
use ntentan\config\Config;
36
use clearice\ConsoleIO;
37
38
/**
39
 * The migrate command for the yentu database migration system. This class is
40
 * responsible for creating and updating items 
41
 */
42
class Migrate implements Reversible
43
{
44
45
    const FILTER_UNRUN = 'unrun';
46
    const FILTER_LAST_SESSION = 'lastSession';
47
48
    private $driver;
49
    private $dryDriver;
50
    private $defaultSchema = false;
51
    private $lastSession;
52
    private $currentPath;
53
    private $yentu;
54
    private $manipulator;
55
    private $config;
56
    private $io;
57
58 10 View Code Duplication
    public function __construct(Yentu $yentu, DatabaseManipulatorFactory $manipulatorFactory, Config $config, ConsoleIO $io)
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...
59
    {
60 10
        $this->manipulator = $manipulatorFactory->createManipulator();
61 10
        $this->yentu = $yentu;
62 10
        $this->config = $config;
63 10
        $this->io = $io;
64 10
    }
65
66 10
    public function setupOptions($options, &$filter)
67
    {
68 10
        if (isset($options['no-foreign-keys'])) {
69 3
            $this->io->output("Ignoring all foreign key constraints ...\n");
70 3
            $this->driver->skip('ForeignKey');
71
        }
72
73 10 View Code Duplication
        if (isset($options['only-foreign-keys'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
74 3
            $this->io->output("Applying only foreign keys ...\n");
75 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...
76 3
            $this->driver->allowOnly('ForeignKey');
77 3
            $filter = self::FILTER_LAST_SESSION;
78
        }
79
80 10 View Code Duplication
        if (isset($options['force-foreign-keys'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
81
            $this->io->output("Applying only foreign keys and skipping on errors ...\n");
82
            $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...
83
            $this->driver->setSkipOnErrors($options['force-foreign-keys']);
84
            $this->driver->allowOnly('ForeignKey');
85
            $filter = self::FILTER_LAST_SESSION;
86
        }
87
88 10
        if (isset($options['default-ondelete-action'])) {
89
            ForeignKey::$defaultOnDelete = $options['default-ondelete-action'];
90
        }
91
92 10
        if (isset($options['default-onupdate-action'])) {
93
            ForeignKey::$defaultOnUpdate = $options['default-onupdate-action'];
94
        }
95
96 10
        $this->setDefaultSchema($options);
97 10
    }
98
99 10
    private function setDefaultSchema($options)
100
    {
101 10
        global $defaultSchema;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
102 10
        if (isset($options['default-schema'])) {
103
            $this->driver->setDefaultSchema($options['default-schema']);
104
            $this->defaultSchema = $options['default-schema'];
105
            $defaultSchema = $this->defaultSchema;
106
        }
107 10
    }
108
109 10
    private function announceMigration($migrations, $path)
110
    {
111 10
        $size = count($migrations);
112 10
        $defaultSchema = null;
113 10
        if ($size > 0) {
114 10
            if (isset($path['default-schema'])) {
115
                $defaultSchema = $path['default-schema'];
116
            }
117 10
            $this->io->output("Running $size migration(s) from '{$path['home']}'");
118 10
            if ($defaultSchema != '') {
119 10
                $this->io->output(" with '$defaultSchema' as the default schema.\n");
120
            }
121
        } else {
122
            $this->io->output("No migrations to run from '{$path['home']}'\n");
123
        }
124 10
    }
125
126 10
    public function getBegin()
127
    {
128 10
        return new \yentu\database\Begin($this->defaultSchema);
129
    }
130
131 10
    private static function fillOptions(&$options)
132
    {
133 10
        if (!isset($options['dump-queries'])) {
134 10
            $options['dump-queries'] = false;
135
        }
136 10
        if (!isset($options['dry'])) {
137 10
            $options['dry'] = false;
138
        }
139 10
    }
140
141 10
    public function run($options = array())
142
    {
143 10
        global $migrateCommand;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
144 10
        global $migrateVariables;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
145
146 10
        self::fillOptions($options);
147
148 10
        $migrateCommand = $this;
149
150 10
        if ($options['dump-queries'] !== true) {
151 10
            $this->yentu->greet();
152
        }
153
154 10
        $this->driver = ChangeLogger::wrap($this->manipulator, $this->yentu, $this->io);
155 10
        $this->driver->setDumpQueriesOnly($options['dump-queries']);
156 10
        $this->driver->setDryRun($options['dry']);
157
158 10
        $totalOperations = 0;
159
160 10
        $filter = self::FILTER_UNRUN;
161 10
        $this->setupOptions($options, $filter);
162 10
        DatabaseItem::setDriver($this->driver);
163
164 10
        \yentu\Timer::start();
165 10
        $migrationPaths = $this->getMigrationPathsInfo();
166 10
        $migrationsToBeRun = [];
167 10
        foreach ($migrationPaths as $path) {
168 10
            $this->setDefaultSchema($path);
169 10
            $migrateVariables = $path['variables'] ?? [];
170 10
            $migrations = $this->filter($this->yentu->getMigrations($path['home']), $filter);
171 10
            $this->announceMigration($migrations, $path);
172 10
            $this->currentPath = $path;
173
174 10
            foreach ($migrations as $migration) {
175 10
                $this->countOperations("{$path['home']}/{$migration['file']}");
176 10
                $this->driver->setVersion($migration['timestamp']);
177 10
                $this->driver->setMigration($migration['migration']);
178 10
                $this->io->output("\nApplying '{$migration['migration']}' migration\n");
179 10
                require "{$path['home']}/{$migration['file']}";
180 10
                DatabaseItem::purge();
181 10
                $this->io->output("\n");
182 10
                $totalOperations += $this->driver->resetOperations();
183
            }
184
        }
185
186 10
        if ($this->driver->getChanges()) {
187 10
            $elapsed = \yentu\Timer::stop();
188 10
            $this->io->output("\nMigration took " . \yentu\Timer::pretty($elapsed) . "\n");
189 10
            $this->io->output($this->driver->getChanges() . " operations performed\n");
190 10
            $this->io->output($totalOperations - $this->driver->getChanges() . " operations skipped\n");
191
        }
192
193 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...
194 10
    }
195
196 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...
197
    {
198 10
        $filterMethod = "{$type}Filter";
199 10
        return $this->$filterMethod($migrations);
200
    }
201
202 10
    private function countOperations($migrationFile)
203
    {
204 10
        if ($this->dryDriver === null) {
205 10
            $this->dryDriver = clone $this->driver;
206 10
            $this->dryDriver->setDryRun(true);
207
        }
208 10
        $this->io->pushOutputLevel(ConsoleIO::OUTPUT_LEVEL_0);
209 10
        DatabaseItem::setDriver($this->dryDriver);
210 10
        require "$migrationFile";
211 10
        DatabaseItem::purge();
212 10
        DatabaseItem::setDriver($this->driver);
213 10
        $this->io->popOutputLevel();
214 10
        $this->driver->setExpectedOperations($this->dryDriver->resetOperations());
215 10
    }
216
217
    public function getCurrentPath()
218
    {
219
        return $this->currentPath;
220
    }
221
222 10
    private function unrunFilter($input)
223
    {
224 10
        $output = array();
225 10
        foreach ($input as $migration) {
226 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...
227 10
                "SELECT count(*) as number_run FROM yentu_history WHERE migration = ? and version = ? and default_schema = ?", 
228 10
                array($migration['migration'], $migration['timestamp'], (string) $this->defaultSchema)
229
            );
230
231 10
            if ($run[0]['number_run'] == 0) {
232 10
                $output[] = $migration;
233
            }
234
        }
235 10
        return $output;
236
    }
237
238 3
    private function lastSessionFilter($input)
239
    {
240 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...
241 3
        $output = array();
242 3
        foreach ($input as $migration) {
243 3
            if (array_search($migration['timestamp'], $versions) !== false) {
244 3
                $output[] = $migration;
245
            }
246
        }
247 3
        return $output;
248
    }
249
    
250 10
    private function getMigrationPathsInfo()
251
    {
252 10
        $variables = $this->config->get('variables', []);
253 10
        $otherMigrations = $this->config->get('other_migrations', []);
254
255 10
        return array_merge(
256
            array(
257
            array(
258 10
                'home' => $this->yentu->getPath('migrations'),
259 10
                'variables' => $variables
260
            )
261 10
            ), $otherMigrations
262
        );
263
    }    
264
265
    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...
266
    {
267
        return $this->driver->getChanges();
268
    }
269
270
    public function reverse()
271
    {
272
        if ($this->driver === null) {
273
            return;
274
        }
275
276
        $this->io->output("Attempting to reverse all changes ... ");
277
        if ($this->getChanges() > 0) {
278
            $this->io->pushOutputLevel(0);
279
            $rollback = $this->yentu->getContainer()->resolve(\yentu\commands\Rollback::class);
0 ignored issues
show
Bug introduced by
The method getContainer() does not seem to exist on object<yentu\Yentu>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
280
            $rollback->run(array());
281
            $this->io->popOutputLevel();
282
        }
283
        $this->io->output("OK\n");
284
    }
285
286
}
287