CompareCommand::execute()   C
last analyzed

Complexity

Conditions 12
Paths 268

Size

Total Lines 84
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 12

Importance

Changes 0
Metric Value
cc 12
eloc 48
c 0
b 0
f 0
nc 268
nop 2
dl 0
loc 84
rs 5.2833
ccs 48
cts 48
cp 1
crap 12

How to fix   Long Method    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 Incenteev\TranslationCheckerBundle\Command;
4
5
use Symfony\Component\Console\Command\Command;
6
use Symfony\Component\Console\Input\InputArgument;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use Symfony\Component\Translation\Catalogue\TargetOperation;
11
use Symfony\Component\Translation\MessageCatalogue;
12
use Symfony\Component\Translation\TranslatorBagInterface;
13
use Symfony\Component\Yaml\Yaml;
14
15
/**
16
 * @final
17
 */
18
class CompareCommand extends Command
19 18
{
20
    private TranslatorBagInterface $exposingTranslator;
21 18
22
    public function __construct(TranslatorBagInterface $exposingTranslator)
23 18
    {
24 18
        parent::__construct();
25
26 18
        $this->exposingTranslator = $exposingTranslator;
27
    }
28 18
29 18
    protected function configure(): void
30 18
    {
31 18
        $this->setName('incenteev:translation:compare')
32 18
            ->setDescription('Compares two translation catalogues to ensure they are in sync')
33 18
            ->addArgument('locale', InputArgument::REQUIRED, 'The locale being checked')
34 18
            ->addArgument('source', InputArgument::OPTIONAL, 'The source of the comparison', 'en')
35 18
            ->addOption('obsolete-only', null, InputOption::VALUE_NONE, 'Report only obsolete keys')
36 18
            ->addOption('domain', 'd', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The domains being compared')
37
            ->addOption('whitelist-file', 'w', InputOption::VALUE_REQUIRED, 'Path to a YAML whitelist file')
38
            ->setHelp(<<<EOF
39
The <info>%command.name%</info> command compares 2 translation catalogues to
40
ensure they are in sync. If there is missing keys or obsolete keys in the target
41
catalogue, the command will exit with an error code.
42
43
When running the command in verbose mode, the translation keys will also be displayed.
44
<info>php %command.full_name% fr --verbose</info>
45
46
The <info>--domain</info> option allows to restrict the domains being checked.
47
It can be specified several times to check several domains. If the option is not passed,
48
all domains will be compared.
49
50
The <info>--obsolete-only</info> option allows to check only obsolete keys, and ignore any
51
missing keys.
52
53
The <info>--whitelist-file</info> option allows to define a whitelist of keys which are
54
ignored from the comparison (they are never reported as missing or as obsolete). This
55 18
file must be a Yaml file where keys are domains, and values are an array of whitelisted
56
EOF
57 18
            );
58
    }
59 18
60 18
    protected function execute(InputInterface $input, OutputInterface $output): int
61
    {
62
        $sourceCatalogue = $this->exposingTranslator->getCatalogue($input->getArgument('source'));
63 18
        $comparedCatalogue = $this->exposingTranslator->getCatalogue($input->getArgument('locale'));
64
65 18
        // Change the locale of the catalogue as DiffOperation requires operating on a single locale
66
        $catalogue = new MessageCatalogue($sourceCatalogue->getLocale(), $comparedCatalogue->all());
67 18
68 18
        $operation = new TargetOperation($catalogue, $sourceCatalogue);
69 18
70 4
        $domains = $operation->getDomains();
71 4
        $restrictedDomains = $input->getOption('domain');
72
        if (!empty($restrictedDomains)) {
73
            $domains = array_intersect($domains, $restrictedDomains);
74 18
            $output->writeln(sprintf('<comment>Checking the domains %s</comment>', implode(', ', $domains)));
75
        }
76 18
77 18
        $checkMissing = !$input->getOption('obsolete-only');
78
79 18
        $whitelistFile = $input->getOption('whitelist-file');
80 5
        $whitelist = array();
81 1
82
        if (null !== $whitelistFile) {
83 1
            if (!file_exists($whitelistFile)) {
84
                $output->writeln(sprintf('<error>The whitelist file "%s" does not exist.</error>', $whitelistFile));
85
86 4
                return 1;
87
            }
88 4
89 1
            $whitelist = Yaml::parseFile($whitelistFile);
90
91 1
            if (!is_array($whitelist)) {
92
                $output->writeln(sprintf('<error>The whitelist file "%s" is invalid. It must be a Yaml file containing a map.</error>', $whitelistFile));
93
94
                return 1;
95 16
            }
96
            /** @var array<string, string[]> $whitelist */
97 16
        }
98 16
99 16
        $valid = true;
100 16
101
        foreach ($domains as $domain) {
102 16
            $missingMessages = $checkMissing ? $operation->getNewMessages($domain) : array();
103 2
            $obsoleteMessages = $operation->getObsoleteMessages($domain);
104 2
            $written = false;
105 2
106
            if (isset($whitelist[$domain])) {
107
                $domainWhitelist = array_flip($whitelist[$domain]);
108 16
                $missingMessages = array_diff_key($missingMessages, $domainWhitelist);
109 7
                $obsoleteMessages = array_diff_key($obsoleteMessages, $domainWhitelist);
110 7
            }
111 7
112
            if (!empty($missingMessages)) {
113 7
                $valid = false;
114
                $written = true;
115
                $output->writeln(sprintf('<comment>%s</comment> messages are missing in the <info>%s</info> domain', count($missingMessages), $domain));
116 16
117 6
                $this->displayMessages($output, $missingMessages);
118 6
            }
119 6
120
            if (!empty($obsoleteMessages)) {
121 6
                $valid = false;
122
                $written = true;
123
                $output->writeln(sprintf('<comment>%s</comment> messages are obsolete in the <info>%s</info> domain', count($obsoleteMessages), $domain));
124 16
125 10
                $this->displayMessages($output, $obsoleteMessages);
126
            }
127
128
            if ($written) {
129 16
                $output->writeln('');
130 6
            }
131 6
        }
132 6
133 6
        if ($valid) {
134
            $output->writeln(sprintf(
135
                '<info>The <comment>%s</comment> catalogue is in sync with the <comment>%s</comment> one.</info>',
136 6
                $input->getArgument('locale'),
137
                $input->getArgument('source')
138
            ));
139 10
140
            return 0;
141
        }
142 10
143
        return 1;
144 10
    }
145 8
146
    /**
147
     * @param array<string, string> $messages
148 2
     */
149 2
    private function displayMessages(OutputInterface $output, array $messages): void
150
    {
151 2
        if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
152 2
            return;
153
        }
154
155
        foreach ($messages as $key => $translation) {
156
            $output->writeln('    '.$key);
157
        }
158
        $output->writeln('');
159
    }
160
}
161