Passed
Push — master ( 598295...73f8db )
by Théo
04:39 queued 02:04
created

AddPrefixCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 59
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 46
nc 1
nop 0
dl 0
loc 59
rs 9.597
c 0
b 0
f 0
ccs 45
cts 45
cp 1
crap 1

How to fix   Long Method   

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
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\Exception\RuntimeException;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Input\StringInput;
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 BaseCommand
32
{
33
    private const PATH_ARG = 'paths';
34
    private const PREFIX_OPT = 'prefix';
35
    private const OUTPUT_DIR_OPT = 'output-dir';
36
    private const FORCE_OPT = 'force';
37
    private const STOP_ON_FAILURE_OPT = 'stop-on-failure';
38
    private const CONFIG_FILE_OPT = 'config';
39
    private const CONFIG_FILE_DEFAULT = 'scoper.inc.php';
40
    private const NO_CONFIG_OPT = 'no-config';
41
42
    private $fileSystem;
43
    private $handle;
44
45
    /**
46
     * @inheritdoc
47
     */
48 23
    public function __construct(Filesystem $fileSystem, HandleAddPrefix $handle)
49
    {
50 23
        parent::__construct();
51
52 23
        $this->fileSystem = $fileSystem;
53 23
        $this->handle = $handle;
54
    }
55
56
    /**
57
     * @inheritdoc
58
     */
59 23
    protected function configure(): void
60
    {
61 23
        parent::configure();
62
63
        $this
64 23
            ->setName('add-prefix')
65 23
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
66 23
            ->addArgument(
67 23
                self::PATH_ARG,
68 23
                InputArgument::IS_ARRAY,
69 23
                'The path(s) to process.'
70
            )
71 23
            ->addOption(
72 23
                self::PREFIX_OPT,
73 23
                'p',
74 23
                InputOption::VALUE_REQUIRED,
75 23
                'The namespace prefix to add.'
76
            )
77 23
            ->addOption(
78 23
                self::OUTPUT_DIR_OPT,
79 23
                'o',
80 23
                InputOption::VALUE_REQUIRED,
81 23
                'The output directory in which the prefixed code will be dumped.',
82 23
                'build'
83
            )
84 23
            ->addOption(
85 23
                self::FORCE_OPT,
86 23
                'f',
87 23
                InputOption::VALUE_NONE,
88 23
                'Deletes any existing content in the output directory without any warning.'
89
            )
90 23
            ->addOption(
91 23
                self::STOP_ON_FAILURE_OPT,
92 23
                's',
93 23
                InputOption::VALUE_NONE,
94 23
                'Stops on failure.'
95
            )
96 23
            ->addOption(
97 23
                self::CONFIG_FILE_OPT,
98 23
                'c',
99 23
                InputOption::VALUE_REQUIRED,
100 23
                sprintf(
101 23
                    'Configuration file. Will use "%s" if found by default.',
102 23
                    self::CONFIG_FILE_DEFAULT
103
                ),
104 23
                null
105
            )
106 23
            ->addOption(
107 23
                self::NO_CONFIG_OPT,
108 23
                null,
109 23
                InputOption::VALUE_NONE,
110 23
                sprintf(
111 23
                    'Do not look for a configuration file.',
112 23
                    self::CONFIG_FILE_DEFAULT
113
                ),
114 23
                null
115
            )
116
        ;
117
    }
118
119
    /**
120
     * @inheritdoc
121
     */
122 21
    protected function execute(InputInterface $input, OutputInterface $output): int
123
    {
124 21
        $io = new SymfonyStyle($input, $output);
125 21
        $io->writeln('');
126
127 21
        $this->changeWorkingDirectory($input);
128
129 21
        $this->validatePrefix($input);
130 16
        $this->validatePaths($input);
131 16
        $this->validateOutputDir($input, $io);
132
133 16
        $config = $this->retrieveConfig($input, $output, $io);
134
135 14
        $logger = new ConsoleLogger(
136 14
            $this->getApplication(),
137 14
            $io
138
        );
139
140 14
        $logger->outputScopingStart(
141 14
            $input->getOption(self::PREFIX_OPT),
142 14
            $input->getArgument(self::PATH_ARG)
143
        );
144
145 14
        $paths = $this->retrievePaths($input, $config);
146
147
        try {
148 14
            $this->handle->__invoke(
149 14
                $input->getOption(self::PREFIX_OPT),
150 14
                $paths,
151 14
                $input->getOption(self::OUTPUT_DIR_OPT),
152 14
                $config->getPatchers(),
153 14
                $config->getWhitelist(),
154 14
                $config->getGlobalNamespaceWhitelisters(),
155 14
                $input->getOption(self::STOP_ON_FAILURE_OPT),
156 14
                $logger
157
            );
158 1
        } catch (Throwable $throwable) {
159 1
            $logger->outputScopingEndWithFailure();
160
161 1
            throw $throwable;
162
        }
163
164 13
        $logger->outputScopingEnd();
165
166 13
        return 0;
167
    }
168
169 21
    private function validatePrefix(InputInterface $input): void
170
    {
171 21
        $prefix = $input->getOption(self::PREFIX_OPT);
172
173 21
        if (null === $prefix) {
174 1
            $prefix = uniqid('PhpScoper');
175
        } else {
176 20
            $prefix = trim($prefix);
177
        }
178
179 21
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
180 21
            $prefix = $matches['prefix'];
181
        }
182
183 21
        if ('' === $prefix) {
184 5
            throw new RuntimeException(
185 5
                sprintf(
186 5
                    'Expected "%s" argument to be a non empty string.',
187 5
                    self::PREFIX_OPT
188
                )
189
            );
190
        }
191
192 16
        $input->setOption(self::PREFIX_OPT, $prefix);
193
    }
