Passed
Pull Request — master (#42)
by
unknown
05:57
created

Version::execute()   B

Complexity

Conditions 9
Paths 117

Size

Total Lines 66
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 9.3455

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 41
c 1
b 0
f 0
nc 117
nop 2
dl 0
loc 66
ccs 31
cts 37
cp 0.8378
crap 9.3455
rs 7.5951

How to fix   Long Method   

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
/*
4
 * This file is part of the AntiMattr MongoDB Migrations Library, a library by Matthew Fitzgerald.
5
 *
6
 * (c) 2014 Matthew Fitzgerald
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace AntiMattr\MongoDB\Migrations;
13
14
use AntiMattr\MongoDB\Migrations\Collection\Statistics;
15
use AntiMattr\MongoDB\Migrations\Configuration\Configuration;
16
use AntiMattr\MongoDB\Migrations\Exception\SkipException;
17
use AntiMattr\MongoDB\Migrations\Exception\AbortException;
18
use \MongoDB\Collection;
19
use \MongoDB\Database;
20
use Exception;
21
use MongoDB\BSON\UTCDateTime;
22
23
/**
24
 * @author Matthew Fitzgerald <[email protected]>
25
 */
26
class Version
27
{
28
    const STATE_NONE = 0;
29
    const STATE_PRE = 1;
30
    const STATE_EXEC = 2;
31
    const STATE_POST = 3;
32
33
    /**
34
     * @var string
35
     */
36
    private $class;
37
38
    /**
39
     * @var \AntiMattr\MongoDB\Migrations\Configuration\Configuration
40
     */
41
    private $configuration;
42
43
    /**
44
     * @var \MongoDB\Database
45
     */
46
    private $db;
47
48
    /**
49
     * @var \AntiMattr\MongoDB\Migrations\AbstractMigration
50
     */
51
    protected $migration;
52
53
    /**
54
     * @var \AntiMattr\MongoDB\Migrations\OutputWriter
55
     */
56
    private $outputWriter;
57
58
    /**
59
     * The version in timestamp format (YYYYMMDDHHMMSS).
60
     *
61
     * @var int
62
     */
63
    private $version;
64
65
    /**
66
     * @var \AntiMattr\MongoDB\Migrations\Collection\Statistics[]
67
     */
68
    private $statistics = [];
69
70
    /**
71
     * @var int
72
     */
73
    private $time;
74
75
    /**
76
     * @var int
77
     */
78
    protected $state = self::STATE_NONE;
79
80 16
    public function __construct(Configuration $configuration, $version, $class)
81
    {
82 16
        $this->configuration = $configuration;
83 16
        $this->outputWriter = $configuration->getOutputWriter();
84 16
        $this->class = $class;
85 16
        $this->db = $configuration->getDatabase();
86 16
        $this->migration = $this->createMigration();
87 16
        $this->version = $version;
88 16
    }
89
90
    /**
91
     * @return \AntiMattr\MongoDB\Migrations\Configuration\Configuration $configuration
92
     */
93 3
    public function getConfiguration()
94
    {
95 3
        return $this->configuration;
96
    }
97
98 2
    public function getExecutionState()
99
    {
100 2
        switch ($this->state) {
101 2
            case self::STATE_PRE:
102
                return 'Pre-Checks';
103 2
            case self::STATE_POST:
104
                return 'Post-Checks';
105 2
            case self::STATE_EXEC:
106
                return 'Execution';
107
            default:
108 2
                return 'No State';
109
        }
110
    }
111
112
    /**
113
     * @return \AntiMattr\MongoDB\Migrations\AbstractMigration
114
     */
115 1
    public function getMigration()
116
    {
117 1
        return $this->migration;
118
    }
119
120
    /**
121
     * @return bool
122
     */
123 1
    public function isMigrated()
124
    {
125 1
        return $this->configuration->hasVersionMigrated($this);
126
    }
127
128
    /**
129
     * Returns the time this migration version took to execute.
130
     *
131
     * @return int $time The time this migration version took to execute
132
     */
133
    public function getTime()
134
    {
135
        return $this->time;
136
    }
137
138
    /**
139
     * @return string $version
140
     */
141 3
    public function getVersion()
142
    {
143 3
        return $this->version;
144
    }
145
146
    /**
147
     * @param \MongoDB\Collection
148
     */
149 4
    public function analyze(Collection $collection)
150
    {
151 4
        $statistics = $this->createStatistics();
152 4
        $statistics->setDatabase($this->db);
153 4
        $statistics->setCollection($collection);
154 4
        $name = $collection->getCollectionName();
155 4
        $this->statistics[$name] = $statistics;
156
157
        try {
158 4
            $statistics->updateBefore();
159 1
        } catch (\Exception $e) {
160 1
            $message = sprintf('     <info>Warning during %s: %s</info>',
161 1
                $this->getExecutionState(),
162 1
                $e->getMessage()
163
            );
164
165 1
            $this->outputWriter->write($message);
166
        }
167 4
    }
168
169
    /**
170
     * Execute this migration version up or down.
171
     *
172
     * @param string $direction The direction to execute the migration
173
     * @param bool   $replay    If the migration is being replayed
174
     *
175
     * @throws \Exception when migration fails
176
     */
177 5
    public function execute($direction, $replay = false)
178
    {
179 5
        if ('down' === $direction && $replay) {
180 1
            throw new AbortException(
181 1
                'Cannot run \'down\' and replay it. Use replay with \'up\''
182
            );
183
        }
184
185
        try {
186 4
            $start = microtime(true);
187
188 4
            $this->state = self::STATE_PRE;
189
190 4
            $this->migration->{'pre' . ucfirst($direction)}($this->db);
191
192 4
            if ('up' === $direction) {
193 2
                $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n");
194
            } else {
195 2
                $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n");
196
            }
197
198 4
            $this->state = self::STATE_EXEC;
199
200 4
            $this->migration->$direction($this->db);
201
202 2
            $this->updateStatisticsAfter();
203
204 2
            if ('up' === $direction) {
205 1
                $this->markMigrated($replay);
206
            } else {
207 1
                $this->markNotMigrated();
208
            }
209
210 2
            $this->summarizeStatistics();
211
212 2
            $this->state = self::STATE_POST;
213 2
            $this->migration->{'post' . ucfirst($direction)}($this->db);
214
215 2
            $end = microtime(true);
216 2
            $this->time = round($end - $start, 2);
217 2
            if ('up' === $direction) {
218 1
                $this->outputWriter->write(sprintf("\n  <info>++</info> migrated (%ss)", $this->time));
219
            } else {
220 1
                $this->outputWriter->write(sprintf("\n  <info>--</info> reverted (%ss)", $this->time));
221
            }
222
223 2
            $this->state = self::STATE_NONE;
224 2
        } catch (SkipException $e) {
225
            // now mark it as migrated
226 2
            if ('up' === $direction) {
227 1
                $this->markMigrated();
228
            } else {
229 1
                $this->markNotMigrated();
230
            }
231
232 2
            $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
233
234 2
            $this->state = self::STATE_NONE;
235
        } catch (\Exception $e) {
236
            $this->outputWriter->write(sprintf(
237
                '<error>Migration %s failed during %s. Error %s</error>',
238
                $this->version, $this->getExecutionState(), $e->getMessage()
239
            ));
240
241
            $this->state = self::STATE_NONE;
242
            throw $e;
243
        }
244 4
    }
245
246
    /**
247
     * @param \MongoDB\Database
248
     * @param string $file
249
     *
250
     * @return array
251
     *
252
     * @throws RuntimeException
253
     * @throws InvalidArgumentException
254
     * @throws Exception
255
     */
256
    public function executeScript(Database $db, $file)
257
    {
258
        $scripts = $this->configuration->getMigrationsScriptDirectory();
259
        if (null === $scripts) {
0 ignored issues
show
introduced by
The condition null === $scripts is always false.
Loading history...
260
            throw new \RuntimeException('Missing Configuration for migrations script directory');
261
        }
262
263
        $path = realpath($scripts . '/' . $file);
264
        if (!file_exists($path)) {
265
            throw new \InvalidArgumentException(sprintf('Could not execute %s. File does not exist.', $path));
266
        }
267
268
        try {
269
            $js = file_get_contents($path);
270
            if (false === $js) {
271
                throw new \Exception('file_get_contents returned false');
272
            }
273
        } catch (\Exception $e) {
274
            throw $e;
275
        }
276
277
        return $db->command(['$eval' => $js, 'nolock' => true]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $db->command(arra...$js, 'nolock' => true)) returns the type MongoDB\Driver\Cursor which is incompatible with the documented return type array.
Loading history...
278
    }
279
280
    /**
281
     * markMigrated.
282
     *
283
     * @param bool $replay This is a replayed migration, do an update instead of an insert
284
     */
285 4
    public function markMigrated($replay = false)
286
    {
287 4
        $this->configuration->createMigrationCollection();
288 4
        $collection = $this->configuration->getCollection();
289
290 4
        $document = ['v' => $this->version, 't' => $this->createMongoTimestamp()];
291
292 4
        if ($replay) {
293 1
            $query = ['v' => $this->version];
294
            // If the user asked for a 'replay' of a migration that
295
            // has not been run, it will be inserted anew
296 1
            $options = ['upsert' => true];
297 1
            $collection->updateOne($query, $document, $options);
298
        } else {
299 3
            $collection->insertOne($document);
300
        }
301 4
    }
302
303 3
    public function markNotMigrated()
304
    {
305 3
        $this->configuration->createMigrationCollection();
306 3
        $collection = $this->configuration->getCollection();
307 3
        $collection->deleteOne(['v' => $this->version]);
308 3
    }
309
310 4
    protected function updateStatisticsAfter()
311
    {
312 4
        foreach ($this->statistics as $name => $statistic) {
313
            try {
314 2
                $statistic->updateAfter();
315 1
                $name = $statistic->getCollection()->getCollectionName();
316 1
                $this->statistics[$name] = $statistic;
317 1
            } catch (\Exception $e) {
318 1
                $message = sprintf('     <info>Warning during %s: %s</info>',
319 1
                    $this->getExecutionState(),
320 1
                    $e->getMessage()
321
                );
322
323 2
                $this->outputWriter->write($message);
324
            }
325
        }
326 4
    }
327
328 2
    private function summarizeStatistics()
329
    {
330 2
        foreach ($this->statistics as $key => $statistic) {
331
            $this->outputWriter->write(sprintf("\n     Collection %s\n", $key));
332
333
            $line = '     ';
334
            $line .= 'metric ' . str_repeat(' ', 16 - strlen('metric'));
335
            $line .= 'before ' . str_repeat(' ', 20 - strlen('before'));
336
            $line .= 'after ' . str_repeat(' ', 20 - strlen('after'));
337
            $line .= 'difference ' . str_repeat(' ', 20 - strlen('difference'));
338
339
            $this->outputWriter->write($line . "\n     " . str_repeat('=', 80));
340
            $before = $statistic->getBefore();
341
            $after = $statistic->getAfter();
342
343
            foreach (Statistics::$metrics as $metric) {
344
                $valueBefore = isset($before[$metric]) ? $before[$metric] : 0;
345
                $valueAfter = isset($after[$metric]) ? $after[$metric] : 0;
346
                $difference = $valueAfter - $valueBefore;
347
348
                $nameMessage = $metric . str_repeat(' ', 16 - strlen($metric));
349
                $beforeMessage = $valueBefore . str_repeat(' ', 20 - strlen($valueBefore));
350
                $afterMessage = $valueAfter . str_repeat(' ', 20 - strlen($valueAfter));
351
                $differenceMessage = $difference . str_repeat(' ', 20 - strlen($difference));
352
353
                $line = sprintf(
354
                    '     %s %s %s %s',
355
                    $nameMessage,
356
                    $beforeMessage,
357
                    $afterMessage,
358
                    $differenceMessage
359
                );
360
                $this->outputWriter->write($line);
361
            }
362
        }
363 2
    }
364
365 1
    public function __toString()
366
    {
367 1
        return (string) $this->version;
368
    }
369
370
    /**
371
     * @return \AntiMattr\MongoDB\Migrations\AbstractMigration
372
     */
373 2
    protected function createMigration()
374
    {
375 2
        return new $this->class($this);
376
    }
377
378
    /**
379
     * @return UTCDateTime
380
     */
381
    protected function createMongoTimestamp()
382
    {
383
        return new UTCDateTime();
384
    }
385
386
    /**
387
     * @return \AntiMattr\MongoDB\Migrations\Collection\Statistics
388
     */
389
    protected function createStatistics()
390
    {
391
        return new Statistics();
392
    }
393
}
394