Passed
Push — master ( d80c5e...864907 )
by Théo
02:09
created

InspectSymbolCommand::getSymbolType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 16
rs 10
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\Command\Command;
18
use Fidry\Console\Command\CommandRegistry;
19
use Fidry\Console\Command\Configuration as CommandConfiguration;
20
use Fidry\Console\ExitCode;
21
use Fidry\Console\Input\IO;
22
use Humbug\PhpScoper\Configuration\Configuration;
23
use Humbug\PhpScoper\Configuration\ConfigurationFactory;
24
use Humbug\PhpScoper\Console\ConfigLoader;
25
use Humbug\PhpScoper\Symbol\EnrichedReflector;
26
use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory;
27
use InvalidArgumentException;
28
use Symfony\Component\Console\Application as DummyApplication;
29
use Symfony\Component\Console\Input\InputArgument;
30
use Symfony\Component\Console\Input\InputOption;
31
use Symfony\Component\Console\Output\NullOutput;
32
use Symfony\Component\Filesystem\Filesystem;
33
use Symfony\Component\Filesystem\Path;
34
use function assert;
35
use function file_exists;
36
use function implode;
37
use function in_array;
38
use function Safe\getcwd;
39
use function Safe\sprintf;
40
use function trim;
41
use const DIRECTORY_SEPARATOR;
42
43
/**
44
 * @private
45
 */
