Completed
Push — master ( f14ab5...f0e450 )
by Théo
12:13
created

AddPrefixCommand::validateOutputDir()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 61
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 38.5117

Importance

Changes 0
Metric Value
cc 8
eloc 35
nc 14
nop 2
dl 0
loc 61
ccs 7
cts 32
cp 0.2188
crap 38.5117
rs 7.0047
c 0
b 0
f 0

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
            )
105 23
            ->addOption(
106 23
                self::NO_CONFIG_OPT,
107 23
                null,
108 23
                InputOption::VALUE_NONE,
109 23
            'Do not look for a configuration file.'
110
            )
111
        ;
112
    }
113
114
    /**
115
     * @inheritdoc
116
     */
117 21
    protected function execute(InputInterface $input, OutputInterface $output): int
118
    {
119 21
        $io = new SymfonyStyle($input, $output);
120 21
        $io->writeln('');
121
122 21
        $this->changeWorkingDirectory($input);
123
124 21
        $this->validatePrefix($input);
125 16
        $this->validatePaths($input);
126 16
        $this->validateOutputDir($input, $io);
127
128 16
        $config = $this->retrieveConfig($input, $output, $io);
129
130 14
        $logger = new ConsoleLogger(
131 14
            $this->getApplication(),
132 14
            $io
133
        );
134
135 14
        $logger->outputScopingStart(
136 14
            $input->getOption(self::PREFIX_OPT),
137 14
            $input->getArgument(self::PATH_ARG)
138
        );
139
140 14
        $paths = $this->retrievePaths($input, $config);
141
142
        try {
143 14
            $this->handle->__invoke(
144 14
                $input->getOption(self::PREFIX_OPT),
145 14
                $paths,
146 14
                $input->getOption(self::OUTPUT_DIR_OPT),
147 14
                $config->getPatchers(),
148 14
                $config->getWhitelist(),
149 14
                $config->getGlobalNamespaceWhitelisters(),
150 14
                $input->getOption(self::STOP_ON_FAILURE_OPT),
151 14
                $logger
152
            );
153 1
        } catch (Throwable $throwable) {
154 1
            $logger->outputScopingEndWithFailure();
155
156 1
            throw $throwable;
157
        }
158
159 13
        $logger->outputScopingEnd();
160
161 13
        return 0;
162
    }
163
164 21
    private function validatePrefix(InputInterface $input): void
165
    {
166 21
        $prefix = $input->getOption(self::PREFIX_OPT);
167
168 21
        if (null === $prefix) {
169 1
            $prefix = uniqid('PhpScoper');
170
        } else {
171 20
            $prefix = trim($prefix);
172
        }
173
174 21
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
175 21
            $prefix = $matches['prefix'];
176
        }
177
178 21
        if ('' === $prefix) {
179 5
            throw new RuntimeException(
180 5
                sprintf(
181 5
                    'Expected "%s" argument to be a non empty string.',
182 5
                    self::PREFIX_OPT
183
                )
184
            );
185
        }
186
187 16
        $input->setOption(self::PREFIX_OPT, $prefix);
188
    }
189
190 16
    private function validatePaths(InputInterface $input): void
191
    {
192 16
        $cwd = getcwd();
193 16
        $fileSystem = $this->fileSystem;
194
195 16
        $paths = array_map(
196 16
            function (string $path) use ($cwd, $fileSystem) {
197 14
                if (false === $fileSystem->isAbsolutePath($path)) {
198 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
199
                }
200
201 14
                return $path;
202 16
            },
203 16
            $input->getArgument(self::PATH_ARG)
204
        );
205
206 16
        $input->setArgument(self::PATH_ARG, $paths);
207
    }
208
209 16
    private function validateOutputDir(InputInterface $input, OutputStyle $io): void
