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

Version   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 381
Duplicated Lines 0 %

Test Coverage

Coverage 65.58%

Importance

Changes 0
Metric Value
eloc 154
dl 0
loc 381
ccs 101
cts 154
cp 0.6558
rs 9.1199
c 0
b 0
f 0
wmc 41

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getTime() 0 3 1
A isMigrated() 0 3 1
A getVersion() 0 3 1
A getMigration() 0 3 1
A getConfiguration() 0 3 1
A getExecutionState() 0 11 4
A __construct() 0 9 1
B execute() 0 66 9
A analyze() 0 17 2
A executeScript() 0 31 5
A summarizeStatistics() 0 33 5
A createMigration() 0 3 1
A markNotMigrated() 0 5 1
A createMongoTimestamp() 0 3 1
A createStatistics() 0 3 1
A updateStatisticsAfter() 0 14 3
A markMigrated() 0 15 2
A __toString() 0 3 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\Connection
0 ignored issues
show
Bug introduced by
The type MongoDB\Connection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
45
     */
46
    private $connection;
47
48
    /**
49
     * @var \MongoDB\Database
50
     */
51
    private $db;
52
53
    /**
54
     * @var \AntiMattr\MongoDB\Migrations\AbstractMigration
55
     */
56
    protected $migration;
57
58
    /**
59
     * @var \AntiMattr\MongoDB\Migrations\OutputWriter
60
     */
61
    private $outputWriter;
62
63
    /**
64
     * The version in timestamp format (YYYYMMDDHHMMSS).
65
     *
66
     * @var int
67
     */
68
    private $version;
69
70
    /**
71
     * @var \AntiMattr\MongoDB\Migrations\Collection\Statistics[]
72
     */
73
    private $statistics = [];
74
75
    /**
76
     * @var int
77
     */
78
    private $time;
79
80
    /**
81
     * @var int
82
     */
83
    protected $state = self::STATE_NONE;
84
85 15
    public function __construct(Configuration $configuration, $version, $class)
86
    {
87 15
        $this->configuration = $configuration;
88 15
        $this->outputWriter = $configuration->getOutputWriter();
89 15
        $this->class = $class;
90 15
        $this->connection = $configuration->getConnection();
0 ignored issues
show
Documentation Bug introduced by
It seems like $configuration->getConnection() of type MongoDB\Client is incompatible with the declared type MongoDB\Connection of property $connection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91 15
        $this->db = $configuration->getDatabase();
92 15
        $this->migration = $this->createMigration();
93 15
        $this->version = $version;
94 15
    }
95
96
    /**
97
     * @return \AntiMattr\MongoDB\Migrations\Configuration\Configuration $configuration
98
     */
99 2
    public function getConfiguration()
100
    {
101 2
        return $this->configuration;
102
    }
103
104 2
    public function getExecutionState()
105
    {
106 2
        switch ($this->state) {
107 2
            case self::STATE_PRE:
108
                return 'Pre-Checks';
109 2
            case self::STATE_POST:
110
                return 'Post-Checks';
111 2
            case self::STATE_EXEC:
112
                return 'Execution';
113
            default:
114 2
                return 'No State';
115
        }
116
    }
117
118
    /**
119
     * @return \AntiMattr\MongoDB\Migrations\AbstractMigration
120
     */
121 1
    public function getMigration()
122
    {
123 1
        return $this->migration;
124
    }
125
126
    /**
127
     * @return bool
128
     */
129 1
    public function isMigrated()
130
    {
131 1
        return $this->configuration->hasVersionMigrated($this);
132
    }
133
134
    /**
135
     * Returns the time this migration version took to execute.
136
     *
137
     * @return int $time The time this migration version took to execute
138
     */
139
    public function getTime()
140
    {
141
        return $this->time;
142
    }
143
144
    /**
145
     * @return string $version
146
     */
147 2
    public function getVersion()
148
    {
149 2
        return $this->version;
150
    }
151
152
    /**
153
     * @param \MongoDB\Collection
154
     */
155 4
    public function analyze(Collection $collection)
