Passed
Pull Request — master (#41)
by
unknown
02:55
created

Version   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Test Coverage

Coverage 65.79%

Importance

Changes 0
Metric Value
eloc 151
dl 0
loc 366
ccs 100
cts 152
cp 0.6579
rs 9.1199
c 0
b 0
f 0
wmc 41

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getTime() 0 3 1
A executeScript() 0 22 5
B execute() 0 66 9
A analyze() 0 17 2
A summarizeStatistics() 0 33 5
A createMigration() 0 3 1
A isMigrated() 0 3 1
A markNotMigrated() 0 5 1
A getVersion() 0 3 1
A getMigration() 0 3 1
A createMongoTimestamp() 0 3 1
A createStatistics() 0 3 1
A updateStatisticsAfter() 0 14 3
A getConfiguration() 0 3 1
A getExecutionState() 0 11 4
A markMigrated() 0 15 2
A __toString() 0 3 1
A __construct() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like Version often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Version, and based on these observations, apply Extract Interface, too.

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