Passed
Push — master ( d6ce84...9c42e9 )
by Gaetano
10:07
created

StatusCommand::sortMigrationIndex()   B

Complexity

Conditions 11
Paths 3

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 21
nc 3
nop 2
dl 0
loc 31
ccs 0
cts 0
cp 0
crap 132
rs 7.3166
c 1
b 0
f 0

How to fix   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 Kaliop\eZMigrationBundle\Command;
4
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
use Symfony\Component\Console\Input\InputOption;
8
use Kaliop\eZMigrationBundle\API\Value\Migration;
9
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
10
use Symfony\Component\Console\Helper\Table;
11
12
/**
13
 * Command to display the status of migrations.
14
 *
15
 * @todo add option to skip displaying already executed migrations
16
 * @todo allow sorting migrations by their execution date
17
 */
18
class StatusCommand extends AbstractCommand
19
{
20
    const STATUS_INVALID = -1;
21
22 94
    protected function configure()
23
    {
24 94
        $this->setName('kaliop:migration:status')
25 94
            ->setDescription('View the status of all (or a set of) migrations.')
26 94
            ->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from")
27 94
            ->addOption('sort-by', null, InputOption::VALUE_REQUIRED, "Supported sorting order: name, execution", 'name')
28 94
            ->addOption('summary', null, InputOption::VALUE_NONE, "Only print summary information")
29 94
            ->addOption('todo', null, InputOption::VALUE_NONE, "Only print list of migrations to execute (full path to each)")
30 94
            ->setHelp(<<<EOT
31
The <info>kaliop:migration:status</info> command displays the status of all available migrations:
32
33
    <info>./ezpublish/console kaliop:migration:status</info>
34
35
You can optionally specify the path to migration versions with <info>--path</info>:
36
37
    <info>./ezpublish/console kaliop:migrations:status --path=/path/to/bundle/version_directory --path=/path/to/bundle/version_directory/single_migration_file</info>
38
EOT
39 94
            );
40
    }
41 34
42
    public function execute(InputInterface $input, OutputInterface $output)
43 34
    {
44 34
        $this->setOutput($output);
45
        $this->setVerbosity($output->getVerbosity());
46 34
47
        $migrationsService = $this->getMigrationService();
48 34
49 34
        $migrationDefinitions = $migrationsService->getMigrationsDefinitions($input->getOption('path'));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('path') can also be of type boolean and null and string; however, parameter $paths of Kaliop\eZMigrationBundle...MigrationsDefinitions() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

49
        $migrationDefinitions = $migrationsService->getMigrationsDefinitions(/** @scrutinizer ignore-type */ $input->getOption('path'));
Loading history...
50
        $migrations = $migrationsService->getMigrations();
51 34
52
        if (!count($migrationDefinitions) && !count($migrations)) {
53
            $output->writeln('<info>No migrations found</info>');
54
            return;
55
        }
56
57 34
        // create a unique list of all migrations (coming from db) and definitions (coming from disk)
58 34
        $index = array();
59 34
        foreach ($migrationDefinitions as $migrationDefinition) {
60
            $index[$migrationDefinition->name] = array('definition' => $migrationDefinition);
61 34
        }
62
        foreach ($migrations as $migration) {
63
            if (isset($index[$migration->name])) {
64
                $index[$migration->name]['migration'] = $migration;
65
            } else {
66
                $index[$migration->name] = array('migration' => $migration);
67
68
                // no definition, but a migration is there. Check if the definition sits elsewhere on disk than we expect it to be...
69
                // q: what if we have a loader which does not work with is_file? Could we remove this check?
70
                if ($migration->path != '' && is_file($migration->path)) {
71
                    try {
72
                        $migrationDefinitionCollection = $migrationsService->getMigrationsDefinitions(array($migration->path));
73
                        if (count($migrationDefinitionCollection)) {
74
                            $index[$migration->name]['definition'] = $migrationDefinitionCollection->reset();
75
                        }
76
                    } catch (\Exception $e) {
77
                        /// @todo one day we should be able to limit the kind of exceptions we have to catch here...
78
                    }
79
                }
80
            }
81 34
        }
82
83 34
        if (!$input->getOption('summary')) {
84 34
            $this->sortMigrationIndex($index, $input->getOption('sort-by'));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('sort-by') can also be of type string[]; however, parameter $sortBy of Kaliop\eZMigrationBundle...d::sortMigrationIndex() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
            $this->sortMigrationIndex($index, /** @scrutinizer ignore-type */ $input->getOption('sort-by'));
Loading history...
85
        }
86
87 34
        if (!$input->getOption('summary') && !$input->getOption('todo')) {
88
            if (count($index) > 50000) {
89
                $output->writeln("WARNING: printing the status table might take a while as it contains many rows. Please wait...");
90
            }
91 34
            $output->writeln("\n <info>==</info> All Migrations\n");
92
        }
93
94
        $summary = array(
95
            self::STATUS_INVALID => array('Invalid', 0),
96
            Migration::STATUS_TODO => array('To do', 0),
97
            Migration::STATUS_STARTED => array('Started', 0),
98
            Migration::STATUS_DONE => array('Done', 0),
99
            Migration::STATUS_SUSPENDED => array('Suspended', 0),
100 34
            Migration::STATUS_FAILED => array('Failed', 0),
101
            Migration::STATUS_SKIPPED => array('Skipped', 0),
102 34
            Migration::STATUS_PARTIALLY_DONE => array('Partially done', 0),
103 34
        );
104 34
        $data = array();
105 34
106 34
        $i = 1;
107 34
        foreach ($index as $name => $value) {
108 2
            if (!isset($value['migration'])) {
109 2
                $migrationDefinition = $migrationsService->parseMigrationDefinition($value['definition']);
110
                $notes = '';
111 34
                if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) {
112
                    $notes = '<error>' . $migrationDefinition->parsingError . '</error>';
113 34
                    $summary[self::STATUS_INVALID][1]++;
114
                } else {
115
                    $summary[Migration::STATUS_TODO][1]++;
116 34
                }
117 34
                if ($input->getOption('todo')) {
118 34
                    $data[] = $migrationDefinition->path;
119 34
                } else {
120 34
                    $data[] = array(
121 34
                        $i++,
122
                        $name,
123
                        '<error>not executed</error>',
124
                        '',
125
                        $notes
126
                    );
127
128
                }
129
            } else {
130
                $migration = $value['migration'];
131
132
                if (!isset($summary[$migration->status])) {
133
                    $summary[$migration->status] = array($migration->status, 0);
134
                }
135
                $summary[$migration->status][1]++;
136
                if ($input->getOption('summary')) {
137
                    continue;
138
                }
139
140
                if ($input->getOption('todo')) {
141
                    if ($migration->status == Migration::STATUS_TODO) {
142
                        $data[] = $migration->path;
143
                    }
144
145
                    continue;
146
                }
147
148
                switch ($migration->status) {
149
                    case Migration::STATUS_DONE:
150
                        $status = '<info>executed</info>';
151
                        break;
152
                    case Migration::STATUS_STARTED:
153
                        $status = '<comment>execution started</comment>';
154
                        break;
155
                    case Migration::STATUS_TODO:
156
                        // bold to-migrate!
157
                        $status = '<error>not executed</error>';
158
                        break;
159
                    case Migration::STATUS_SKIPPED:
160
                        $status = '<comment>skipped</comment>';
161
                        break;
162
                    case Migration::STATUS_PARTIALLY_DONE:
163
                        $status = '<comment>partially executed</comment>';
164
                        break;
165
                    case Migration::STATUS_SUSPENDED:
166
                        $status = '<comment>suspended</comment>';
167
                        break;
168
                    case Migration::STATUS_FAILED:
169
                        $status = '<error>failed</error>';
170
                        break;
171
                }
172
                $notes = array();
173
                if ($migration->executionError != '') {
174
                    $notes[] = "<error>{$migration->executionError}</error>";
175
                }
176
                if (!isset($value['definition'])) {
177
                    $notes[] = '<comment>The migration definition file can not be found any more</comment>';
178
                } else {
179
                    $migrationDefinition = $value['definition'];
180
                    if (md5($migrationDefinition->rawDefinition) != $migration->md5) {
181
                        $notes[] = '<comment>The migration definition file has now a different checksum</comment>';
182
                    }
183
                    if ($migrationDefinition->path != $migrationDefinition->path) {
184
                        $notes[] = '<comment>The migration definition file has now moved</comment>';
185
                    }
186
                }
187
                $notes = implode(' ', $notes);
188
                $data[] = array(
189 34
                    $i++,
190
                    $migration->name,
191
                    $status,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $status does not seem to be defined for all execution paths leading up to this point.
Loading history...
192
                    ($migration->executionDate != null ? date("Y-m-d H:i:s", $migration->executionDate) : ''),
193
                    $notes
194 34
                );
195
            }
196
        }
197
198
        if ($input->getOption('todo')) {
199
            foreach ($data as $migrationData) {
200
                echo "$migrationData\n";
201 34
            }
202
            return;
203
        }
204
205
        if ($input->getOption('summary')) {
206
            $output->writeln("\n <info>==</info> Migrations Summary\n");
207
            // do not print info about the not yet supported case
208 34
            unset($summary[Migration::STATUS_PARTIALLY_DONE]);
209
            $data = $summary;
210
            $headers = array('Status', 'Count');
211 34
        } else {
212
            $headers = array('#', 'Migration', 'Status', 'Executed on', 'Notes');
213 34
        }
214 34
215 34
        $table = new Table($output);
216 34
        $table
217
            ->setHeaders($headers)
218
            ->setRows($data);
219
        $table->render();
220
    }
