Completed
Push — master ( ab8b94...b9208a )
by Christian
03:58
created

CompareVersionsCommand::mapModuleToRow()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8497
c 0
b 0
f 0
cc 6
nc 5
nop 4
1
<?php
2
/*
3
 * this file is part of magerun
4
 *
5
 * @author Tom Klingenberg <https://github.com/ktomk>
6
 */
7
8
namespace N98\Magento\Command\System\Setup;
9
10
use N98\Util\ArrayFunctions;
11
use N98\Util\Console\Helper\Table\Renderer\RendererFactory;
12
use N98\Util\Console\Helper\TableHelper;
13
use N98\Util\JUnitSession;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
18
class CompareVersionsCommand extends AbstractSetupCommand
19
{
20
    /**
21
     * Setup
22
     */
23
    protected function configure()
24
    {
25
        $this
26
            ->setName('sys:setup:compare-versions')
27
            ->addOption('ignore-data', null, InputOption::VALUE_NONE, 'Ignore data updates')
28
            ->addOption('log-junit', null, InputOption::VALUE_REQUIRED, 'Log output to a JUnit xml file.')
29
            ->addOption(
30
                'format',
31
                null,
32
                InputOption::VALUE_OPTIONAL,
33
                'Output Format. One of [' . implode(',', RendererFactory::getFormats()) . ']'
34
            )
35
            ->setDescription('Compare module version with setup_module table.');
36
        $help = <<<HELP
37
Compares module version with saved setup version in `setup_module` table and displays version mismatch.
38
HELP;
39
        $this->setHelp($help);
40
    }
41
42
    /**
43
     * @param InputInterface $input
44
     * @param OutputInterface $output
45
     * @return int
46
     * @throws \Exception
47
     */
48
    protected function execute(InputInterface $input, OutputInterface $output)
49
    {
50
        $this->detectMagento($output, true);
51
52
        if (!$this->initMagento()) {
53
            return 0;
54
        }
55
56
        $junit = $input->getOption('log-junit') ? new JUnitSession($input->getOption('log-junit')) : null;
57
58
        $ignoreDataUpdate = $input->getOption('ignore-data');
59
        if ($ignoreDataUpdate) {
60
            $headers = array('Setup', 'Module', 'DB', 'Status');
61
        } else {
62
            $headers = array('Setup', 'Module', 'DB', 'Data', 'Status');
63
        }
64
65
        $table = $this->getModuleTable($ignoreDataUpdate, $headers, $errorCount);
66
67
        $this->output($input, $output, $headers, $table, $junit, $errorCount);
0 ignored issues
show
Bug introduced by
It seems like $junit defined by $input->getOption('log-j...on('log-junit')) : null on line 56 can be null; however, N98\Magento\Command\Syst...rsionsCommand::output() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
68
69
        return 0;
70
    }
71
72
    /**
73
     * @param array $data
74
     * @param JUnitSession $session
75
     * @throws \Exception
76
     */
77
    protected function logJUnit(array $data, JUnitSession $session)
78
    {
79
        $suite = $session->addTestSuite();
80
        $suite->setName('n98-magerun2: ' . $this->getName());
81
        $suite->setTimestamp(new \DateTime());
82
        $suite->setTime($session->getDuration());
83
84
        $testCase = $suite->addTestCase();
85
        $testCase->setName('Magento Setup Version Test');
86
        $testCase->setClassname('CompareVersionsCommand');
87
88
        if (count($data) > 0) {
89
            foreach ($data as $moduleSetup) {
90
                if (false !== stripos($moduleSetup['Status'], 'error')) {
91
                    $testCase->addFailure(
92
                        'Setup Script Error',
93
                        'MagentoSetupScriptVersionException'
94
                    );
95
                }
96
            }
97
        }
98
99
        $session->save($session->getName());
100
    }
101
102
    /**
103
     * @param bool $ignoreDataUpdate
104
     * @param array $headers
105
     * @param int $errorCount
106
     * @return array
107
     */
108
    private function getModuleTable($ignoreDataUpdate, array $headers, &$errorCount)
109
    {
110
        $errorCount = 0;
111
        $table = array();
112
        $magentoModuleList = $this->getMagentoModuleList();
113
114
        foreach ($magentoModuleList as $name => $module) {
115
            $row = $this->mapModuleToRow($name, $module, $ignoreDataUpdate, $errorCount);
116
117
            if (empty($row)) {
118
                continue;
119
            }
120
121
            if ($ignoreDataUpdate) {
122
                unset($row['Data']);
123
            }
124
125
            $table[] = ArrayFunctions::columnOrder($headers, $row);
126
        }
127
128
        return $table;
129
    }
130
131
    private function testVersionProblem(array $row, $ignoreDataUpdate)
132
    {
133
        $moduleVersion = $row['Module'];
134
        $dbVersion = $row['DB'];
135
        $dataVersion = $row['Data'];
136
137
        if ($moduleVersion === null) {
138
            return true;
139
        }
140
141
        $result = $dbVersion === $moduleVersion;
142
        if (!$ignoreDataUpdate && $result && $dataVersion !== $moduleVersion) {
143
            $result = false;
144
        }
145
146
        return $result;
147
    }
148
149
    /**
150
     * @param int $errorCount
151
     * @return string
152
     */
153
    private function buildSetupResultMessage($errorCount)
154
    {
155
        if (0 === $errorCount) {
156
            return 'No setup errors were found.';
157
        }
158
159
        $message = sprintf(
160
            '%s setup error%s %s found!',
161
            $errorCount,
162
            $errorCount === 1 ? '' : 's',
163
            $errorCount === 1 ? 'was' : 'were'
164
        );
165
166
        return $message;
167
    }
168
169
    /**
170
     * format highlight the status (green/red) and show error'd rows at bottom
171
     *
172
     * @param $table
173
     */
174
    private function sortAndDecorate(&$table)
175
    {
176
        usort($table, function ($a, $b) {
177
            if ($a['Status'] === $b['Status']) {
178
                return strcmp($a['Setup'], $b['Setup']);
179
            }
180
181
            return $a['Status'] !== 'OK';
182
        });
183
184
        array_walk($table, function (&$row) {
185
            $status = $row['Status'];
186
            $availableStatus = array('OK' => 'info', 'Error' => 'error');
187
            $statusString = sprintf(
188
                '<%s>%s</%s>',
189
                $availableStatus[$status],
190
                $status,
191
                $availableStatus[$status]
192
            );
193
            $row['Status'] = $statusString;
194
        });
195
    }
196
197
    /**
198
     * @param $ignoreDataUpdate
199
     * @param $errorCount
200
     * @param $name
201
     * @param $module
202
     * @return array
203
     */
204
    private function mapModuleToRow($name, $module, $ignoreDataUpdate, &$errorCount)
205
    {
206
        $resource = $this->getMagentoModuleResource();
207
208
        $row = array(
209
            'Setup'  => $name,
210
            'Module' => $module['setup_version'],
211
            'DB'     => $resource->getDbVersion($name),
212
            'Data'   => $resource->getDataVersion($name),
213
        );
214
215
        if (empty($row['Module'])
216
            && empty($row['DB'])
217
            && empty($row['Data'])
218
        ) {
219
            return [];
220
        }
221
222
        $test = $this->testVersionProblem($row, $ignoreDataUpdate);
223
224
        if (!$test) {
225
            $errorCount++;
226
        }
227
228
        $row['Status'] = $test ? 'OK' : 'Error';
229
230
        return $row;
231
    }
232
233
    /**
234
     * @param \Symfony\Component\Console\Input\InputInterface $input
235
     * @param \Symfony\Component\Console\Output\OutputInterface $output
236
     * @param array $headers
237
     * @param array $table
238
     * @param JUnitSession $junit
239
     * @param int $errorCount
240
     * @throws \Exception
241
     */
242
    private function output(
243
        InputInterface $input,
244
        OutputInterface $output,
245
        $headers,
246
        $table,
247
        $junit,
248
        $errorCount
249
    ) {
250
        if ($junit) {
251
            $this->logJUnit($table, $junit);
252
        } else {
253
            // sort errors to bottom and decorate status with colors if no output format is specified
254
            if (!$input->getOption('format')) {
255
                $this->sortAndDecorate($table);
256
            }
257
258
            /** @var $table TableHelper */
259
            $tableHelper = $this->getHelper('table');
260
            $tableHelper
261
                ->setHeaders($headers)
262
                ->renderByFormat($output, $table, $input->getOption('format'));
263
264
            // output summary line if no output format is specified
265
            if (!$input->getOption('format')) {
266
                $this->writeSection(
267
                    $output,
268
                    $this->buildSetupResultMessage($errorCount), $errorCount > 0 ? 'error' : 'info'
269
                );
270
            }
271
        }
272
    }
273
}
274