Passed
Push — master ( 210c58...766469 )
by Théo
03:10 queued 13s
created

parseLegacyExposedElements()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 29
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

59
        ] = /** @scrutinizer ignore-deprecated */ self::parseLegacyExposedElements($legacyExposedElements, $excludedNamespaceNames);
Loading history...
60
61
        $exposeGlobalConstants = self::retrieveExposeGlobalSymbol(
62
            $config,
63
            ConfigurationKeys::EXPOSE_GLOBAL_CONSTANTS_KEYWORD,
64
        );
65
        $exposeGlobalClasses = self::retrieveExposeGlobalSymbol(
66
            $config,
67
            ConfigurationKeys::EXPOSE_GLOBAL_CLASSES_KEYWORD,
68
        );
69
        $exposeGlobalFunctions = self::retrieveExposeGlobalSymbol(
70
            $config,
71
            ConfigurationKeys::EXPOSE_GLOBAL_FUNCTIONS_KEYWORD,
72
        );
73
74
        [$exposedClassNames, $exposedClassRegexes] = $this->retrieveElements(
75
            $config,
76
            ConfigurationKeys::EXPOSE_CLASSES_SYMBOLS_KEYWORD,
77
        );
78
79
        [$exposedFunctionNames, $exposedFunctionRegexes] = $this->retrieveElements(
80
            $config,
81
            ConfigurationKeys::EXPOSE_FUNCTIONS_SYMBOLS_KEYWORD,
82
        );
83
84
        [$exposedConstantNames, $exposedConstantRegexes] = $this->retrieveElements(
85
            $config,
86
            ConfigurationKeys::EXPOSE_CONSTANTS_SYMBOLS_KEYWORD,
87
        );
88
89
        return SymbolsConfiguration::create(
90
            $exposeGlobalConstants,
91
            $exposeGlobalClasses,
92
            $exposeGlobalFunctions,
93
            NamespaceRegistry::create(
94
                $excludedNamespaceNames,
95
                $excludedNamespaceRegexes,
96
            ),
97
            null,
98
            SymbolRegistry::create(
99
                array_merge(
100
                    $exposedClassNames,
101
                    $legacyExposedSymbols,
102
                ),
103
                array_merge(
104
                    $exposedClassRegexes,
105
                    $legacyExposedSymbolsPatterns,
106
                ),
107
            ),
108
            SymbolRegistry::create(
109
                array_merge(
110
                    $exposedFunctionNames,
111
                    $legacyExposedSymbols,
112
                ),
113
                array_merge(
114
                    $exposedFunctionRegexes,
115
                    $legacyExposedSymbolsPatterns,
116
                ),
117
            ),
118
            SymbolRegistry::createForConstants(
119
                array_merge(
120
                    $exposedConstantNames,
121
                    $legacyExposedConstants,
122
                ),
123
                array_merge(
124
                    $exposedConstantRegexes,
125
                    $legacyExposedSymbolsPatterns,
126
                ),
127
            ),
128
        );
129
    }
130
131
    private static function retrieveExposeGlobalSymbol(array $config, string $key): bool
132
    {
133
        if (!array_key_exists($key, $config)) {
134
            return false;
135
        }
136
137
        $value = $config[$key];
138
139
        if (!is_bool($value)) {
140
            throw new InvalidArgumentException(
141
                sprintf(
142
                    'Expected %s to be a boolean, found "%s" instead.',
143
                    $key,
144
                    gettype($value),
145
                ),
146
            );
147
        }
148
149
        return $value;
150
    }
151
152
    /**
153
     * return list<string>
154
     */
155
    private static function retrieveLegacyExposedElements(array $config): array
156
    {
157
        $key = ConfigurationKeys::WHITELIST_KEYWORD;
158
159
        if (!array_key_exists($key, $config)) {
160
            return [];
161
        }
162
163
        $whitelist = $config[$key];
164
165
        if (!is_array($whitelist)) {
166
            throw new InvalidArgumentException(
167
                sprintf(
168
                    'Expected "%s" to be an array of strings, found "%s" instead.',
169
                    $key,
170
                    gettype($whitelist),
171
                ),
172
            );
173
        }
174
175
        foreach ($whitelist as $index => $className) {
176
            if (is_string($className)) {
177
                continue;
178
            }
179
180
            throw new InvalidArgumentException(
181
                sprintf(
182
                    'Expected whitelist to be an array of string, the "%d" element is not.',
183
                    $index,
184
                ),
185
            );
186
        }
187
188
        return array_values($whitelist);
189
    }
190
191
    /**
192
     * return array{string[], string[]}
193
     */
194
    private function retrieveElements(array $config, string $key): array
