Completed
Push — master ( 72182d...f36c81 )
by Théo
02:00
created

AddPrefixCommand::validatePaths()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 1
nop 1
dl 0
loc 18
ccs 11
cts 11
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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->getGlobalNamespaceWhitelisters(),
145 13
                $input->getOption(self::STOP_ON_FAILURE_OPT),
146 13
                $logger
147
            );
148 1
        } catch (Throwable $throwable) {
149 1
            $logger->outputScopingEndWithFailure();
150
151 1
            throw $throwable;
152
        }
153
154 12
        $logger->outputScopingEnd();
155
    }
156
157 20
    private function validatePrefix(InputInterface $input)
158
    {
159 20
        $prefix = $input->getOption(self::PREFIX_OPT);
160
161 20
        if (null === $prefix) {
162 1
            $prefix = uniqid('PhpScoper');
163
        } else {
164 19
            $prefix = trim($prefix);
165
        }
166
167 20
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
168 20
            $prefix = $matches['prefix'];
169
        }
170
171 20
        if ('' === $prefix) {
172 5
            throw new RuntimeException(
173 5
                sprintf(
174 5
                    'Expected "%s" argument to be a non empty string.',
175 5
                    self::PREFIX_OPT
176
                )
177
            );
178
        }
179
180 15
        $input->setOption(self::PREFIX_OPT, $prefix);
181
    }
182
183 15
    private function validatePaths(InputInterface $input)
184
    {
185 15
        $cwd = getcwd();
186 15
        $fileSystem = $this->fileSystem;
187
188 15
        $paths = array_map(
189 15
            function (string $path) use ($cwd, $fileSystem) {
190 13
                if (false === $fileSystem->isAbsolutePath($path)) {
191 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
192
                }
193
194 13
                return $path;
195 15
            },
196 15
            $input->getArgument(self::PATH_ARG)
197
        );
198
199 15
        $input->setArgument(self::PATH_ARG, $paths);
200
    }
201
202 15
    private function validateOutputDir(InputInterface $input, OutputStyle $io)
203
    {
204 15
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
205
206 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...
207 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
208
        }
209
210 15
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
211
212 15
        if (false === $this->fileSystem->exists($outputDir)) {
213 15
            return;
214
        }
215
216
        if (false === is_writable($outputDir)) {
217
            throw new RuntimeException(
218
                sprintf(
219
                    'Expected "<comment>%s</comment>" to be writeable.',
220
                    $outputDir
221
                )
222
            );
223
        }
224
225
        if ($input->getOption(self::FORCE_OPT)) {
226
            $this->fileSystem->remove($outputDir);
227
228
            return;
229
        }
230
231
        if (false === is_dir($outputDir)) {
232
            $canDeleteFile = $io->confirm(
233
                sprintf(
234
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
235
                    .'removed, do you wish to proceed?',
236
                    $outputDir
237
                ),
238
                false
239
            );
240
241
            if (false === $canDeleteFile) {
242
                return;
243
            }
244
245
            $this->fileSystem->remove($outputDir);
246
        } else {
247
            $canDeleteFile = $io->confirm(
248
                sprintf(
249
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
250
                    .' content, do you wish to proceed?',
251
                    $outputDir
252
                ),
253
                false
254
            );
255
256
            if (false === $canDeleteFile) {
257
                return;
258
            }
259
260
            $this->fileSystem->remove($outputDir);
261
        }
262
    }
263
264 15
    private function retrieveConfig(InputInterface $input, OutputStyle $io): Configuration
265
    {
266 15
        $configFile = $input->getOption(self::CONFIG_FILE);
267
268 15
        if (null === $configFile) {
269 14
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
270
271 14
            if (false === file_exists($configFile)) {
272 3
                $io->writeln(
273 3
                    sprintf(
274 3
                        'Patch file "%s" not found. Skipping.',
275 3
                        $configFile
276
                    ),
277 3
                    OutputStyle::VERBOSITY_DEBUG
278
                );
279
280 14
                return Configuration::load(null);
281
            }
282
        } else {
283 1
            $configFile = $this->makeAbsolutePath($configFile);
284
        }
285
286 12
        if (false === file_exists($configFile)) {
287 1
            throw new RuntimeException(
288 1
                sprintf(
289 1
                    'Could not find the file "%s".',
290 1
                    $configFile
291
                )
292
            );
293
        }
294
295 11
        $io->writeln(
296 11
            sprintf(
297 11
                'Using the configuration file "%s".',
298 11
                $configFile
299
            ),
300 11
            OutputStyle::VERBOSITY_DEBUG
301
        );
302
303 11
        return Configuration::load($configFile);
304
    }
305
306
    /**
307
     * @param InputInterface $input
308
     * @param Configuration  $configuration
309
     *
310
     * @return string[] List of absolute paths
311
     */
312 13
    private function retrievePaths(InputInterface $input, Configuration $configuration): array
313
    {
314 13
        $paths = $input->getArgument(self::PATH_ARG);
315
316 13
        $finders = $configuration->getFinders();
317
318 13
        foreach ($finders as $finder) {
319 1
            foreach ($finder as $file) {
320 1
                $paths[] = $file->getRealPath();
321
            }
322
        }
323
324 13
        if (0 === count($paths)) {
325 2
            return [getcwd()];
326
        }
327
328 11
        return $paths;
329
    }
330
331 15
    private function makeAbsolutePath(string $path): string
332
    {
333 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...
334 12
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
335
        }
336
337 15
        return $path;
338
    }
339
}
340