Passed
Push — master ( a14eec...8c9177 )
by Théo
02:07
created

AddPrefixCommand::checkPathIsWriteable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
ccs 3
cts 3
cp 1
crap 2
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 Fidry\Console\Application\Application;
18
use Fidry\Console\Command\Command;
19
use Fidry\Console\Command\CommandAware;
20
use Fidry\Console\Command\CommandAwareness;
21
use Fidry\Console\Command\Configuration as CommandConfiguration;
22
use Fidry\Console\ExitCode;
23
use Fidry\Console\IO;
24
use Humbug\PhpScoper\Configuration\Configuration;
25
use Humbug\PhpScoper\Configuration\ConfigurationFactory;
26
use Humbug\PhpScoper\Console\ConfigLoader;
27
use Humbug\PhpScoper\Console\ConsoleScoper;
28
use Humbug\PhpScoper\Scoper\ScoperFactory;
29
use Symfony\Component\Console\Exception\RuntimeException;
30
use Symfony\Component\Console\Input\InputArgument;
31
use Symfony\Component\Console\Input\InputOption;
32
use Symfony\Component\Filesystem\Filesystem;
33
use Symfony\Component\Filesystem\Path;
34
use function array_map;
35
use function is_dir;
36
use function is_writable;
37
use function Safe\getcwd;
38
use function Safe\sprintf;
39
use const DIRECTORY_SEPARATOR;
40
41
/**
42
 * @private
43
 */