195
    {
196
        if (!array_key_exists($key, $config)) {
197
            return [[], []];
198
        }
199
200
        $symbolNamesAndRegexes = $config[$key];
201
202
        self::assertIsArrayOfStrings($config[$key], $key);
203
204
        // Store the strings in the keys for avoiding a unique check later on
205
        $names = [];
206
        $regexes = [];
207
208
        foreach ($symbolNamesAndRegexes as $index => $nameOrRegex) {
209
            if (!$this->regexChecker->isRegexLike($nameOrRegex)) {
210
                $names[$nameOrRegex] = null;
211
212
                continue;
213
            }
214
215
            $regex = $nameOrRegex;
216
217
            $this->assertValidRegex($regex, $key, (string) $index);
218
219
            $errorMessage = $this->regexChecker->validateRegex($regex);
220
221
            if (null !== $errorMessage) {
222
                throw new InvalidArgumentException(
223
                    sprintf(
224
                        'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.',
225
                        $key,
226
                        $regex,
227
                        $index,
228
                        $errorMessage,
229
                    ),
230
                );
231
            }
232
233
            // Ensure namespace comparisons are always case-insensitive
234
            // TODO: double check that we are not adding it twice or that adding it twice does not break anything
235
            $regex .= 'i';
236
            $regexes[$regex] = null;
237
        }
238
239
        return [
240
            array_keys($names),
241
            array_keys($regexes),
242
        ];
243
    }
244
245
    /**
246
     * @deprecated
247
     *
248
     * @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...
249
     * @param list<string> $excludedNamespaceNames
250
     */
251
    private static function parseLegacyExposedElements(array $elements, array $excludedNamespaceNames): array
252
    {
253
        $exposedSymbols = [];
254
        $exposedConstants = [];
255
        $exposedSymbolsPatterns = [];
256
        $excludedNamespaceNames = array_map('strtolower', $excludedNamespaceNames);
257
258
        foreach ($elements as $element) {
259
            $element = ltrim(trim($element), '\\');
260
261
            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

261
            /** @scrutinizer ignore-deprecated */ self::assertValidElement($element);
Loading history...
262
263
            if ('\*' === substr($element, -2)) {
264
                $excludedNamespaceNames[] = strtolower(substr($element, 0, -2));
265
            } elseif ('*' === $element) {
266
                $excludedNamespaceNames[] = '';
267
            } elseif (false !== strpos($element, '*')) {
268
                $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

268
                $exposedSymbolsPatterns[] = /** @scrutinizer ignore-deprecated */ self::createExposePattern($element);
Loading history...
269
            } else {
270
                $exposedSymbols[] = strtolower($element);
271
                $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

271
                $exposedConstants[] = /** @scrutinizer ignore-deprecated */ self::lowerCaseConstantName($element);
Loading history...
272
            }
273
        }
274
275
        return [
276
            $exposedSymbols,
277
            $exposedSymbolsPatterns,
278
            $exposedConstants,
279
            $excludedNamespaceNames,
280
        ];
281
    }
282
283
    /**
284
     * @psalm-assert string[] $value
285
     *
286
     * @param mixed $value
287
     */
288
    private static function assertIsArrayOfStrings($value, string $key): void
289
    {
290
        if (!is_array($value)) {
291
            throw new InvalidArgumentException(
292
                sprintf(
293
                    'Expected "%s" to be an array of strings, found "%s" instead.',
294
                    $key,
295
                    get_debug_type($value),
296
                ),
297
            );
298
        }
299
300
        foreach ($value as $index => $element) {
301
            if (is_string($element)) {
302
                continue;
303
            }
304
305
            throw new InvalidArgumentException(
306
                sprintf(
307
                    'Expected "%s" to be an array of strings, found "%s" for the element with the index "%s".',
308
                    $key,
309
                    get_debug_type($element),
310
                    $index,
311
                ),
312
            );
313
        }
314
    }
315
316
    private function assertValidRegex(string $regex, string $key, string $index): void
317
    {
318
        $errorMessage = $this->regexChecker->validateRegex($regex);
319
320
        if (null !== $errorMessage) {
321
            throw new InvalidArgumentException(
322
                sprintf(
323
                    'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.',
324
                    $key,
325
                    $regex,
326
                    $index,
327
                    $errorMessage,
328
                ),
329
            );
330
        }
331
    }
332
333
    /**
334
     * @deprecated
335
     */
336
    private static function assertValidElement(string $element): void
337
    {
338
        if ('' !== $element) {
339
            return;
340
        }
341
342
        throw new InvalidArgumentException(
343
            sprintf(
344
                'Invalid whitelist element "%s": cannot accept an empty string',
345
                $element,
346
            ),
347
        );
348
    }
349
350
    /**
351
     * @deprecated
352
     */
353
    private static function createExposePattern(string $element): string
354
    {
355
        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

355
        /** @scrutinizer ignore-deprecated */ self::assertValidPattern($element);
Loading history...
356
357
        return sprintf(
358
            '/^%s$/u',
359
            str_replace(
360
                '\\',
361
                '\\\\',
362
                str_replace(
363
                    '*',
364
                    '.*',
365
                    $element,
366
                ),
367
            ),
368
        );
369
    }
370
371
    /**
372
     * @deprecated
373
     */
374
    private static function assertValidPattern(string $element): void
375
    {
376
        if (1 !== native_preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) {
377
            throw new InvalidArgumentException(
378
                sprintf(
379
                    'Invalid whitelist pattern "%s".',
380
                    $element,
381
                ),
382
            );
383
        }
384
    }
385
386
    /**
387
     * @deprecated
388
     */
389
    private static function lowerCaseConstantName(string $name): string
390
    {
391
        $parts = explode('\\', $name);
392
393
        $lastPart = array_pop($parts);
394
395
        $parts = array_map('strtolower', $parts);
396
397
        $parts[] = $lastPart;
398
399
        return implode('\\', $parts);
400
    }
401
}
402