194
195 16
    private function validatePaths(InputInterface $input): void
196
    {
197 16
        $cwd = getcwd();
198 16
        $fileSystem = $this->fileSystem;
199
200 16
        $paths = array_map(
201 16
            function (string $path) use ($cwd, $fileSystem) {
202 14
                if (false === $fileSystem->isAbsolutePath($path)) {
203 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
204
                }
205
206 14
                return $path;
207 16
            },
208 16
            $input->getArgument(self::PATH_ARG)
209
        );
210
211 16
        $input->setArgument(self::PATH_ARG, $paths);
212
    }
213
214 16
    private function validateOutputDir(InputInterface $input, OutputStyle $io): void
215
    {
216 16
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
217
218 16
        if (false === $this->fileSystem->isAbsolutePath($outputDir)) {
219 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
220
        }
221
222 16
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
223
224 16
        if (false === $this->fileSystem->exists($outputDir)) {
225 16
            return;
226
        }
227
228
        if (false === is_writable($outputDir)) {
229
            throw new RuntimeException(
230
                sprintf(
231
                    'Expected "<comment>%s</comment>" to be writeable.',
232
                    $outputDir
233
                )
234
            );
235
        }
236
237
        if ($input->getOption(self::FORCE_OPT)) {
238
            $this->fileSystem->remove($outputDir);
239
240
            return;
241
        }
242
243
        if (false === is_dir($outputDir)) {
244
            $canDeleteFile = $io->confirm(
245
                sprintf(
246
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
247
                    .'removed, do you wish to proceed?',
248
                    $outputDir
249
                ),
250
                false
251
            );
252
253
            if (false === $canDeleteFile) {
254
                return;
255
            }
256
257
            $this->fileSystem->remove($outputDir);
258
        } else {
259
            $canDeleteFile = $io->confirm(
260
                sprintf(
261
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
262
                    .' content, do you wish to proceed?',
263
                    $outputDir
264
                ),
265
                false
266
            );
267
268
            if (false === $canDeleteFile) {
269
                return;
270
            }
271
272
            $this->fileSystem->remove($outputDir);
273
        }
274
    }
275
276 16
    private function retrieveConfig(InputInterface $input, OutputInterface $output, OutputStyle $io): Configuration
277
    {
278 16
        if ($input->getOption(self::NO_CONFIG_OPT)) {
279 12
            $io->writeln(
280 12
                'Loading without configuration file.',
281 12
                OutputStyle::VERBOSITY_DEBUG
282
            );
283
284 12
            return Configuration::load(null);
285
        }
286
287 4
        $configFile = $input->getOption(self::CONFIG_FILE_OPT);
288
289 4
        if (null === $configFile) {
290 3
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
291
292 3
            if (false === file_exists($configFile)) {
293
                $initCommand = $this->getApplication()->find('init');
294
295
                $initInput = new StringInput('');
296
                $initInput->setInteractive($input->isInteractive());
297
298
                $initCommand->run($initInput, $output);
299
300
                $io->writeln(
301
                    sprintf(
302
                        'Config file "<comment>%s</comment>" not found. Skipping.',
303
                        $configFile
304
                    ),
305
                    OutputStyle::VERBOSITY_DEBUG
306
                );
307
308
                return Configuration::load(null);
309
            }
310
        } else {
311 1
            $configFile = $this->makeAbsolutePath($configFile);
312
        }
313
314 4
        if (false === file_exists($configFile)) {
315 1
            throw new RuntimeException(
316 1
                sprintf(
317 1
                    'Could not find the file "<comment>%s</comment>".',
318 1
                    $configFile
319
                )
320
            );
321
        }
322
323 3
        $io->writeln(
324 3
            sprintf(
325 3
                'Using the configuration file "<comment>%s</comment>".',
326 3
                $configFile
327
            ),
328 3
            OutputStyle::VERBOSITY_DEBUG
329
        );
330
331 3
        return Configuration::load($configFile);
332
    }
333
334
    /**
335
     * @param InputInterface $input
336
     * @param Configuration  $configuration
337
     *
338
     * @return string[] List of absolute paths
339
     */
340 14
    private function retrievePaths(InputInterface $input, Configuration $configuration): array
341
    {
342 14
        $paths = $input->getArgument(self::PATH_ARG);
343
344 14
        $finders = $configuration->getFinders();
345
346 14
        foreach ($finders as $finder) {
347 1
            foreach ($finder as $file) {
348 1
                $paths[] = $file->getRealPath();
349
            }
350
        }
351
352 14
        if (0 === count($paths)) {
353 2
            return [getcwd()];
354
        }
355
356 12
        return array_unique($paths);
357
    }
358
359 4
    private function makeAbsolutePath(string $path): string
360
    {
361 4
        if (false === $this->fileSystem->isAbsolutePath($path)) {
362 2
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
363
        }
364
365 4
        return $path;
366
    }
367
}
368