Passed
Branch master (c976c6)
by Théo
03:51
created

AddPrefixCommand::scopeFile()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 35
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4.0039

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 5
nop 8
dl 0
loc 35
ccs 15
cts 16
cp 0.9375
crap 4.0039
rs 9.7666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Autoload\ScoperAutoloadGenerator;
18
use Humbug\PhpScoper\Configuration;
19
use Humbug\PhpScoper\Console\ScoperLogger;
20
use Humbug\PhpScoper\Scoper;
21
use Humbug\PhpScoper\Scoper\ConfigurableScoper;
22
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
23
use Humbug\PhpScoper\Whitelist;
24
use Symfony\Component\Console\Exception\RuntimeException;
25
use Symfony\Component\Console\Input\InputArgument;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Input\InputOption;
28
use Symfony\Component\Console\Input\StringInput;
29
use Symfony\Component\Console\Output\OutputInterface;
30
use Symfony\Component\Console\Style\OutputStyle;
31
use Symfony\Component\Console\Style\SymfonyStyle;
32
use Symfony\Component\Filesystem\Filesystem;
33
use Throwable;
34
use function count;
35
use function Humbug\PhpScoper\get_common_path;
36
37
final class AddPrefixCommand extends BaseCommand
38
{
39
    private const PATH_ARG = 'paths';
40
    private const PREFIX_OPT = 'prefix';
41
    private const OUTPUT_DIR_OPT = 'output-dir';
42
    private const FORCE_OPT = 'force';
43
    private const STOP_ON_FAILURE_OPT = 'stop-on-failure';
44
    private const CONFIG_FILE_OPT = 'config';
45
    private const CONFIG_FILE_DEFAULT = 'scoper.inc.php';
46
    private const NO_CONFIG_OPT = 'no-config';
47
48
    private $fileSystem;
49
    private $scoper;
50
    private $init = false;
51
52
    /**
53
     * @inheritdoc
54
     */
55 16
    public function __construct(Filesystem $fileSystem, Scoper $scoper)
56
    {
57 16
        parent::__construct();
58
59 16
        $this->fileSystem = $fileSystem;
60 16
        $this->scoper = new ConfigurableScoper($scoper);
61
    }
62
63
    /**
64
     * @inheritdoc
65
     */
66 16
    protected function configure(): void
67
    {
68 16
        parent::configure();
69
70
        $this
71 16
            ->setName('add-prefix')
72 16
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
73 16
            ->addArgument(
74 16
                self::PATH_ARG,
75 16
                InputArgument::IS_ARRAY,
76 16
                'The path(s) to process.'
77
            )
78 16
            ->addOption(
79 16
                self::PREFIX_OPT,
80 16
                'p',
81 16
                InputOption::VALUE_REQUIRED,
82 16
                'The namespace prefix to add.'
83
            )
84 16
            ->addOption(
85 16
                self::OUTPUT_DIR_OPT,
86 16
                'o',
87 16
                InputOption::VALUE_REQUIRED,
88 16
                'The output directory in which the prefixed code will be dumped.',
89 16
                'build'
90
            )
91 16
            ->addOption(
92 16
                self::FORCE_OPT,
93 16
                'f',
94 16
                InputOption::VALUE_NONE,
95 16
                'Deletes any existing content in the output directory without any warning.'
96
            )
97 16
            ->addOption(
98 16
                self::STOP_ON_FAILURE_OPT,
99 16
                's',
100 16
                InputOption::VALUE_NONE,
101 16
                'Stops on failure.'
102
            )
103 16
            ->addOption(
104 16
                self::CONFIG_FILE_OPT,
105 16
                'c',
106 16
                InputOption::VALUE_REQUIRED,
107 16
                sprintf(
108 16
                    'Configuration file. Will use "%s" if found by default.',
109 16
                    self::CONFIG_FILE_DEFAULT
110
                )
111
            )
112 16
            ->addOption(
113 16
                self::NO_CONFIG_OPT,
114 16
                null,
115 16
                InputOption::VALUE_NONE,
116 16
            'Do not look for a configuration file.'
117
            )
118
        ;
119
    }
120
121
    /**
122
     * @inheritdoc
123
     */
124 14
    protected function execute(InputInterface $input, OutputInterface $output): int
125
    {
126 14
        $io = new SymfonyStyle($input, $output);
127 14
        $io->writeln('');
128
129 14
        $this->changeWorkingDirectory($input);
130
131 14
        $this->validatePrefix($input);
132 14
        $this->validatePaths($input);
133 14
        $this->validateOutputDir($input, $io);
134
135 14
        $config = $this->retrieveConfig($input, $output, $io);
136 12
        $output = $input->getOption(self::OUTPUT_DIR_OPT);
137
138 12
        if ([] !== $config->getWhitelistedFiles()) {
139
            $this->scoper = $this->scoper->withWhitelistedFiles(...$config->getWhitelistedFiles());
140
        }
141
142 12
        $logger = new ScoperLogger(
143 12
            $this->getApplication(),
144 12
            $io
145
        );
146
147 12
        $logger->outputScopingStart(
148 12
            $config->getPrefix(),
149 12
            $input->getArgument(self::PATH_ARG)
150
        );
151
152
        try {
153 12
            $this->scopeFiles(
154 12
                $config->getPrefix(),
155 12
                $config->getFilesWithContents(),
156 12
                $output,
157 12
                $config->getPatchers(),
158 12
                $config->getWhitelist(),
159 12
                $input->getOption(self::STOP_ON_FAILURE_OPT),
160 12
                $logger
161
            );
162
        } catch (Throwable $throwable) {
163
            $this->fileSystem->remove($output);
164
165
            $logger->outputScopingEndWithFailure();
166
167
            throw $throwable;
168
        }
169
170 12
        $logger->outputScopingEnd();
171
172 12
        return 0;
173
    }
174
175
    /**
176
     * @var callable[]
177
     */
178 12
    private function scopeFiles(
179
        string $prefix,
180
        array $filesWithContents,
181
        string $output,
182
        array $patchers,
183
        Whitelist $whitelist,
184
        bool $stopOnFailure,
185
        ScoperLogger $logger
186
    ): void {
187
        // Creates output directory if does not already exist
188 12
        $this->fileSystem->mkdir($output);
189
190 12
        $logger->outputFileCount(count($filesWithContents));
191
192 12
        $vendorDirs = [];
193 12
        $commonPath = get_common_path(array_keys($filesWithContents));
194
195 12
        foreach ($filesWithContents as [$inputFilePath, $inputContents]) {
196 12
            $outputFilePath = $output.str_replace($commonPath, '', $inputFilePath);
197
198 12
            $pattern = '~((?:.*)\\'.DIRECTORY_SEPARATOR.'vendor)\\'.DIRECTORY_SEPARATOR.'.*~';
199 12
            if (preg_match($pattern, $outputFilePath, $matches)) {
200
                $vendorDirs[$matches[1]] = true;
201
            }
202
203 12
            $this->scopeFile(
204 12
                $inputFilePath,
205 12
                $inputContents,
206 12
                $outputFilePath,
207 12
                $prefix,
208 12
                $patchers,
209 12
                $whitelist,
210 12
                $stopOnFailure,
211 12
                $logger
212
            );
213
        }
214
215 12
        $vendorDirs = array_keys($vendorDirs);
216
217 12
        usort(
218 12
            $vendorDirs,
219
            static function ($a, $b) {
220
                return strlen($b) <=> strlen($a);
221 12
            }
222
        );
223
224 12
        $vendorDir = (0 === count($vendorDirs)) ? null : $vendorDirs[0];
225
226 12
        if (null !== $vendorDir) {
227
            $autoload = (new ScoperAutoloadGenerator($whitelist))->dump($prefix);
228
229
            $this->fileSystem->dumpFile($vendorDir.'/scoper-autoload.php', $autoload);
230
        }
231
    }
232
233
    /**
234
     * @param callable[] $patchers
235
     */
236 12
    private function scopeFile(
237
        string $inputFilePath,
238
        string $inputContents,
239
        string $outputFilePath,
240
        string $prefix,
241
        array $patchers,
242
        Whitelist $whitelist,
243
        bool $stopOnFailure,
244
        ScoperLogger $logger
245
    ): void {
246
        try {
247 12
            $scoppedContent = $this->scoper->scope($inputFilePath, $inputContents, $prefix, $patchers, $whitelist);
248 2
        } catch (Throwable $throwable) {
249 2
            $exception = new ParsingException(
250 2
                sprintf(
251 2
                    'Could not parse the file "%s".',
252 2
                    $inputFilePath
253
                ),
254 2
                0,
255 2
                $throwable
256
            );
257
258 2
            if ($stopOnFailure) {
259
                throw $exception;
260
            }
261
262 2
            $logger->outputWarnOfFailure($inputFilePath, $exception);
263
264 2
            $scoppedContent = file_get_contents($inputFilePath);
265
        }
266
267 12
        $this->fileSystem->dumpFile($outputFilePath, $scoppedContent);
268
269 12
        if (false === isset($exception)) {
270 11
            $logger->outputSuccess($inputFilePath);
271
        }
272
    }
273
274 14
    private function validatePrefix(InputInterface $input): void
275
    {
276 14
        $prefix = $input->getOption(self::PREFIX_OPT);
277
278 14
        if (null !== $prefix && 1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
1 ignored issue
show
Bug introduced by
It seems like $prefix can also be of type string[]; however, parameter $subject of preg_match() 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

278
        if (null !== $prefix && 1 === preg_match('/(?<prefix>.*?)\\\\*$/', /** @scrutinizer ignore-type */ $prefix, $matches)) {
Loading history...
279 13
            $prefix = $matches['prefix'];
280
        }
281
282 14
        $input->setOption(self::PREFIX_OPT, $prefix);
283
    }
284
285 14
    private function validatePaths(InputInterface $input): void
286
    {
287 14
        $cwd = getcwd();
288 14
        $fileSystem = $this->fileSystem;
289
290 14
        $paths = array_map(
291
            static function (string $path) use ($cwd, $fileSystem) {
292 9
                if (false === $fileSystem->isAbsolutePath($path)) {
293
                    return $cwd.DIRECTORY_SEPARATOR.$path;
294
                }
295
296 9
                return $path;
297 14
            },
298 14
            $input->getArgument(self::PATH_ARG)
299
        );
300
301 14
        $input->setArgument(self::PATH_ARG, $paths);
302
    }
303
304 14
    private function validateOutputDir(InputInterface $input, OutputStyle $io): void
305
    {
306 14
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
307
308 14
        if (false === $this->fileSystem->isAbsolutePath($outputDir)) {
309 3
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
310
        }
311
312 14
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
313
314 14
        if (false === $this->fileSystem->exists($outputDir)) {
315 14
            return;
316
        }
317
318
        if (false === is_writable($outputDir)) {
319
            throw new RuntimeException(
320
                sprintf(
321
                    'Expected "<comment>%s</comment>" to be writeable.',
322
                    $outputDir
323
                )
324
            );
325
        }
326
327
        if ($input->getOption(self::FORCE_OPT)) {
328
            $this->fileSystem->remove($outputDir);
329
330
            return;
331
        }
332
333
        if (false === is_dir($outputDir)) {
334
            $canDeleteFile = $io->confirm(
335
                sprintf(
336
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
337
                    .'removed, do you wish to proceed?',
338
                    $outputDir
339
                ),
340
                false
341
            );
342
343
            if (false === $canDeleteFile) {
344
                return;
345
            }
346
347
            $this->fileSystem->remove($outputDir);
348
        } else {
349
            $canDeleteFile = $io->confirm(
350
                sprintf(
351
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
352
                    .' content, do you wish to proceed?',
353
                    $outputDir
354
                ),
355
                false
356
            );
357
358
            if (false === $canDeleteFile) {
359
                return;
360
            }
361
362
            $this->fileSystem->remove($outputDir);
363
        }
364
    }
365
366 14
    private function retrieveConfig(InputInterface $input, OutputInterface $output, OutputStyle $io): Configuration
367
    {
368 14
        $prefix = $input->getOption(self::PREFIX_OPT);
369
370 14
        if ($input->getOption(self::NO_CONFIG_OPT)) {
371 10
            $io->writeln(
372 10
                'Loading without configuration file.',
373 10
                OutputInterface::VERBOSITY_DEBUG
374
            );
375
376 10
            $config = Configuration::load();
377
378 10
            if (null !== $prefix) {
379 9
                $config = $config->withPrefix($prefix);
380
            }
381
382 10
            if (null === $config->getPrefix()) {
383 1
                $config = $config->withPrefix($this->generateRandomPrefix());
384
            }
385
386 10
            return $this->retrievePaths($input, $config);
387
        }
388
389 4
        $configFile = $input->getOption(self::CONFIG_FILE_OPT);
390
391 4
        if (null === $configFile) {
392 3
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
393
394 3
            if (false === file_exists($configFile) && false === $this->init) {
395
                $this->init = true;
396
397
                $initCommand = $this->getApplication()->find('init');
398
399
                $initInput = new StringInput('');
400
                $initInput->setInteractive($input->isInteractive());
401
402
                $initCommand->run($initInput, $output);
403
404
                $io->writeln(
405
                    sprintf(
406
                        'Config file "<comment>%s</comment>" not found. Skipping.',
407
                        $configFile
408
                    ),
409
                    OutputInterface::VERBOSITY_DEBUG
410
                );
411
412
                return self::retrieveConfig($input, $output, $io);
413
            }
414
415 3
            if ($this->init) {
416
                $configFile = null;
417
            }
418
        } else {
419 1
            $configFile = $this->makeAbsolutePath($configFile);
420
        }
421
422 4
        if (null === $configFile) {
423
            $io->writeln(
424
                'Loading without configuration file.',
425
                OutputInterface::VERBOSITY_DEBUG
426
            );
427 4
        } elseif (false === file_exists($configFile)) {
428 1
            throw new RuntimeException(
429 1
                sprintf(
430 1
                    'Could not find the configuration file "%s".',
431 1
                    $configFile
432
                )
433
            );
434
        } else {
435 3
            $io->writeln(
436 3
                sprintf(
437 3
                    'Using the configuration file "%s".',
438 3
                    $configFile
439
                ),
440 3
                OutputInterface::VERBOSITY_DEBUG
441
            );
442
        }
443
444 3
        $config = Configuration::load($configFile);
445 2
        $config = $this->retrievePaths($input, $config);
446
447 2
        if (null !== $prefix) {
448 2
            $config = $config->withPrefix($prefix);
449
        }
450
451 2
        if (null === $config->getPrefix()) {
452
            $config = $config->withPrefix($this->generateRandomPrefix());
453
        }
454
455 2
        return $config;
456
    }
457
458 12
    private function retrievePaths(InputInterface $input, Configuration $config): Configuration
459
    {
460
        // Checks if there is any path included and if note use the current working directory as the include path
461 12
        $paths = $input->getArgument(self::PATH_ARG);
462
463 12
        if (0 === count($paths) && 0 === count($config->getFilesWithContents())) {
464 3
            $paths = [getcwd()];
465
        }
466
467 12
        return $config->withPaths($paths);
468
    }
469
470 4
    private function makeAbsolutePath(string $path): string
471
    {
472 4
        if (false === $this->fileSystem->isAbsolutePath($path)) {
473 4
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
474
        }
475
476 4
        return $path;
477
    }
478
479 1
    private function generateRandomPrefix(): string
480
    {
481 1
        return uniqid('_PhpScoper', false);
482
    }
483
}
484