Passed
Pull Request — master (#65)
by Pádraic
02:54
created

AddPrefixCommand::validateReplaceStrings()   C

Complexity

Conditions 10
Paths 22

Size

Total Lines 82
Code Lines 48

Duplication

Lines 7
Ratio 8.54 %

Code Coverage

Tests 6
CRAP Score 76.3739

Importance

Changes 0
Metric Value
cc 10
eloc 48
nc 22
nop 1
dl 7
loc 82
ccs 6
cts 47
cp 0.1277
crap 76.3739
rs 5.4646
c 0
b 0
f 0

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
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\Handler\HandleAddPrefix;
18
use Humbug\PhpScoper\Logger\ConsoleLogger;
19
use Humbug\PhpScoper\Scoper\StringReplacer;
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 Symfony\Component\Yaml\Yaml;
30
use Throwable;
31
32
final class AddPrefixCommand extends Command
33
{
34
    /** @internal */
35
    const PATH_ARG = 'paths';
36
    /** @internal */
37
    const PREFIX_OPT = 'prefix';
38
    /** @internal */
39
    const OUTPUT_DIR_OPT = 'output-dir';
40
    /** @internal */
41
    const FORCE_OPT = 'force';
42
    /** @internal */
43
    const REPLACE_STRINGS_OPT = 'replace-strings';
44
    /** @internal */
45
    const REPLACE_STRINGS_DEFAULT = 'scoper.yml';
46
47
    private $fileSystem;
48
    private $handle;
49
    private $stringReplacer;
50
51
    /**
52
     * @inheritdoc
53
     */
54 16
    public function __construct(Filesystem $fileSystem, HandleAddPrefix $handle, StringReplacer $stringReplacer)
55
    {
56 16
        parent::__construct();
57
58 16
        $this->fileSystem = $fileSystem;
59 16
        $this->handle = $handle;
60 16
        $this->stringReplacer = $stringReplacer;
61 16
    }
62
63
    /**
64
     * @inheritdoc
65
     */
66 16
    protected function configure()
67
    {
68
        $this
69 16
            ->setName('add-prefix')
70 16
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
71 16
            ->addArgument(
72 16
                self::PATH_ARG,
73 16
                InputArgument::IS_ARRAY,
74 16
                'The path(s) to process.'
75
            )
76 16
            ->addOption(
77 16
                self::PREFIX_OPT,
78 16
                'p',
79 16
                InputOption::VALUE_REQUIRED,
80 16
                'The namespace prefix to add'
81
            )
82 16
            ->addOption(
83 16
                self::OUTPUT_DIR_OPT,
84 16
                'o',
85 16
                InputOption::VALUE_REQUIRED,
86 16
                'The output directory in which the prefixed code will be dumped.',
87 16
                'build'
88
            )
89 16
            ->addOption(
90 16
                self::REPLACE_STRINGS_OPT,
91 16
                'r',
92 16
                InputOption::VALUE_REQUIRED,
93 16
                'File in which string replacements are defined.'
94
            )
95 16
            ->addOption(
96 16
                self::FORCE_OPT,
97 16
                'f',
98 16
                InputOption::VALUE_NONE,
99 16
                'Deletes any existing content in the output directory without any warning'
100
            )
101
        ;
102 16
    }
103
104
    /**
105
     * @inheritdoc
106
     */
107 14
    protected function execute(InputInterface $input, OutputInterface $output)
108
    {
109 14
        $io = new SymfonyStyle($input, $output);
110
111 14
        $this->validatePrefix($input);
112 9
        $this->validatePaths($input);
113 9
        $this->validateOutputDir($input, $io);
114
115 9
        $stringReplacements = $this->validateReplaceStrings($input);
116 9
        if (null !== $stringReplacements) {
117
            $this->stringReplacer->configureReplaceMap($stringReplacements);
118
        }
119
120 9
        $logger = new ConsoleLogger(
121 9
            $this->getApplication(),
122 9
            $io
123
        );
124
125 9
        $logger->outputScopingStart(
126 9
            $input->getOption(self::PREFIX_OPT),
127 9
            $input->getArgument(self::PATH_ARG)
128
        );
129
130
        try {
131 9
            $this->handle->__invoke(
132 9
                $input->getOption(self::PREFIX_OPT),
133 9
                $input->getArgument(self::PATH_ARG),
134 9
                $input->getOption(self::OUTPUT_DIR_OPT),
135 9
                $logger
136
            );
137 1
        } catch (Throwable $throwable) {
138 1
            $logger->outputScopingEndWithFailure();
139
140 1
            throw $throwable;
141
        }
142
143 8
        $logger->outputScopingEnd();
144 8
    }
145
146 14
    private function validatePrefix(InputInterface $input)
147
    {
148 14
        $prefix = $input->getOption(self::PREFIX_OPT);
149
150 14
        if (null === $prefix) {
151 1
            $prefix = uniqid('PhpScoper');
152
        } else {
153 13
            $prefix = trim($prefix);
154
        }
155
156 14
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
157 14
            $prefix = $matches['prefix'];
158
        }
159
160 14
        if ('' === $prefix) {
161 5
            throw new RuntimeException(
162 5
                sprintf(
163 5
                    'Expected "%s" argument to be a non empty string.',
164 5
                    self::PREFIX_OPT
165
                )
166
            );
167
        }
168
169 9
        $input->setOption(self::PREFIX_OPT, $prefix);
170 9
    }
171
172 9
    private function validatePaths(InputInterface $input)
173
    {
174 9
        $cwd = getcwd();
175 9
        $fileSystem = $this->fileSystem;
176
177 9
        $paths = array_map(
178 View Code Duplication
            function (string $path) use ($cwd, $fileSystem) {
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...
179 8
                if (false === $fileSystem->isAbsolutePath($path)) {
180 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
181
                }
182
183 8
                return $path;
184 9
            },
185 9
            $input->getArgument(self::PATH_ARG)
186
        );
187
188 9
        if (0 === count($paths)) {
189 1
            $paths[] = $cwd;
190
        }
191
192 9
        $input->setArgument(self::PATH_ARG, $paths);
193 9
    }
194
195 9
    private function validateOutputDir(InputInterface $input, OutputStyle $io)
196
    {
197 9
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
198
199 9 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...
200 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
201
        }
202
203 9
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
204
205 9
        if (false === $this->fileSystem->exists($outputDir)) {
206 9
            return;
207
        }
208
209
        if (false === is_writable($outputDir)) {
210
            throw new RuntimeException(
211
                sprintf(
212
                    'Expected "<comment>%s</comment>" to be writeable.',
213
                    $outputDir
214
                )
215
            );
216
        }
217
218
        if ($input->getOption(self::FORCE_OPT)) {
219
            $this->fileSystem->remove($outputDir);
220
221
            return;
222
        }
223
224
        if (false === is_dir($outputDir)) {
225
            $canDeleteFile = $io->confirm(
226
                sprintf(
227
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
228
                    .'removed, do you wish to proceed?',
229
                    $outputDir
230
                ),
231
                false
232
            );
233
234
            if (false === $canDeleteFile) {
235
                return;
236
            }
237
238
            $this->fileSystem->remove($outputDir);
239
        } else {
240
            $canDeleteFile = $io->confirm(
241
                sprintf(
242
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
243
                    .' content, do you wish to proceed?',
244
                    $outputDir
245
                ),
246
                false
247
            );
248
249
            if (false === $canDeleteFile) {
250
                return;
251
            }
252
253
            $this->fileSystem->remove($outputDir);
254
        }
255
    }