156
    {
157 4
        $statistics = $this->createStatistics();
158 4
        $statistics->setDatabase($this->db);
159 4
        $statistics->setCollection($collection);
160 4
        $name = $collection->getCollectionName();
161 4
        $this->statistics[$name] = $statistics;
162
163
        try {
164 4
            $statistics->updateBefore();
165 1
        } catch (\Exception $e) {
166 1
            $message = sprintf('     <info>Warning during %s: %s</info>',
167 1
                $this->getExecutionState(),
168 1
                $e->getMessage()
169
            );
170
171 1
            $this->outputWriter->write($message);
172
        }
173 4
    }
174
175
    /**
176
     * Execute this migration version up or down.
177
     *
178
     * @param string $direction The direction to execute the migration
179
     * @param bool   $replay    If the migration is being replayed
180
     *
181
     * @throws \Exception when migration fails
182
     */
183 5
    public function execute($direction, $replay = false)
184
    {
185 5
        if ('down' === $direction && $replay) {
186 1
            throw new AbortException(
187 1
                'Cannot run \'down\' and replay it. Use replay with \'up\''
188
            );
189
        }
190
191
        try {
192 4
            $start = microtime(true);
193
194 4
            $this->state = self::STATE_PRE;
195
196 4
            $this->migration->{'pre' . ucfirst($direction)}($this->db);
197
198 4
            if ('up' === $direction) {
199 2
                $this->outputWriter->write("\n" . sprintf('  <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n");
200
            } else {
201 2
                $this->outputWriter->write("\n" . sprintf('  <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n");
202
            }
203
204 4
            $this->state = self::STATE_EXEC;
205
206 4
            $this->migration->$direction($this->db);
207
208 2
            $this->updateStatisticsAfter();
209
210 2
            if ('up' === $direction) {
211 1
                $this->markMigrated($replay);
212
            } else {
213 1
                $this->markNotMigrated();
214
            }
215
216 2
            $this->summarizeStatistics();
217
218 2
            $this->state = self::STATE_POST;
219 2
            $this->migration->{'post' . ucfirst($direction)}($this->db);
220
221 2
            $end = microtime(true);
222 2
            $this->time = round($end - $start, 2);
223 2
            if ('up' === $direction) {
224 1
                $this->outputWriter->write(sprintf("\n  <info>++</info> migrated (%ss)", $this->time));
225
            } else {
226 1
                $this->outputWriter->write(sprintf("\n  <info>--</info> reverted (%ss)", $this->time));
227
            }
228
229 2
            $this->state = self::STATE_NONE;
230 2
        } catch (SkipException $e) {
231
            // now mark it as migrated
232 2
            if ('up' === $direction) {
233 1
                $this->markMigrated();
234
            } else {
235 1
                $this->markNotMigrated();
236
            }
237
238 2
            $this->outputWriter->write(sprintf("\n  <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
239
240 2
            $this->state = self::STATE_NONE;
241
        } catch (\Exception $e) {
242
            $this->outputWriter->write(sprintf(
243
                '<error>Migration %s failed during %s. Error %s</error>',
244
                $this->version, $this->getExecutionState(), $e->getMessage()
245
            ));
246
247
            $this->state = self::STATE_NONE;
248
            throw $e;
249
        }
250 4
    }
251
252
    /**
253
     * @param \MongoDB\Database
254
     * @param string $file
255
     *
256
     * @return array
257
     *
258
     * @throws RuntimeException
259
     * @throws InvalidArgumentException
260
     * @throws Exception
261
     */
262
    public function executeScript(Database $db, $file)
263
    {
264
        $scripts = $this->configuration->getMigrationsScriptDirectory();
265
        if (null === $scripts) {
0 ignored issues
show
introduced by
The condition null === $scripts is always false.
Loading history...
266
            throw new \RuntimeException('Missing Configuration for migrations script directory');
267
        }
268
269
        $path = realpath($scripts . '/' . $file);
270
        if (!file_exists($path)) {
271
            throw new \InvalidArgumentException(sprintf('Could not execute %s. File does not exist.', $path));
272
        }
273
274
        try {
275
            $js = file_get_contents($path);
276
            if (false === $js) {
277
                throw new \Exception('file_get_contents returned false');
278
            }
279
        } catch (\Exception $e) {
280
            throw $e;
281
        }
282
283
        $result = $db->command(['$eval' => $js, 'nolock' => true]);
284
285
        /* Command throws it's own exceptions if something is wrong, this errmsg no longer exists on MongoDB\DriverCursor
286
          @todo what to do with this, disabled for now
287
        if (isset($result['errmsg'])) {
288
            throw new \Exception($result['errmsg'], isset($result['errno']) ? $result['errno'] : null);
289
        }
290
        */
291
292
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type MongoDB\Driver\Cursor which is incompatible with the documented return type array.
Loading history...
293
    }
294
295
    /**
296
     * markMigrated.
297
     *
298
     * @param bool $replay This is a replayed migration, do an update instead of an insert
299
     */
300 4
    public function markMigrated($replay = false)
301
    {
302 4
        $this->configuration->createMigrationCollection();
303 4
        $collection = $this->configuration->getCollection();
304
305 4
        $document = ['v' => $this->version, 't' => $this->createMongoTimestamp()];
306
307 4
        if ($replay) {
308 1
            $query = ['v' => $this->version];
309
            // If the user asked for a 'replay' of a migration that
310
            // has not been run, it will be inserted anew
311 1
            $options = ['upsert' => true];
312 1
            $collection->updateOne($query, $document, $options);
313
        } else {
314 3
            $collection->insertOne($document);
315
        }
316 4
    }
317
318 3
    public function markNotMigrated()
319
    {
320 3
        $this->configuration->createMigrationCollection();
321 3
        $collection = $this->configuration->getCollection();
322 3
        $collection->deleteOne(['v' => $this->version]);
323 3
    }
324
325 4
    protected function updateStatisticsAfter()
326
    {
327 4
        foreach ($this->statistics as $name => $statistic) {
328
            try {
329 2
                $statistic->updateAfter();
330 1
                $name = $statistic->getCollection()->getCollectionName();
331 1
                $this->statistics[$name] = $statistic;
332 1
            } catch (\Exception $e) {
333 1
                $message = sprintf('     <info>Warning during %s: %s</info>',
334 1
                    $this->getExecutionState(),
335 1
                    $e->getMessage()
336
                );
337
338 2
                $this->outputWriter->write($message);
339
            }
340
        }
341 4
    }
342
343 2
    private function summarizeStatistics()
344
    {
345 2
        foreach ($this->statistics as $key => $statistic) {
346
            $this->outputWriter->write(sprintf("\n     Collection %s\n", $key));
347
348
            $line = '     ';
349
            $line .= 'metric ' . str_repeat(' ', 16 - strlen('metric'));
350
            $line .= 'before ' . str_repeat(' ', 20 - strlen('before'));
351
            $line .= 'after ' . str_repeat(' ', 20 - strlen('after'));
352
            $line .= 'difference ' . str_repeat(' ', 20 - strlen('difference'));
353
354
            $this->outputWriter->write($line . "\n     " . str_repeat('=', 80));
355
            $before = $statistic->getBefore();
356
            $after = $statistic->getAfter();
357
358
            foreach (Statistics::$metrics as $metric) {
359
                $valueBefore = isset($before[$metric]) ? $before[$metric] : 0;
360
                $valueAfter = isset($after[$metric]) ? $after[$metric] : 0;
361
                $difference = $valueAfter - $valueBefore;
362
363
                $nameMessage = $metric . str_repeat(' ', 16 - strlen($metric));
364
                $beforeMessage = $valueBefore . str_repeat(' ', 20 - strlen($valueBefore));
365
                $afterMessage = $valueAfter . str_repeat(' ', 20 - strlen($valueAfter));
366
                $differenceMessage = $difference . str_repeat(' ', 20 - strlen($difference));
367
368
                $line = sprintf(
369
                    '     %s %s %s %s',
370
                    $nameMessage,
371
                    $beforeMessage,
372
                    $afterMessage,
373
                    $differenceMessage
374
                );
375
                $this->outputWriter->write($line);
376
            }
377
        }
378 2
    }
379
380 1
    public function __toString()
381
    {
382 1
        return $this->version;
383
    }
384
385
    /**
386
     * @return \AntiMattr\MongoDB\Migrations\AbstractMigration
387
     */
388 1
    protected function createMigration()
389
    {
390 1
        return new $this->class($this);
391
    }
392
393
    /**
394
     * @return UTCDateTime
395
     */
396
    protected function createMongoTimestamp()
397
    {
398
        return new UTCDateTime();
399
    }
400
401
    /**
402
     * @return \AntiMattr\MongoDB\Migrations\Collection\Statistics
403
     */
404
    protected function createStatistics()
405
    {
406
        return new Statistics();
407
    }
408
}
409