221
222
    /**
223
     * @param array[] $index
224
     * @param string $sortBy
225
     * @throws \Exception
226
     */
227
    protected function sortMigrationIndex(array &$index, $sortBy)
228
    {
229
        switch($sortBy) {
230
            case 'execution':
231
                uasort($index, function($m1, $m2) {
232
                    if (isset($m1['migration']) && $m1['migration']->executionDate !== null) {
233
                        if (isset($m2['migration']) && $m2['migration']->executionDate !== null) {
234
                            return $m1['migration']->executionDate - $m2['migration']->executionDate;
235
                        } else {
236
                            // m2 not executed: send to bottom
237
                            return -1;
238
                        }
239
                    } else {
240
                        if (isset($m2['migration']) && $m2['migration']->executionDate !== null) {
241
                            // m1 not executed: send to bottom
242
                            return 1;
243
                        } else {
244
                            // both not executed: compare by name
245
                            return strcmp(
246
                                isset($m1['migration']) ? $m1['migration']->name : $m1['definition']->name,
247
                                isset($m2['migration']) ? $m2['migration']->name : $m2['definition']->name
248
                            );
249
                        }
250
                    }
251
                });
252
                break;
253
            case 'name':
254
                ksort($index);
255
                break;
256
            default:
257
                throw new \Exception("Unsupported sort order: '$sortBy'");
258
        }
259
    }
260
}
261