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

InspectSymbolCommand::printInspectionHeadline()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 13
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\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->getStringArgument(self::SYMBOL_ARG);
121
        $symbolType = self::getSymbolType($io);
122
        $config = $this->retrieveConfig($io, $cwd);
123
124
        $enrichedReflector = $this->enrichedReflectorFactory->create(
125
            $config->getSymbolsConfiguration(),
126
        );
127
128
        self::printSymbol(
129
            $io,
130
            $symbol,
131
            $symbolType,
132
            $config->getPath(),
133
            $enrichedReflector,
134
        );
135
136
        return ExitCode::SUCCESS;
137
    }
138
139
    /**
140
     * @return SymbolType::*_TYPE
141
     */
142
    private static function getSymbolType(IO $io): string
143
    {
144
        // TODO: use options when available https://github.com/theofidry/console/issues/18
145
        $type = $io->getStringArgument(self::SYMBOL_TYPE_ARG);
146
147
        if (!in_array($type, SymbolType::ALL, true)) {
148
            throw new InvalidArgumentException(
149
                sprintf(
150
                    'Expected symbol type to be one of "%s". Got "%s"',
151
                    implode('", "', SymbolType::ALL),
152
                    $type,
153
                ),
154
            );
155
        }
156
157
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $type returns the type string which is incompatible with the documented return type Humbug\PhpScoper\Console\Command\SymbolType.
Loading history...
158
    }
159
160
    private function retrieveConfig(IO $io, string $cwd): Configuration
161
    {
162
        $configLoader = new ConfigLoader(
163
            new CommandRegistry(new DummyApplication()),
164
            $this->fileSystem,
165
            $this->configFactory,
166
        );
167
168
        $configFilePath = $this->getConfigFilePath($io, $cwd);
169
        $noConfig = $io->getBooleanOption(self::NO_CONFIG_OPT);
170
171
        if (null === $configFilePath) {
172
            // Unlike when scoping, we do not want a config file to be created
173
            // neither bother the user with passing the no-config option if the
174
            // file does not exist.
175
            $noConfig = true;
176
        }
177
178
        return $configLoader->loadConfig(
179
            new IO(
180
                $io->getInput(),
181
                new NullOutput(),
182
            ),
183
            '',
184
            $noConfig,
185
            $configFilePath,
186
            ConfigurationFactory::DEFAULT_FILE_NAME,
187
            // We do not want the init command to be triggered if there is no
188
            // config file.
189
            true,
190
            [],
191
            getcwd(),
192
        );
193
    }
194
195
    /**
196
     * @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...
197
     */
198
    private function getConfigFilePath(IO $io, string $cwd): ?string
199
    {
200
        $configPath = trim($io->getStringOption(self::CONFIG_FILE_OPT));
201
202
        if ('' === $configPath) {
203
            $configPath = ConfigurationFactory::DEFAULT_FILE_NAME;
204
        }
205
206
        $configPath = $this->canonicalizePath($configPath, $cwd);
207
208
        return file_exists($configPath) ? $configPath : null;
209
    }
210
211
    /**
212
     * @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...
213
     */
214
    private static function printSymbol(
215
        IO $io,
216
        string $symbol,
217
        string $type,
218
        ?string $configPath,
219
        EnrichedReflector $reflector
220
    ): void
221
    {
222
        self::printDocBlock($io);
223
        self::printConfigLoaded($io, $configPath);
224
        self::printInspectionHeadline($io, $symbol, $type);
225
226
        $io->newLine();
227
228
        if (!(SymbolType::ANY_TYPE === $type)) {
229
            self::printTypedSymbol($io, $symbol, $type, $reflector);
230
        } else {
231
            self::printAnyTypeSymbol($io, $symbol, $reflector);
232
        }
233
    }
234
235
    private static function printDocBlock(IO $io): void
