Passed
Push — master ( 8feb2a...cb2398 )
by Théo
01:57
created

dSymbols()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Humbug\PhpScoper\Configuration;
6
7
use Humbug\PhpScoper\Symbol\NamespaceRegistry;
8
use Humbug\PhpScoper\Symbol\SymbolRegistry;
9
use InvalidArgumentException;
10
use function array_key_exists;
11
use function array_keys;
12
use function array_map;
13
use function array_merge;
14
use function array_pop;
15
use function array_values;
16
use function explode;
17
use function get_debug_type;
18
use function gettype;
19
use function implode;
20
use function is_array;
21
use function is_bool;
22
use function is_string;
23
use function ltrim;
24
use function Safe\preg_match as native_preg_match;
25
use function Safe\sprintf;
26
use function Safe\substr;
27
use function str_replace;
28
use function strpos;
29
use function strtolower;
30
use function trim;
31
32
final class SymbolsConfigurationFactory
33
{
34
    private RegexChecker $regexChecker;
35
36
    public function __construct(RegexChecker $regexChecker)
37
    {
38
        $this->regexChecker = $regexChecker;
39
    }
40
41
    public function createSymbolsConfiguration(array $config): SymbolsConfiguration
42
    {
43
        [
44
            $excludedNamespaceNames,
45
            $excludedNamespaceRegexes,
46
        ] = $this->retrieveElements(
47
            $config,
48
            ConfigurationKeys::EXCLUDE_NAMESPACES_KEYWORD,
49
        );
50
51
        [
52
            $exposedNamespaceNames,
53
            $exposedNamespaceRegexes,
54
        ] = $this->retrieveElements(
55
            $config,
56
            ConfigurationKeys::EXPOSE_NAMESPACES_KEYWORD,
57
        );
58
59
        $legacyExposedElements = self::retrieveLegacyExposedElements($config);
60
61
        [
62
            $legacyExposedSymbols,
63
            $legacyExposedSymbolsPatterns,
64
            $legacyExposedConstants,
65
            $excludedNamespaceNames,
66
        ] = self::parseLegacyExposedElements($legacyExposedElements, $excludedNamespaceNames);
0 ignored issues
show
Deprecated Code introduced by
The function Humbug\PhpScoper\Configu...LegacyExposedElements() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
        ] = /** @scrutinizer ignore-deprecated */ self::parseLegacyExposedElements($legacyExposedElements, $excludedNamespaceNames);
Loading history...
67
68
        $exposeGlobalConstants = self::retrieveExposeGlobalSymbol(
69
            $config,
70
            ConfigurationKeys::EXPOSE_GLOBAL_CONSTANTS_KEYWORD,
71
        );
72
        $exposeGlobalClasses = self::retrieveExposeGlobalSymbol(
73
            $config,
74
            ConfigurationKeys::EXPOSE_GLOBAL_CLASSES_KEYWORD,
75
        );
76
        $exposeGlobalFunctions = self::retrieveExposeGlobalSymbol(
77
            $config,
78
            ConfigurationKeys::EXPOSE_GLOBAL_FUNCTIONS_KEYWORD,
79
        );
80
81
        [$exposedClassNames, $exposedClassRegexes] = $this->retrieveElements(
82
            $config,
83
            ConfigurationKeys::EXPOSE_CLASSES_SYMBOLS_KEYWORD,
84
        );
85
86
        [$exposedFunctionNames, $exposedFunctionRegexes] = $this->retrieveElements(
87
            $config,
88
            ConfigurationKeys::EXPOSE_FUNCTIONS_SYMBOLS_KEYWORD,
89
        );
90
91
        [$exposedConstantNames, $exposedConstantRegexes] = $this->retrieveElements(
92
            $config,
93
            ConfigurationKeys::EXPOSE_CONSTANTS_SYMBOLS_KEYWORD,
94
        );
95
96
        $excludedClasses = SymbolRegistry::create(
97
            ...$this->retrieveElements(
0 ignored issues
show
Bug introduced by
$this->retrieveElements(...TERNAL_SYMBOLS_KEYWORD) is expanded, but the parameter $names of Humbug\PhpScoper\Symbol\SymbolRegistry::create() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            /** @scrutinizer ignore-type */ ...$this->retrieveElements(
Loading history...
98
                $config,
99
                ConfigurationKeys::CLASSES_INTERNAL_SYMBOLS_KEYWORD,
100
            ),
101
        );
102
103
        $excludedFunctions = SymbolRegistry::create(
104
            ...$this->retrieveElements(
105
                $config,
106
                ConfigurationKeys::FUNCTIONS_INTERNAL_SYMBOLS_KEYWORD,
107
            ),
108
        );
109
110
        $excludedConstants = SymbolRegistry::createForConstants(
111
            ...$this->retrieveElements(
0 ignored issues
show
Bug introduced by
$this->retrieveElements(...TERNAL_SYMBOLS_KEYWORD) is expanded, but the parameter $names of Humbug\PhpScoper\Symbol\...y::createForConstants() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
            /** @scrutinizer ignore-type */ ...$this->retrieveElements(
Loading history...
112
                $config,
113
                ConfigurationKeys::CONSTANTS_INTERNAL_SYMBOLS_KEYWORD,
114
            ),
115
        );
116
117
        return SymbolsConfiguration::create(
118
            $exposeGlobalConstants,
119
            $exposeGlobalClasses,
120
            $exposeGlobalFunctions,
121
            NamespaceRegistry::create(
122
                $excludedNamespaceNames,
123
                $excludedNamespaceRegexes,
124
            ),
125
            NamespaceRegistry::create(
126
                $exposedNamespaceNames,
127
                $exposedNamespaceRegexes,
128
            ),
129
            SymbolRegistry::create(
130
                array_merge(
131
                    $exposedClassNames,
132
                    $legacyExposedSymbols,
133
                ),
134
                array_merge(
135
                    $exposedClassRegexes,
136
                    $legacyExposedSymbolsPatterns,
137
                ),
138
            ),
139
            SymbolRegistry::create(
140
                array_merge(
141
                    $exposedFunctionNames,
142
                    $legacyExposedSymbols,
143
                ),
144
                array_merge(
145
                    $exposedFunctionRegexes,
146
                    $legacyExposedSymbolsPatterns,
147
                ),
148
            ),
149
            SymbolRegistry::createForConstants(
150
                array_merge(
151
                    $exposedConstantNames,
152
                    $legacyExposedConstants,
153
                ),
154
                array_merge(
155
                    $exposedConstantRegexes,
156
                    $legacyExposedSymbolsPatterns,
157
                ),
158
            ),
159
            $excludedClasses,
160
            $excludedFunctions,
161
            $excludedConstants,
162
        );
163
    }
164
165
    private static function retrieveExposeGlobalSymbol(array $config, string $key): bool
166
    {
167
        if (!array_key_exists($key, $config)) {
168
            return false;
169
        }
170
171
        $value = $config[$key];
172
173
        if (!is_bool($value)) {
174
            throw new InvalidArgumentException(
175
                sprintf(
176
                    'Expected %s to be a boolean, found "%s" instead.',
177
                    $key,
178
                    gettype($value),
179
                ),
180
            );
181
        }
182
183
        return $value;
184
    }
185
186
    /**
187
     * return list<string>
188
     */
189
    private static function retrieveLegacyExposedElements(array $config): array
190
    {
191
        $key = ConfigurationKeys::WHITELIST_KEYWORD;
192
193
        if (!array_key_exists($key, $config)) {
194
            return [];
195
        }
196
197
        $whitelist = $config[$key];
198
199
        if (!is_array($whitelist)) {
200
            throw new InvalidArgumentException(
201
                sprintf(
202
                    'Expected "%s" to be an array of strings, found "%s" instead.',
203
                    $key,
204
                    gettype($whitelist),
205
                ),
206
            );
207
        }
208
209
        foreach ($whitelist as $index => $className) {
210
            if (is_string($className)) {
211
                continue;
212
            }
213
214
            throw new InvalidArgumentException(
215
                sprintf(
216
                    'Expected whitelist to be an array of string, the "%d" element is not.',
217
                    $index,
218
                ),
219
            );
220
        }
221
222
        return array_values($whitelist);
223
    }
224
225
    /**
226
     * return array{string[], string[]}
227
     */
228
    private function retrieveElements(array $config, string $key): array
229
    {
230
        if (!array_key_exists($key, $config)) {
231
            return [[], []];
232
        }
233
234
        $symbolNamesAndRegexes = $config[$key];
235
236
        self::assertIsArrayOfStrings($config[$key], $key);
237
238
        // Store the strings in the keys for avoiding a unique check later on
239
        $names = [];
240
        $regexes = [];
241
242
        foreach ($symbolNamesAndRegexes as $index => $nameOrRegex) {
243
            if (!$this->regexChecker->isRegexLike($nameOrRegex)) {
244
                $names[$nameOrRegex] = null;
245
246
                continue;
247
            }
248
249
            $regex = $nameOrRegex;
250
251
            $this->assertValidRegex($regex, $key, (string) $index);
252
253
            $errorMessage = $this->regexChecker->validateRegex($regex);
254
255
            if (null !== $errorMessage) {
256
                throw new InvalidArgumentException(
257
                    sprintf(
258
                        'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.',
259
                        $key,
260
                        $regex,
261
                        $index,
262
                        $errorMessage,
263
                    ),
264
                );
265
            }
266
267
            // Ensure namespace comparisons are always case-insensitive
268
            // TODO: double check that we are not adding it twice or that adding it twice does not break anything
269
            $regex .= 'i';
270
            $regexes[$regex] = null;
271
        }
272
273
        return [
274
            array_keys($names),
275
            array_keys($regexes),
276
        ];
277
    }
278
279
    /**
280
     * @deprecated
281
     *
282
     * @param list<string> $elements
0 ignored issues
show
Bug introduced by
The type Humbug\PhpScoper\Configuration\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...
283
     * @param list<string> $excludedNamespaceNames
284
     */
285
    private static function parseLegacyExposedElements(array $elements, array $excludedNamespaceNames): array
286
    {
287
        $exposedSymbols = [];
288
        $exposedConstants = [];
289
        $exposedSymbolsPatterns = [];
290
        $excludedNamespaceNames = array_map('strtolower', $excludedNamespaceNames);
291
292
        foreach ($elements as $element) {
293
            $element = ltrim(trim($element), '\\');
294
295
            self::assertValidElement($element);
0 ignored issues
show
Deprecated Code introduced by
The function Humbug\PhpScoper\Configu...y::assertValidElement() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

295
            /** @scrutinizer ignore-deprecated */ self::assertValidElement($element);
Loading history...
296
297
            if ('\*' === substr($element, -2)) {
298
                $excludedNamespaceNames[] = strtolower(substr($element, 0, -2));
299
            } elseif ('*' === $element) {
300
                $excludedNamespaceNames[] = '';
301
            } elseif (false !== strpos($element, '*')) {
302
                $exposedSymbolsPatterns[] = self::createExposePattern($element);
0 ignored issues
show
Deprecated Code introduced by
The function Humbug\PhpScoper\Configu...::createExposePattern() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

302
                $exposedSymbolsPatterns[] = /** @scrutinizer ignore-deprecated */ self::createExposePattern($element);
Loading history...
303
            } else {
304
                $exposedSymbols[] = strtolower($element);
305
                $exposedConstants[] = self::lowerCaseConstantName($element);
0 ignored issues
show
Deprecated Code introduced by
The function Humbug\PhpScoper\Configu...lowerCaseConstantName() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

305
                $exposedConstants[] = /** @scrutinizer ignore-deprecated */ self::lowerCaseConstantName($element);
Loading history...
306
            }
307
        }
308
309
        return [
310
            $exposedSymbols,
311
            $exposedSymbolsPatterns,
312
            $exposedConstants,
313
            $excludedNamespaceNames,
314
        ];
315
    }
316
317
    /**
318
     * @psalm-assert string[] $value
319
     *
320
     * @param mixed $value
321
     */
322
    private static function assertIsArrayOfStrings($value, string $key): void
323
    {
324
        if (!is_array($value)) {
325
            throw new InvalidArgumentException(
326
                sprintf(
327
                    'Expected "%s" to be an array of strings, found "%s" instead.',
328
                    $key,
329
                    get_debug_type($value),
330
                ),
331
            );
332
        }
333
334
        foreach ($value as $index => $element) {
335
            if (is_string($element)) {
336
                continue;
337
            }
338
339
            throw new InvalidArgumentException(
340
                sprintf(
341
                    'Expected "%s" to be an array of strings, found "%s" for the element with the index "%s".',
342
                    $key,
343
                    get_debug_type($element),
344
                    $index,
345
                ),
346
            );
347
        }
348
    }
349
350
    private function assertValidRegex(string $regex, string $key, string $index): void
351
    {
352
        $errorMessage = $this->regexChecker->validateRegex($regex);
353
354
        if (null !== $errorMessage) {
355
            throw new InvalidArgumentException(
356
                sprintf(
357
                    'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.',
358
                    $key,
359
                    $regex,
360
                    $index,
361
                    $errorMessage,
362
                ),
363
            );
364
        }
365
    }
366
367
    /**
368
     * @deprecated
369
     */
370
    private static function assertValidElement(string $element): void
371
    {
372
        if ('' !== $element) {
373
            return;
374
        }
375
376
        throw new InvalidArgumentException(
377
            sprintf(
378
                'Invalid whitelist element "%s": cannot accept an empty string',
379
                $element,
380
            ),
381
        );
382
    }
383
384
    /**
385
     * @deprecated
386
     */
387
    private static function createExposePattern(string $element): string
388
    {
389
        self::assertValidPattern($element);
0 ignored issues
show
Deprecated Code introduced by
The function Humbug\PhpScoper\Configu...y::assertValidPattern() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

389
        /** @scrutinizer ignore-deprecated */ self::assertValidPattern($element);
Loading history...
390
391
        return sprintf(
392
            '/^%s$/u',
393
            str_replace(
394
                '\\',
395
                '\\\\',
396
                str_replace(
397
                    '*',
398
                    '.*',
399
                    $element,
400
                ),
401
            ),
402
        );
403
    }
404
405
    /**
406
     * @deprecated
407
     */
408
    private static function assertValidPattern(string $element): void
409
    {
410
        if (1 !== native_preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) {
411
            throw new InvalidArgumentException(
412
                sprintf(
413
                    'Invalid whitelist pattern "%s".',
414
                    $element,
415
                ),
416
            );
417
        }
418
    }
419
420
    /**
421
     * @deprecated
422
     */
423
    private static function lowerCaseConstantName(string $name): string
424
    {
425
        $parts = explode('\\', $name);
426
427
        $lastPart = array_pop($parts);
428
429
        $parts = array_map('strtolower', $parts);
430
431
        $parts[] = $lastPart;
432
433
        return implode('\\', $parts);
434
    }
435
}
436