Completed
Push — master ( 706d4d...7d1021 )
by max
02:05
created

Migration::applyMigration()   D

Complexity

Conditions 14
Paths 394

Size

Total Lines 74
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 74
rs 4.0377
cc 14
eloc 48
nc 394
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace T4web\Migrations\Service;
4
5
use T4web\Migrations\Migration\AbstractMigration;
6
use Zend\Db\Adapter\Adapter;
7
use Zend\Db\Adapter\AdapterAwareInterface;
8
use Zend\Db\Adapter\Exception\InvalidQueryException;
9
use Zend\Db\Sql\Ddl;
10
use Zend\ServiceManager\ServiceLocatorAwareInterface;
11
12
/**
13
 * Main migration logic
14
 */
15
class Migration
16
{
17
    protected $migrationsDir;
18
    protected $migrationsNamespace;
19
    protected $adapter;
20
    /**
21
     * @var \Zend\Db\Adapter\Driver\ConnectionInterface
22
     */
23
    protected $connection;
24
    protected $metadata;
25
    protected $migrationVersionTable;
26
    protected $outputWriter;
27
    protected $serviceLocator;
28
29
    /**
30
     * @return OutputWriter
31
     */
32
    public function getOutputWriter()
33
    {
34
        return $this->outputWriter;
35
    }
36
37
    /**
38
     * @param \Zend\Db\Adapter\Adapter $adapter
39
     * @param                          $metadata
40
     * @param array                    $config
41
     * @param                          $migrationVersionTable
42
     * @param VersionResolver          $versionResolver
43
     * @param OutputWriter             $writer
44
     * @param null                     $serviceLocator
45
     * @throws \Exception
46
     */
47
    public function __construct(
48
        Adapter $adapter,
49
        $metadata,
50
        array $config,
51
        VersionResolver $versionResolver,
52
        $migrationVersionTable,
53
        OutputWriter $writer = null,
54
        $serviceLocator = null
55
    ) {
56
        $this->adapter = $adapter;
57
        $this->metadata = $metadata;
58
        $this->connection = $this->adapter->getDriver()->getConnection();
59
        $this->migrationsDir = $config['dir'];
60
        $this->migrationsNamespace = $config['namespace'];
61
        $this->versionResolver = $versionResolver;
0 ignored issues
show
Bug introduced by
The property versionResolver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
62
        $this->migrationVersionTable = $migrationVersionTable;
63
        $this->outputWriter = $writer;
64
        $this->serviceLocator = $serviceLocator;
65
66
        if (is_null($this->migrationsDir)) {
67
            throw new \Exception('Migrations directory not set!');
68
        }
69
70
        if (is_null($this->migrationsNamespace)) {
71
            throw new \Exception('Unknown namespaces!');
72
        }
73
74
        if (!is_dir($this->migrationsDir)) {
75
            if (!mkdir($this->migrationsDir, 0775)) {
76
                throw new \Exception(sprintf('Failed to create migrations directory %s', $this->migrationsDir));
77
            }
78
        }
79
    }
80
81
    /**
82
     * @return int
83
     */
84
    public function getCurrentVersion()
85
    {
86
        return $this->migrationVersionTable->getCurrentVersion();
87
    }
88
89
    /**
90
     * @param int $version target migration version, if not set all not applied available migrations will be applied
91
     * @param bool $force force apply migration
92
     * @param bool $down rollback migration
93
     * @param bool $fake
94
     * @throws \Exception
95
     */
96
    public function migrate($version = null, $force = false, $down = false, $fake = false)
97
    {
98
        //$migrations = $this->getMigrationClasses($force);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
99
        $migrations = $this->versionResolver->getAll($force);
100
101
        if (!is_null($version) && !$this->hasMigrationVersions($migrations, $version)) {
102
            throw new \Exception(sprintf('Migration version %s is not found!', $version));
103
        }
104
105
        $currentMigrationVersion = $this->migrationVersionTable->getCurrentVersion();
106
        if (!is_null($version) && $version == $currentMigrationVersion && !$force) {
107
            throw new \Exception(sprintf('Migration version %s is current version!', $version));
108
        }
109
110
        if ($version && $force) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
111
            foreach ($migrations as $migration) {
112
                if ($migration['version'] == $version) {
113
                    // if existing migration is forced to apply - delete its information from migrated
114
                    // to avoid duplicate key error
115
                    if (!$down) {
116
                        $this->migrationVersionTable->delete($migration['version']);
117
                    }
118
                    $this->applyMigration($migration, $down, $fake);
119
                    break;
120
                }
121
            }
122
123
            return;
124
        }
125
126
        foreach ($this->versionResolver->getAll() as $migration) {
127
            $this->applyMigration($migration, false, $fake);
128
        }
129
    }