44
final class AddPrefixCommand implements Command, CommandAware
45
{
46
    use CommandAwareness;
47
48
    private const PATH_ARG = 'paths';
49
    private const PREFIX_OPT = 'prefix';
50
    private const OUTPUT_DIR_OPT = 'output-dir';
51
    private const FORCE_OPT = 'force';
52
    private const STOP_ON_FAILURE_OPT = 'stop-on-failure';
53
    private const CONFIG_FILE_OPT = 'config';
54
    private const NO_CONFIG_OPT = 'no-config';
55 16
56
    private Filesystem $fileSystem;
57 16
    private ScoperFactory $scoperFactory;
58
    private bool $init = false;
59 16
    private Application $application;
60 16
    private ConfigurationFactory $configFactory;
61
62
    public function __construct(
63
        Filesystem $fileSystem,
64
        ScoperFactory $scoperFactory,
65
        Application $application,
66 16
        ConfigurationFactory $configFactory
67
    ) {
68 16
        $this->fileSystem = $fileSystem;
69
        $this->scoperFactory = $scoperFactory;
70
        $this->application = $application;
71 16
        $this->configFactory = $configFactory;
72 16
    }
73 16
74 16
    public function getConfiguration(): CommandConfiguration
75 16
    {
76 16
        return new CommandConfiguration(
77
            'add-prefix',
78 16
            'Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.',
79 16
            '',
80 16
            [
81 16
                new InputArgument(
82 16
                    self::PATH_ARG,
83
                    InputArgument::IS_ARRAY,
84 16
                    'The path(s) to process.'
85 16
                ),
86 16
            ],
87 16
            [
88 16
                ChangeableDirectory::createOption(),
89 16
                new InputOption(
90
                    self::PREFIX_OPT,
91 16
                    'p',
92 16
                    InputOption::VALUE_REQUIRED,
93 16
                    'The namespace prefix to add.',
94 16
                ),
95 16
                new InputOption(
96
                    self::OUTPUT_DIR_OPT,
97 16
                    'o',
98 16
                    InputOption::VALUE_REQUIRED,
99 16
                    'The output directory in which the prefixed code will be dumped.',
100 16
                    'build',
101 16
                ),
102
                new InputOption(
103 16
                    self::FORCE_OPT,
104 16
                    'f',
105 16
                    InputOption::VALUE_NONE,
106 16
                    'Deletes any existing content in the output directory without any warning.'
107 16
                ),
108 16
                new InputOption(
109 16
                    self::STOP_ON_FAILURE_OPT,
110
                    's',
111
                    InputOption::VALUE_NONE,
112 16
                    'Stops on failure.'
113 16
                ),
114 16
                new InputOption(
115 16
                    self::CONFIG_FILE_OPT,
116 16
                    'c',
117
                    InputOption::VALUE_REQUIRED,
118
                    sprintf(
119
                        'Conf,iguration file. Will use "%s" if found by default.',
120
                        ConfigurationFactory::DEFAULT_FILE_NAME,
121
                    )
122
                ),
123
                new InputOption(
124 14
                    self::NO_CONFIG_OPT,
125
                    null,
126 14
                    InputOption::VALUE_NONE,
127 14
                    'Do not look for a configuration file.'
128
                ),
129 14
            ],
130
        );
131 14
    }
132 14
133 14
    public function execute(IO $io): int
134
    {
135 14
        $io->newLine();
136 12
137
        ChangeableDirectory::changeWorkingDirectory($io);
138 12
139
        // Only get current working directory _after_ we changed to the desired
140
        // working directory
141
        $cwd = getcwd();
142 12
143 12
        $paths = $this->getPathArguments($io, $cwd);
144 12
        $outputDir = $this->getOutputDir($io, $cwd);
145
146
        $this->checkOutputDir($io, $outputDir);
147 12
148 12
        $config = $this->retrieveConfig($io, $paths, $cwd);
149 12
150
        $this->getScoper()->scope(
151
            $io,
152
            $config,
153 12
            $paths,
154 12
            $outputDir,
155 12
            $io->getBooleanOption(self::STOP_ON_FAILURE_OPT),
156 12
        );
157 12
158 12
        return ExitCode::SUCCESS;
159 12
    }
160 12
161
    private function getOutputDir(IO $io, string $cwd): string
162
    {
163
        return $this->canonicalizePath(
164
            $io->getStringOption(self::OUTPUT_DIR_OPT),
165
            $cwd,
166
        );
167
    }
168
169
    private function checkOutputDir(IO $io, string $outputDir): void
170 12
    {
171
        if (!$this->fileSystem->exists($outputDir)) {
172 12
            return;
173
        }
174
175
        self::checkPathIsWriteable($outputDir);
176
177
        $canDeleteFile = self::canDeleteOutputDir($io, $outputDir);
178 12
179
        if (!$canDeleteFile) {
180
            throw new RuntimeException('Cannot delete the output directory. Interrupting the process.');
181
        }
182
183
        $this->fileSystem->remove($outputDir);
184
    }
185
186
    private static function checkPathIsWriteable(string $path): void
187
    {
188 12
        if (!is_writable($path)) {
189
            throw new RuntimeException(
190 12
                sprintf(
191
                    'Expected "<comment>%s</comment>" to be writeable.',
192 12
                    $path,
193 12
                ),
194
            );
195 12
        }
196 12
    }
197
198 12
    private static function canDeleteOutputDir(IO $io, string $outputDir): bool
199 12
    {
200
        if ($io->getBooleanOption(self::FORCE_OPT)) {
201
            return true;
202
        }
203 12
204 12
        $question = sprintf(
205 12
            is_dir($outputDir)
206 12
                ? 'The output directory "<comment>%s</comment>" already exists. Continuing will erase its content, do you wish to proceed?'
207 12
                : 'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be  removed, do you wish to proceed?',
208 12
            $outputDir,
209 12
        );
210 12
211 12
        return $io->confirm($question, false);
212
    }
213
214
    /**
215 12
     * @param list<non-empty-string> $paths
0 ignored issues
show
Bug introduced by
The type Humbug\PhpScoper\Console\Command\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
216
     */
217 12
    private function retrieveConfig(IO $io, array $paths, string $cwd): Configuration
218 12
    {
219
        $configLoader = new ConfigLoader(
220
            $this->getCommandRegistry(),
221 12
            $this->fileSystem,
222
            $this->configFactory,
223
        );
224 12
225
        return $configLoader->loadConfig(
226 12
            $io,
227
            $io->getStringOption(self::PREFIX_OPT),
228
            $io->getBooleanOption(self::NO_CONFIG_OPT),
229
            $this->getConfigFilePath($io, $cwd),
230
            ConfigurationFactory::DEFAULT_FILE_NAME,
231
            $this->init,
232
            $paths,
233
            $cwd,
234
        );
235
    }
236 12
237
    /**
238
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
239
     */
240
    private function getConfigFilePath(IO $io, string $cwd): ?string
241
    {
242
        $configFilePath = $io->getStringOption(self::CONFIG_FILE_OPT);
243
244
        return '' === $configFilePath ? null : $this->canonicalizePath($configFilePath, $cwd);
245
    }
246
247 12
    /**
248 2
     * @return list<non-empty-string> List of absolute canonical paths
249 2
     */
250 2
    private function getPathArguments(IO $io, string $cwd): array
251 2
    {
252 2
        return array_map(
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_map(functio...gument(self::PATH_ARG)) returns the type array which is incompatible with the documented return type Humbug\PhpScoper\Console\Command\list.
Loading history...
253
            fn (string $path) => $this->canonicalizePath($path, $cwd),
254 2
            $io->getStringArrayArgument(self::PATH_ARG),
255 2
        );
256
    }
257
258 2
    /**
259
     * @return non-empty-string Absolute canonical path
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
260
     */
261
    private function canonicalizePath(string $path, string $cwd): string
262 2
    {
263
        return Path::canonicalize(
264 2
            $this->fileSystem->isAbsolutePath($path)
265
                ? $path
266
                : $cwd.DIRECTORY_SEPARATOR.$path,
267 12
        );
268
    }
269 12
270 11
    private function getScoper(): ConsoleScoper
271
    {
272
        return new ConsoleScoper(
273
            $this->fileSystem,
274 14
            $this->application,
275
            $this->scoperFactory,
276 14
        );
277
    }
278
}
279