46
final class InspectSymbolCommand implements Command
47
{
48
    private const SYMBOL_ARG = 'symbol';
49
    private const SYMBOL_TYPE_ARG = 'type';
50
    private const CONFIG_FILE_OPT = 'config';
51
    private const NO_CONFIG_OPT = 'no-config';
52
53
    private Filesystem $fileSystem;
54
    private ConfigurationFactory $configFactory;
55
    private EnrichedReflectorFactory $enrichedReflectorFactory;
56
57
    public function __construct(
58
        Filesystem $fileSystem,
59
        ConfigurationFactory $configFactory,
60
        EnrichedReflectorFactory $enrichedReflectorFactory
61
    ) {
62
        $this->fileSystem = $fileSystem;
63
        $this->configFactory = $configFactory;
64
        $this->enrichedReflectorFactory = $enrichedReflectorFactory;
65
    }
66
67
    public function getConfiguration(): CommandConfiguration
68
    {
69
        return new CommandConfiguration(
70
            'inspect-symbol',
71
            'Checks the given symbol for a given configuration. Helpful to have an insight on how PHP-Scoper will interpret this symbol',
72
            '',
73
            [
74
                new InputArgument(
75
                    self::SYMBOL_ARG,
76
                    InputArgument::REQUIRED,
77
                    'The symbol to inspect.'
78
                ),
79
                new InputArgument(
80
                    self::SYMBOL_TYPE_ARG,
81
                    InputArgument::OPTIONAL,
82
                    sprintf(
83
                        'The symbol type inspect ("%s").',
84
                        implode('", "', SymbolType::ALL),
85
                    ),
86
                    SymbolType::ANY_TYPE,
87
                ),
88
            ],
89
            [
90
                ChangeableDirectory::createOption(),
91
                new InputOption(
92
                    self::CONFIG_FILE_OPT,
93
                    'c',
94
                    InputOption::VALUE_REQUIRED,
95
                    sprintf(
96
                        'Configuration file. Will use "%s" if found by default.',
97
                        ConfigurationFactory::DEFAULT_FILE_NAME
98
                    )
99
                ),
100
                new InputOption(
101
                    self::NO_CONFIG_OPT,
102
                    null,
103
                    InputOption::VALUE_NONE,
104
                    'Do not look for a configuration file.'
105
                ),
106
            ],
107
        );
108
    }
109
110
    public function execute(IO $io): int
111
    {
112
        $io->newLine();
113
114
        ChangeableDirectory::changeWorkingDirectory($io);
115
116
        // Only get current working directory _after_ we changed to the desired
117
        // working directory
118
        $cwd = getcwd();
119
120
        $symbol = $io->getArgument(self::SYMBOL_ARG)->asString();
121
        /** @var SymbolType::*_TYPE $symbolType */
122
        $symbolType = $io->getArgument(self::SYMBOL_TYPE_ARG)->asStringChoice(SymbolType::ALL);
123
        $config = $this->retrieveConfig($io, $cwd);
124
125
        $enrichedReflector = $this->enrichedReflectorFactory->create(
126
            $config->getSymbolsConfiguration(),
127
        );
128
129
        self::printSymbol(
130
            $io,
131
            $symbol,
132
            $symbolType,
133
            $config->getPath(),
134
            $enrichedReflector,
135
        );
136
137
        return ExitCode::SUCCESS;
138
    }
139
140
    private function retrieveConfig(IO $io, string $cwd): Configuration
141
    {
142
        $configLoader = new ConfigLoader(
143
            new CommandRegistry(new DummyApplication()),
144
            $this->fileSystem,
145
            $this->configFactory,
146
        );
147
148
        $configFilePath = $this->getConfigFilePath($io, $cwd);
149
        $noConfig = $io->getOption(self::NO_CONFIG_OPT)->asBoolean();
150
151
        if (null === $configFilePath) {
152
            // Unlike when scoping, we do not want a config file to be created
153
            // neither bother the user with passing the no-config option if the
154
            // file does not exist.
155
            $noConfig = true;
156
        }
157
158
        return $configLoader->loadConfig(
159
            new IO(
160
                $io->getInput(),
161
                new NullOutput(),
162
            ),
163
            '',
164
            $noConfig,
165
            $configFilePath,
166
            ConfigurationFactory::DEFAULT_FILE_NAME,
167
            // We do not want the init command to be triggered if there is no
168
            // config file.
169
            true,
170
            [],
171
            getcwd(),
172
        );
173
    }
174
175
    /**
176
     * @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...
177
     */
178
    private function getConfigFilePath(IO $io, string $cwd): ?string
179
    {
180
        $configPath = (string) $io->getOption(self::CONFIG_FILE_OPT)->asNullableString();
181
182
        if ('' === $configPath) {
183
            $configPath = ConfigurationFactory::DEFAULT_FILE_NAME;
184
        }
185
186
        $configPath = $this->canonicalizePath($configPath, $cwd);
187
188
        return file_exists($configPath) ? $configPath : null;
189
    }
190
191
    /**
192
     * @param SymbolType::*_TYPE $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment $type at position 0 could not be parsed: Unknown type name '$type' at position 0 in $type.
Loading history...
193
     */
194
    private static function printSymbol(
195
        IO $io,
196
        string $symbol,
197
        string $type,
198
        ?string $configPath,
199
        EnrichedReflector $reflector
200
    ): void
201
    {
202
        self::printDocBlock($io);
203
        self::printConfigLoaded($io, $configPath);
204
        self::printInspectionHeadline($io, $symbol, $type);
205
206
        $io->newLine();
207
208
        if (!(SymbolType::ANY_TYPE === $type)) {
209
            self::printTypedSymbol($io, $symbol, $type, $reflector);
210
        } else {
211
            self::printAnyTypeSymbol($io, $symbol, $reflector);
212
        }
213
    }
214
215
    private static function printDocBlock(IO $io): void
216
    {
217
        $io->writeln([
218
            'Internal (configured via the `excluded-*` settings) are treated as PHP native symbols, i.e. will remain untouched.',
219
            'Exposed symbols (configured via the `expose-*` settings) will be prefixed but aliased to its original symbol.',
220
            'If a symbol is neither internal or exposed, it will be prefixed and not aliased',
221
            '',
222
            'For more information, see:'
223
        ]);
224
        $io->listing([
225
            '<href=https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#excluded-symbols>Doc link for excluded symbols</>',
226
            '<href=https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposed-symbols>Doc link for exposed symbols</>',
227
        ]);
228
    }
229
230
    private static function printConfigLoaded(IO $io, ?string $configPath): void
231
    {
232
        $io->writeln(
233
            null === $configPath
234
                ? 'No configuration loaded.'
235
                : sprintf(
236
                'Loaded the configuration <comment>%s</comment>',
237
                $configPath,
238
            ),
239
        );
240
        $io->newLine();
241
    }
242
243
    /**
244
     * @param SymbolType::*_TYPE $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment $type at position 0 could not be parsed: Unknown type name '$type' at position 0 in $type.
Loading history...
245
     */
246
    private static function printInspectionHeadline(
247
        IO $io,
248
        string $symbol,
249
        string $type
250
    ): void
251
    {
252
        $io->writeln(
253
            sprintf(
254
                'Inspecting the symbol <comment>%s</comment> %s',
255
                $symbol,
256
                SymbolType::ANY_TYPE === $type
257
                    ? 'for all types.'
258
                    : sprintf('for type <comment>%s</comment>:', $type),
259
            ),
260
        );
261
262
    }
263
264
    private static function printAnyTypeSymbol(
265
        IO $io,
266
        string $symbol,
267
        EnrichedReflector $reflector
268
    ): void
269
    {
270
        foreach (SymbolType::getAllSpecificTypes() as $specificType) {
271
            $io->writeln(
272
                sprintf(
273
                    'As a <comment>%s</comment>:',
274
                    $specificType,
275
                ),
276
            );
277
278
            self::printTypedSymbol($io, $symbol, $specificType, $reflector);
279
        }
280
    }
281
282
    /**
283
     * @param SymbolType::*_TYPE $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment $type at position 0 could not be parsed: Unknown type name '$type' at position 0 in $type.
Loading history...
284
     */
285
    private static function printTypedSymbol(
286
        IO $io,
287
        string $symbol,
288
        string $type,
289
        EnrichedReflector $reflector
290
    ): void
291
    {
292
        [$internal, $exposed] = self::determineSymbolStatus(
293
            $symbol,
294
            $type,
295
            $reflector,
296
        );
297
298
        $io->listing([
299
            sprintf(
300
                'Internal: %s',
301
                self::convertBoolToString($internal),
302
            ),
303
            sprintf(
304
                'Exposed:  %s',
305
                self::convertBoolToString($exposed),
306
            ),
307
        ]);
308
    }
309
310
    /**
311
     * @param SymbolType::*_TYPE $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment $type at position 0 could not be parsed: Unknown type name '$type' at position 0 in $type.
Loading history...
312
     */
313
    private static function determineSymbolStatus(
314
        string $symbol,
315
        string $type,
316
        EnrichedReflector $reflector
317
    ): array {
318
        switch ($type) {
319
            case SymbolType::CLASS_TYPE:
320
                return [
321
                    $reflector->isClassInternal($symbol),
322
                    $reflector->isExposedClass($symbol),
323
                ];
324
325
            case SymbolType::FUNCTION_TYPE:
326
                return [
327
                    $reflector->isClassInternal($symbol),
328
                    $reflector->isExposedFunction($symbol),
329
                ];
330
331
            case SymbolType::CONSTANT_TYPE:
332
                return [
333
                    $reflector->isConstantInternal($symbol),
334
                    $reflector->isExposedConstant($symbol),
335
                ];
336
        }
337
338
        throw new InvalidArgumentException(
339
            sprintf(
340
                'Invalid type "%s"',
341
                $type,
342
            ),
343
        );
344
    }
345
346
    private static function convertBoolToString(bool $bool): string
347
    {
348
        return true === $bool ? '<question>true</question>' : '<error>false</error>';
349
    }
350
351
    /**
352
     * @param non-empty-string $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...
353
     *
354
     * @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...
355
     */
356
    private function canonicalizePath(string $path, string $cwd): string
357
    {
358
        $canonicalPath = Path::canonicalize(
359
            $this->fileSystem->isAbsolutePath($path)
360
                ? $path
361
                : $cwd.DIRECTORY_SEPARATOR.$path,
362
        );
363
364
        assert('' !== $canonicalPath);
365
366
        return $canonicalPath;
367
    }
368
}
369