210
    {
211 16
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
212
213 16
        if (false === $this->fileSystem->isAbsolutePath($outputDir)) {
214 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
215
        }
216
217 16
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
218
219 16
        if (false === $this->fileSystem->exists($outputDir)) {
220 16
            return;
221
        }
222
223
        if (false === is_writable($outputDir)) {
224
            throw new RuntimeException(
225
                sprintf(
226
                    'Expected "<comment>%s</comment>" to be writeable.',
227
                    $outputDir
228
                )
229
            );
230
        }
231
232
        if ($input->getOption(self::FORCE_OPT)) {
233
            $this->fileSystem->remove($outputDir);
234
235
            return;
236
        }
237
238
        if (false === is_dir($outputDir)) {
239
            $canDeleteFile = $io->confirm(
240
                sprintf(
241
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
242
                    .'removed, do you wish to proceed?',
243
                    $outputDir
244
                ),
245
                false
246
            );
247
248
            if (false === $canDeleteFile) {
249
                return;
250
            }
251
252
            $this->fileSystem->remove($outputDir);
253
        } else {
254
            $canDeleteFile = $io->confirm(
255
                sprintf(
256
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
257
                    .' content, do you wish to proceed?',
258
                    $outputDir
259
                ),
260
                false
261
            );
262
263
            if (false === $canDeleteFile) {
264
                return;
265
            }
266
267
            $this->fileSystem->remove($outputDir);
268
        }
269
    }
270
271 16
    private function retrieveConfig(InputInterface $input, OutputInterface $output, OutputStyle $io): Configuration
272
    {
273 16
        if ($input->getOption(self::NO_CONFIG_OPT)) {
274 12
            $io->writeln(
275 12
                'Loading without configuration file.',
276 12
                OutputStyle::VERBOSITY_DEBUG
277
            );
278
279 12
            return Configuration::load(null);
280
        }
281
282 4
        $configFile = $input->getOption(self::CONFIG_FILE_OPT);
283
284 4
        if (null === $configFile) {
285 3
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
286
287 3
            if (false === file_exists($configFile)) {
288
                $initCommand = $this->getApplication()->find('init');
289
290
                $initInput = new StringInput('');
291
                $initInput->setInteractive($input->isInteractive());
292
293
                $initCommand->run($initInput, $output);
294
295
                $io->writeln(
296
                    sprintf(
297
                        'Config file "<comment>%s</comment>" not found. Skipping.',
298
                        $configFile
299
                    ),
300
                    OutputStyle::VERBOSITY_DEBUG
301
                );
302
303 3
                return Configuration::load(null);
304
            }
305
        } else {
306 1
            $configFile = $this->makeAbsolutePath($configFile);
307
        }
308
309 4
        if (false === file_exists($configFile)) {
310 1
            throw new RuntimeException(
311 1
                sprintf(
312 1
                    'Could not find the file "<comment>%s</comment>".',
313 1
                    $configFile
314
                )
315
            );
316
        }
317
318 3
        $io->writeln(
319 3
            sprintf(
320 3
                'Using the configuration file "<comment>%s</comment>".',
321 3
                $configFile
322
            ),
323 3
            OutputStyle::VERBOSITY_DEBUG
324
        );
325
326 3
        return Configuration::load($configFile);
327
    }
328
329
    /**
330
     * @param InputInterface $input
331
     * @param Configuration  $configuration
332
     *
333
     * @return string[] List of absolute paths
334
     */
335 14
    private function retrievePaths(InputInterface $input, Configuration $configuration): array
336
    {
337 14
        $paths = $input->getArgument(self::PATH_ARG);
338
339 14
        $finders = $configuration->getFinders();
340
341 14
        foreach ($finders as $finder) {
342 1
            foreach ($finder as $file) {
343 1
                $paths[] = $file->getRealPath();
344
            }
345
        }
346
347 14
        if (0 === count($paths)) {
348 2
            return [getcwd()];
349
        }
350
351 12
        return array_unique($paths);
352
    }
353
354 4
    private function makeAbsolutePath(string $path): string
355
    {
356 4
        if (false === $this->fileSystem->isAbsolutePath($path)) {
357 2
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
358
        }
359
360 4
        return $path;
361
    }
362
}
363