Test Failed
Push — master ( f505f8...9154d8 )
by Théo
02:00
created

AddPrefixCommand   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 310
Duplicated Lines 1.94 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 82.91%

Importance

Changes 0
Metric Value
dl 6
loc 310
ccs 131
cts 158
cp 0.8291
rs 10
c 0
b 0
f 0
wmc 28
lcom 2
cbo 9

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A configure() 0 47 1
B validatePrefix() 0 25 4
A makeAbsolutePath() 3 8 2
B execute() 0 41 2
A validatePaths() 0 18 2
B validateOutputDir() 3 61 8
B retrieveConfig() 0 41 4
A retrievePaths() 0 18 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\Console\Command;
16
17
use Humbug\PhpScoper\Console\Configuration;
18
use Humbug\PhpScoper\Handler\HandleAddPrefix;
19
use Humbug\PhpScoper\Logger\ConsoleLogger;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Exception\RuntimeException;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Style\OutputStyle;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
use Symfony\Component\Filesystem\Filesystem;
29
use Throwable;
30
31
final class AddPrefixCommand extends Command
32
{
33
    /** @internal */
34
    const PATH_ARG = 'paths';
35
    /** @internal */
36
    const PREFIX_OPT = 'prefix';
37
    /** @internal */
38
    const OUTPUT_DIR_OPT = 'output-dir';
39
    /** @internal */
40
    const FORCE_OPT = 'force';
41
    /** @internal */
42
    const STOP_ON_FAILURE_OPT = 'stop-on-failure';
43
    /** @internal */
44
    const CONFIG_FILE = 'config';
45
    /** @internal */
46
    const CONFIG_FILE_DEFAULT = 'scoper.inc.php';
47
48
    private $fileSystem;
49
    private $handle;
50
51
    /**
52
     * @inheritdoc
53
     */
54 22
    public function __construct(Filesystem $fileSystem, HandleAddPrefix $handle)
55
    {
56 22
        parent::__construct();
57
58 22
        $this->fileSystem = $fileSystem;
59 22
        $this->handle = $handle;
60
    }
61
62
    /**
63
     * @inheritdoc
64
     */
65 22
    protected function configure()
66
    {
67
        $this
68 22
            ->setName('add-prefix')
69 22
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
70 22
            ->addArgument(
71 22
                self::PATH_ARG,
72 22
                InputArgument::IS_ARRAY,
73 22
                'The path(s) to process.'
74
            )
75 22
            ->addOption(
76 22
                self::PREFIX_OPT,
77 22
                'p',
78 22
                InputOption::VALUE_REQUIRED,
79 22
                'The namespace prefix to add.'
80
            )
81 22
            ->addOption(
82 22
                self::OUTPUT_DIR_OPT,
83 22
                'o',
84 22
                InputOption::VALUE_REQUIRED,
85 22
                'The output directory in which the prefixed code will be dumped.',
86 22
                'build'
87
            )
88 22
            ->addOption(
89 22
                self::FORCE_OPT,
90 22
                'f',
91 22
                InputOption::VALUE_NONE,
92 22
                'Deletes any existing content in the output directory without any warning.'
93
            )
94 22
            ->addOption(
95 22
                self::STOP_ON_FAILURE_OPT,
96 22
                's',
97 22
                InputOption::VALUE_NONE,
98 22
                'Stops on failure.'
99
            )
100 22
            ->addOption(
101 22
                self::CONFIG_FILE,
102 22
                'c',
103 22
                InputOption::VALUE_REQUIRED,
104 22
                sprintf(
105 22
                    'Configuration file. Will use "%s" if found by default',
106 22
                    self::CONFIG_FILE_DEFAULT
107
                ),
108 22
                null
109
            )
110
        ;
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116 20
    protected function execute(InputInterface $input, OutputInterface $output)
117
    {
118 20
        $io = new SymfonyStyle($input, $output);
119
120 20
        $this->validatePrefix($input);
121 15
        $this->validatePaths($input);
122 15
        $this->validateOutputDir($input, $io);
123
124 15
        $config = $this->retrieveConfig($input, $io);
125
126 13
        $logger = new ConsoleLogger(
127 13
            $this->getApplication(),
128 13
            $io
129
        );
130
131 13
        $logger->outputScopingStart(
132 13
            $input->getOption(self::PREFIX_OPT),
133 13
            $input->getArgument(self::PATH_ARG)
134
        );
135
136 13
        $paths = $this->retrievePaths($input, $config);
137
138
        try {
139 13
            $this->handle->__invoke(
140 13
                $input->getOption(self::PREFIX_OPT),
141 13
                $paths,
142 13
                $input->getOption(self::OUTPUT_DIR_OPT),
143 13
                $config->getPatchers(),
144 13
                $config->getWhitelist(),
145 13
                $config->getGlobalNamespaceWhitelisters(),
146 13
                $input->getOption(self::STOP_ON_FAILURE_OPT),
147 13
                $logger
148
            );
149 1
        } catch (Throwable $throwable) {
150 1
            $logger->outputScopingEndWithFailure();
151
152 1
            throw $throwable;
153
        }
154
155 12
        $logger->outputScopingEnd();
156
    }
157
158 20
    private function validatePrefix(InputInterface $input)
159
    {
160 20
        $prefix = $input->getOption(self::PREFIX_OPT);
161
162 20
        if (null === $prefix) {
163 1
            $prefix = uniqid('PhpScoper');
164
        } else {
165 19
            $prefix = trim($prefix);
166
        }
167
168 20
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
169 20
            $prefix = $matches['prefix'];
170
        }
171
172 20
        if ('' === $prefix) {
173 5
            throw new RuntimeException(
174 5
                sprintf(
175 5
                    'Expected "%s" argument to be a non empty string.',
176 5
                    self::PREFIX_OPT
177
                )
178
            );
179
        }
180
181 15
        $input->setOption(self::PREFIX_OPT, $prefix);
182
    }
183
184 15
    private function validatePaths(InputInterface $input)
185
    {
186 15
        $cwd = getcwd();
187 15
        $fileSystem = $this->fileSystem;
188
189 15
        $paths = array_map(
190 15
            function (string $path) use ($cwd, $fileSystem) {
191 13
                if (false === $fileSystem->isAbsolutePath($path)) {
192 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
193
                }
194
195 13
                return $path;
196 15
            },
197 15
            $input->getArgument(self::PATH_ARG)
198
        );
199
200 15
        $input->setArgument(self::PATH_ARG, $paths);
201
    }
202
203 15
    private function validateOutputDir(InputInterface $input, OutputStyle $io)
204
    {
205 15
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
206
207 15 View Code Duplication
        if (false === $this->fileSystem->isAbsolutePath($outputDir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
209
        }
210
211 15
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
212
213 15
        if (false === $this->fileSystem->exists($outputDir)) {
214 15
            return;
215
        }
216
217
        if (false === is_writable($outputDir)) {
218
            throw new RuntimeException(
219
                sprintf(
220
                    'Expected "<comment>%s</comment>" to be writeable.',
221
                    $outputDir
222
                )
223
            );
224
        }
225
226
        if ($input->getOption(self::FORCE_OPT)) {
227
            $this->fileSystem->remove($outputDir);
228
229
            return;
230
        }
231
232
        if (false === is_dir($outputDir)) {
233
            $canDeleteFile = $io->confirm(
234
                sprintf(
235
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
236
                    .'removed, do you wish to proceed?',
237
                    $outputDir
238
                ),
239
                false
240
            );
241
242
            if (false === $canDeleteFile) {
243
                return;
244
            }
245
246
            $this->fileSystem->remove($outputDir);
247
        } else {
248
            $canDeleteFile = $io->confirm(
249
                sprintf(
250
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
251
                    .' content, do you wish to proceed?',
252
                    $outputDir
253
                ),
254
                false
255
            );
256
257
            if (false === $canDeleteFile) {
258
                return;
259
            }
260
261
            $this->fileSystem->remove($outputDir);
262
        }
263
    }
264
265 15
    private function retrieveConfig(InputInterface $input, OutputStyle $io): Configuration
266
    {
267 15
        $configFile = $input->getOption(self::CONFIG_FILE);
268
269 15
        if (null === $configFile) {
270 14
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
271
272 14
            if (false === file_exists($configFile)) {
273 12
                $io->writeln(
274 12
                    sprintf(
275 12
                        'Config file "%s" not found. Skipping.',
276 12
                        $configFile
277
                    ),
278 12
                    OutputStyle::VERBOSITY_DEBUG
279
                );
280
281 12
                return Configuration::load(null);
282
            }
283
        } else {
284 1
            $configFile = $this->makeAbsolutePath($configFile);
285
        }
286
287 3
        if (false === file_exists($configFile)) {
288 1
            throw new RuntimeException(
289 1
                sprintf(
290 1
                    'Could not find the file "%s".',
291 1
                    $configFile
292
                )
293
            );
294
        }
295
296 2
        $io->writeln(
297 2
            sprintf(
298 2
                'Using the configuration file "%s".',
299 2
                $configFile
300
            ),
301 2
            OutputStyle::VERBOSITY_DEBUG
302
        );
303
304 2
        return Configuration::load($configFile);
305
    }
306
307
    /**
308
     * @param InputInterface $input
309
     * @param Configuration  $configuration
310
     *
311
     * @return string[] List of absolute paths
312
     */
313 13
    private function retrievePaths(InputInterface $input, Configuration $configuration): array
314
    {
315 13
        $paths = $input->getArgument(self::PATH_ARG);
316
317 13
        $finders = $configuration->getFinders();
318
319 13
        foreach ($finders as $finder) {
320
            foreach ($finder as $file) {
321
                $paths[] = $file->getRealPath();
322
            }
323
        }
324
325 13
        if (0 === count($paths)) {
326 2
            return [getcwd()];
327
        }
328
329 11
        return array_unique($paths);
330
    }
331
332 15
    private function makeAbsolutePath(string $path): string
333
    {
334 15 View Code Duplication
        if (false === $this->fileSystem->isAbsolutePath($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335 12
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
336
        }
337
338 15
        return $path;
339
    }
340
}
341