256
257 9
    private function validateReplaceStrings(InputInterface $input)
258
    {
259 9
        $replaceStrings = $input->getOption(self::REPLACE_STRINGS_OPT);
260
261 9
        if (null === $replaceStrings) {
262 9
            $replaceStrings = $this->makeAbsolutePath(self::REPLACE_STRINGS_DEFAULT);
263
264 9
            if (false === $this->fileSystem->exists($replaceStrings)) {
265
                
266 9
                return;
267
            }
268
        } else {
269
            $replaceStrings = $this->makeAbsolutePath($replaceStrings);
270
271
            if (false === $this->fileSystem->exists($replaceStrings)) {
272
                throw new RuntimeException(
273
                    sprintf(
274
                        'Replacement strings config "<comment>%s</comment>" does not exist.',
275
                        $replaceStrings
276
                    )
277
                );
278
            }
279
        }
280
281
        $input->setOption(self::REPLACE_STRINGS_OPT, $replaceStrings);
282
283
        if (false === is_readable($replaceStrings)) {
284
            throw new RuntimeException(
285
                sprintf(
286
                    'Expected "<comment>%s</comment>" to be readable.',
287
                    $replaceStrings
288
                )
289
            );
290
        }
291
292
        try {
293
            $cwd = getcwd();
294
            $fileSystem = $this->fileSystem;
295
296
            $replaceStringsMap = Yaml::parse(
297
                file_get_contents($replaceStrings)
298
            );
299
            if (empty($replaceStringsMap) || !isset($replaceStringsMap['replacements'])) {
300
                throw new RuntimeException(
301
                    sprintf(
302
                        'No replacement strings configured in: %s', $replaceStrings
303
                    )
304
                );
305
            }
306
307
            $map = array_map(
308
                function (array $replacement) use ($cwd, $fileSystem) {
309
                    $replacement['files'] = array_map(
310 View Code Duplication
                        function (string $path) use ($cwd, $fileSystem) {
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...
311
                            if (false === $fileSystem->isAbsolutePath($path)) {
312
                                return $cwd.DIRECTORY_SEPARATOR.$path;
313
                            }
314
315
                            return $path;
316
                        },
317
                        $replacement['files']
318
                    );
319
320
                    return $replacement;
321
                },
322
                $replaceStringsMap['replacements']
323
            );
324
325
            return $map;
326
        } catch (RuntimeException $e) {
327
            throw $e;
328
        } catch (\Exception $e) {
329
            throw new RuntimeException(
330
                sprintf(
331
                    'Unable to parse replacement strings JSON file: %s%s%s',
332
                    $replaceStrings,
333
                    PHP_EOL,
334
                    $e->getMessage()
335
                )
336
            );
337
        }
338
    }
339
340 9
    private function makeAbsolutePath(string $path): string
341
    {
342 9 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...
343 9
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
344
        }
345
346 9
        return $path;
347
    }
348
}
349