Passed
Push — master ( a44f6a...91a76b )
by mon
02:41
created

UpdateCommand::compareMaps()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 17
nc 3
nop 3
dl 0
loc 20
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
namespace FileEye\MimeMap\Command;
4
5
use SebastianBergmann\Comparator\ComparisonFailure;
6
use SebastianBergmann\Comparator\Factory;
7
use SebastianBergmann\Diff\Differ;
8
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
9
use Symfony\Component\Console\Command\Command;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Yaml\Yaml;
14
use FileEye\MimeMap\Map\AbstractMap;
15
use FileEye\MimeMap\MapHandler;
16
use FileEye\MimeMap\MapUpdater;
17
18
/**
19
 * A Symfony application command to update the MIME type to extension map.
20
 */
21
class UpdateCommand extends Command
22
{
23
    /**
24
     * {@inheritdoc}
25
     */
26
    protected function configure()
27
    {
28
        $this
29
            ->setName('update')
30
            ->setDescription('Updates the MIME-type-to-extension map. Executes the commands in the script file specified by --script, then writes the map to the PHP file where the PHP --class is defined.')
31
            ->addOption(
32
                'script',
33
                null,
34
                InputOption::VALUE_REQUIRED,
35
                'File name of the script containing the sequence of commands to execute to build the default map.',
36
                MapUpdater::getDefaultMapBuildFile()
37
            )
38
            ->addOption(
39
                'class',
40
                null,
41
                InputOption::VALUE_REQUIRED,
42
                'The fully qualified class name of the PHP class storing the map.',
43
                MapHandler::DEFAULT_MAP_CLASS
44
            )
45
            ->addOption(
46
                'diff',
47
                null,
48
                InputOption::VALUE_NONE,
49
                'Report updates.'
50
            )
51
            ->addOption(
52
                'fail-on-diff',
53
                null,
54
                InputOption::VALUE_NONE,
55
                'Exit with an error when a difference is found. Map will not be updated.'
56
            )
57
        ;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    protected function execute(InputInterface $input, OutputInterface $output)
64
    {
65
        $updater = new MapUpdater();
66
        $updater->selectBaseMap(MapUpdater::DEFAULT_BASE_MAP_CLASS);
67
68
        // Executes on the base map the script commands.
69
        $commands = Yaml::parse(file_get_contents($input->getOption('script')));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('script') can also be of type string[]; however, parameter $filename of file_get_contents() 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

69
        $commands = Yaml::parse(file_get_contents(/** @scrutinizer ignore-type */ $input->getOption('script')));
Loading history...
70
        foreach ($commands as $command) {
71
            $output->writeln("<info>{$command[0]} ...</info>");
72
            try {
73
                $errors = call_user_func_array([$updater, $command[1]], $command[2]);
74
                if (!empty($errors)) {
75
                    foreach ($errors as $error) {
76
                        $output->writeln("<comment>$error.</comment>");
77
                    }
78
                }
79
            } catch (\Exception $e) {
80
                $output->writeln('<error>' . $e->getMessage() . '</error>');
81
                exit(2);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
82
            }
83
        }
84
85
        // Load the map to be changed.
86
        MapHandler::setDefaultMapClass($input->getOption('class'));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('class') can also be of type string[]; however, parameter $map_class of FileEye\MimeMap\MapHandler::setDefaultMapClass() 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

86
        MapHandler::setDefaultMapClass(/** @scrutinizer ignore-type */ $input->getOption('class'));
Loading history...
87
        $current_map = MapHandler::map();
88
89
        // Check if anything got changed.
90
        $write = true;
91
        if ($input->getOption('diff')) {
92
            $write = false;
93
            foreach ([
94
                't' => 'MIME types',
95
                'a' => 'MIME type aliases',
96
                'e' => 'extensions',
97
            ] as $key => $desc) {
98
                try {
99
                    $output->writeln("<info>Checking changes to {$desc} ...</info>");
100
                    $this->compareMaps($current_map, $updater->getMap(), $key);
101
                } catch (\RuntimeException $e) {
102
                    $output->writeln("<comment>Changes to {$desc} mapping:</comment>");
103
                    $output->writeln($e->getMessage());
104
                    $write = true;
105
                }
106
            }
107
        }
108
109
        // Fail on diff if required.
110
        if ($write && $input->getOption('diff') && $input->getOption('fail-on-diff')) {
111
            throw new \RuntimeException('Changes to mapping detected, aborting.');
112
        }
113
114
        // If changed, save the new map to the PHP file.
115
        if ($write) {
116
            $updater->writeMapToPhpClassFile($current_map->getFileName());
117
            $output->writeln('<comment>Code updated.</comment>');
118
        } else {
119
            $output->writeln('<info>No changes to mapping.</info>');
120
        }
121
122
        // Reset the new map's map array.
123
        $updater->getMap()->reset();
124
    }
125
126
    /**
127
     * Compares two type-to-extension maps by section.
128
     *
129
     * @param AbstractMap $old_map
130
     *   The first map to compare.
131
     * @param AbstractMap $new_map
132
     *   The second map to compare.
133
     * @param string $section
134
     *   The first-level array key to compare: 't' or 'e' or 'a'.
135
     *
136
     * @throws \RuntimeException with diff details if the maps differ.
137
     *
138
     * @return bool
139
     *   True if the maps are equal.
140
     */
141
    protected function compareMaps(AbstractMap $old_map, AbstractMap $new_map, $section)
142
    {
143
        $old_map->sort();
144
        $new_map->sort();
145
        $old = $old_map->getMapArray();
146
        $new = $new_map->getMapArray();
147
148
        $factory = new Factory;
149
        $comparator = $factory->getComparatorFor($old[$section], $new[$section]);
150
        try {
151
            $comparator->assertEquals($old[$section], $new[$section]);
152
            return true;
153
        } catch (ComparisonFailure $failure) {
154
            $old_string = var_export($old[$section], true);
155
            $new_string = var_export($new[$section], true);
156
            if (class_exists('\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder')) {
157
                $differ = new Differ(new UnifiedDiffOutputBuilder("--- Removed\n+++ Added\n"));
158
                throw new \RuntimeException($differ->diff($old_string, $new_string));
159
            } else {
160
                throw new \RuntimeException(' ');
161
            }
162
        }
163
    }
164
}
165