Passed
Push — master ( 6d0b22...d6cda5 )
by Théo
01:59
created

InspectSymbolCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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