Test Failed
Push — master ( 2d8dc6...3a3a08 )
by Théo
03:32 queued 01:28
created

AddPrefixCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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