130
131
    /**
132
     * @param \ArrayIterator $migrations
133
     * @return \ArrayIterator
134
     */
135
    public function sortMigrationsByVersionDesc(\ArrayIterator $migrations)
136
    {
137
        $sortedMigrations = clone $migrations;
138
139
        $sortedMigrations->uasort(
140 View Code Duplication
            function ($a, $b) {
0 ignored issues
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...
141
                if ($a['version'] == $b['version']) {
142
                    return 0;
143
                }
144
145
                return ($a['version'] > $b['version']) ? -1 : 1;
146
            }
147
        );
148
149
        return $sortedMigrations;
150
    }
151
152
    /**
153
     * Check migrations classes existence
154
     *
155
     * @param \ArrayIterator $migrations
156
     * @param int            $version
157
     * @return bool
158
     */
159
    public function hasMigrationVersions(\ArrayIterator $migrations, $version)
160
    {
161
        foreach ($migrations as $migration) {
162
            if ($migration['version'] == $version) {
163
                return true;
164
            }
165
        }
166
167
        return false;
168
    }
169
170
    /**
171
     * @param \ArrayIterator $migrations
172
     * @return int
173
     */
174
    public function getMaxMigrationVersion(\ArrayIterator $migrations)
175
    {
176
        $versions = [];
177
        foreach ($migrations as $migration) {
178
            $versions[] = $migration['version'];
179
        }
180
181
        sort($versions, SORT_NUMERIC);
182
        $versions = array_reverse($versions);
183
184
        return count($versions) > 0 ? $versions[0] : 0;
185
    }
186
187
    protected function applyMigration(array $migration, $down = false, $fake = false)
188
    {
189
        $this->connection->beginTransaction();
190
191
        try {
192
            /** @var $migrationObject AbstractMigration */
193
            $migrationObject = new $migration['class']($this->metadata, $this->outputWriter);
194
195
            if ($migrationObject instanceof ServiceLocatorAwareInterface) {
196
                if (is_null($this->serviceLocator)) {
197
                    throw new \RuntimeException(
198
                        sprintf(
199
                            'Migration class %s requires a ServiceLocator, but there is no instance available.',
200
                            get_class($migrationObject)
201
                        )
202
                    );
203
                }
204
205
                $migrationObject->setServiceLocator($this->serviceLocator);
206
            }
207
208
            if ($migrationObject instanceof AdapterAwareInterface) {
209
                if (is_null($this->adapter)) {
210
                    throw new \RuntimeException(
211
                        sprintf(
212
                            'Migration class %s requires an Adapter, but there is no instance available.',
213
                            get_class($migrationObject)
214
                        )
215
                    );
216
                }
217
218
                $migrationObject->setDbAdapter($this->adapter);
219
            }
220
221
            $this->outputWriter->writeLine(
222
                sprintf(
223
                    "%sExecute migration class %s %s",
224
                    $fake ? '[FAKE] ' : '',
225
                    $migration['class'],
226
                    $down ? 'down' : 'up'
227
                )
228
            );
229
230
            if (!$fake) {
231
                $sqlList = $down ? $migrationObject->getDownSql() : $migrationObject->getUpSql();
232
                foreach ($sqlList as $sql) {
233
                    $this->outputWriter->writeLine("Execute query:\n\n" . $sql);
234
                    $this->connection->execute($sql);
235
                }
236
            }
237
238
            if ($down) {
239
                $this->migrationVersionTable->delete($migration['version']);
240
            } else {
241
                $this->migrationVersionTable->save($migration['version']);
242
            }
243
            $this->connection->commit();
244
        } catch (InvalidQueryException $e) {
245
            $this->connection->rollback();
246
            $previousMessage = $e->getPrevious() ? $e->getPrevious()->getMessage() : null;
247
            $msg = sprintf(
248
                '%s: "%s"; File: %s; Line #%d',
249
                $e->getMessage(),
250
                $previousMessage,
251
                $e->getFile(),
252
                $e->getLine()
253
            );
254
            throw new \Exception($msg, $e->getCode(), $e);
255
        } catch (\Exception $e) {
256
            $this->connection->rollback();
257
            $msg = sprintf('%s; File: %s; Line #%d', $e->getMessage(), $e->getFile(), $e->getLine());
258
            throw new \Exception($msg, $e->getCode(), $e);
259
        }
260
    }
261
}
262