236
    {
237
        $io->writeln([
238
            'Internal (configured via the `excluded-*` settings) are treated as PHP native symbols, i.e. will remain untouched.',
239
            'Exposed symbols (configured via the `expose-*` settings) will be prefixed but aliased to its original symbol.',
240
            'If a symbol is neither internal or exposed, it will be prefixed and not aliased',
241
            '',
242
            'For more information, see:'
243
        ]);
244
        $io->listing([
245
            '<href=https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#excluded-symbols>Doc link for excluded symbols</>',
246
            '<href=https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposed-symbols>Doc link for exposed symbols</>',
247
        ]);
248
    }
249
250
    private static function printConfigLoaded(IO $io, ?string $configPath): void
251
    {
252
        $io->writeln(
253
            null === $configPath
254
                ? 'No configuration loaded.'
255
                : sprintf(
256
                'Loaded the configuration <comment>%s</comment>',
257
                $configPath,
258
            ),
259
        );
260
        $io->newLine();
261
    }
262
263
    /**
264
     * @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...
265
     */
266
    private static function printInspectionHeadline(
267
        IO $io,
268
        string $symbol,
269
        string $type
270
    ): void
271
    {
272
        $io->writeln(
273
            sprintf(
274
                'Inspecting the symbol <comment>%s</comment> %s',
275
                $symbol,
276
                SymbolType::ANY_TYPE === $type
277
                    ? 'for all types.'
278
                    : sprintf('for type <comment>%s</comment>:', $type),
279
            ),
280
        );
281
282
    }
283
284
    private static function printAnyTypeSymbol(
285
        IO $io,
286
        string $symbol,
287
        EnrichedReflector $reflector
288
    ): void
289
    {
290
        foreach (SymbolType::getAllSpecificTypes() as $specificType) {
291
            $io->writeln(
292
                sprintf(
293
                    'As a <comment>%s</comment>:',
294
                    $specificType,
295
                ),
296
            );
297
298
            self::printTypedSymbol($io, $symbol, $specificType, $reflector);
299
        }
300
    }
301
302
    /**
303
     * @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...
304
     */
305
    private static function printTypedSymbol(
306
        IO $io,
307
        string $symbol,
308
        string $type,
309
        EnrichedReflector $reflector
310
    ): void
311
    {
312
        [$internal, $exposed] = self::determineSymbolStatus(
313
            $symbol,
314
            $type,
315
            $reflector,
316
        );
317
318
        $io->listing([
319
            sprintf(
320
                'Internal: %s',
321
                self::convertBoolToString($internal),
322
            ),
323
            sprintf(
324
                'Exposed:  %s',
325
                self::convertBoolToString($exposed),
326
            ),
327
        ]);
328
    }
329
330
    /**
331
     * @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...
332
     */
333
    private static function determineSymbolStatus(
334
        string $symbol,
335
        string $type,
336
        EnrichedReflector $reflector
337
    ): array {
338
        switch ($type) {
339
            case SymbolType::CLASS_TYPE:
340
                return [
341
                    $reflector->isClassInternal($symbol),
342
                    $reflector->isExposedClass($symbol),
343
                ];
344
345
            case SymbolType::FUNCTION_TYPE:
346
                return [
347
                    $reflector->isClassInternal($symbol),
348
                    $reflector->isExposedFunction($symbol),
349
                ];
350
351
            case SymbolType::CONSTANT_TYPE:
352
                return [
353
                    $reflector->isConstantInternal($symbol),
354
                    $reflector->isExposedConstant($symbol),
355
                ];
356
        }
357
358
        throw new InvalidArgumentException(
359
            sprintf(
360
                'Invalid type "%s"',
361
                $type,
362
            ),
363
        );
364
    }
365
366
    private static function convertBoolToString(bool $bool): string
367
    {
368
        return true === $bool ? '<question>true</question>' : '<error>false</error>';
369
    }
370
371
    /**
372
     * @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...
373
     *
374
     * @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...
375
     */
376
    private function canonicalizePath(string $path, string $cwd): string
377
    {
378
        $canonicalPath = Path::canonicalize(
379
            $this->fileSystem->isAbsolutePath($path)
380
                ? $path
381
                : $cwd.DIRECTORY_SEPARATOR.$path,
382
        );
383
384
        assert('' !== $canonicalPath);
385
386
        return $canonicalPath;
387
    }
388
}
389