Passed
Pull Request — master (#584)
by Théo
02:04
created

Whitelist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 10
nc 1
nop 9
dl 0
loc 21
ccs 8
cts 8
cp 1
crap 1
rs 9.9332
c 2
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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;
16
17
use Countable;
18
use Humbug\PhpScoper\Symbol\NamespaceRegistry;
19
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
20
use InvalidArgumentException;
21
use PhpParser\Node\Name\FullyQualified;
22
use function array_key_exists;
23
use function array_keys;
24
use function array_map;
25
use function array_pop;
26
use function array_unique;
27
use function array_values;
28
use function count;
29
use function explode;
30
use function implode;
31
use function ltrim;
32
use function preg_match as native_preg_match;
33
use function Safe\array_flip;
34
use function Safe\sprintf;
35
use function Safe\substr;
36
use function str_replace;
37
use function strpos;
38
use function strtolower;
39
use function trim;
40
41
final class Whitelist implements Countable
42
{
43
    /**
44
     * @var list<string>
0 ignored issues
show
Bug introduced by
The type Humbug\PhpScoper\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...
45
     */
46
    private array $originalElements;
47
48
    /**
49
     * @var array<string, mixed>
50
     */
51
    private array $exposedSymbols;
52 15
53
    /**
54
     * @var array<string, mixed>
55
     */
56
    private array $exposedConstants;
57
58 15
    /**
59 15
     * @var list<string>
60 15
     */
61 15
    private array $exposedSymbolsPatterns;
62 15
63
    private bool $exposeGlobalConstants;
64 15
    private bool $exposeGlobalClasses;
65 12
    private bool $exposeGlobalFunctions;
66 2
67
    private array $whitelistedFunctions = [];
68
    private array $whitelistedClasses = [];
69 12
70
    private NamespaceRegistry $excludedNamespaces;
71
72
    /**
73
     * @param string[] $excludedNamespaceRegexes
74
     * @param string[] $excludedNamespaceNames
75
     */
76
    public static function create(
77
        bool $exposeGlobalConstants = false,
78 12
        bool $exposeGlobalClasses = false,
79
        bool $exposeGlobalFunctions = false,
80 12
        array $excludedNamespaceRegexes = [],
81 2
        array $excludedNamespaceNames = [],
82 11
        string ...$exposedElements
83 3
    ): self {
84 9
        $exposedSymbols = [];
85
        $exposedConstants = [];
86
        $exposedSymbolsPatterns = [];
87
        $originalElements = [];
88
        $excludedNamespaceNames = array_map('strtolower', $excludedNamespaceNames);
89
90
        foreach ($exposedElements as $element) {
91
            $element = ltrim(trim($element), '\\');
92
93
            self::assertValidElement($element);
94
95
            $originalElements[] = $element;
96
97
            if ('\*' === substr($element, -2)) {
98
                $excludedNamespaceNames[] = strtolower(substr($element, 0, -2));
99
            } elseif ('*' === $element) {
100 9
                $excludedNamespaceNames[] = '';
101 9
            } elseif (false !== strpos($element, '*')) {
102
                $exposedSymbolsPatterns[] = self::createExposePattern($element);
103
            } else {
104
                $exposedSymbols[] = strtolower($element);
105 15
                $exposedConstants[] = self::lowerCaseConstantName($element);
106 15
            }
107 15
        }
108 15
109 15
        return new self(
110 15
            $exposeGlobalConstants,
111 15
            $exposeGlobalClasses,
112 15
            $exposeGlobalFunctions,
113 15
            array_unique($originalElements),
114
            array_flip($exposedSymbols),
115
            array_flip($exposedConstants),
116
            array_unique($exposedSymbolsPatterns),
117
            array_unique($excludedNamespaceNames),
118
            array_unique($excludedNamespaceRegexes),
119
        );
120
    }
121
122
    /**
123
     * @param list<string>       $originalElements
124
     * @param array<string, int> $exposedSymbols
125
     * @param array<string, int> $exposedConstants
126
     * @param list<string>       $exposedSymbolsPatterns
127
     * @param list<string>       $excludedNamespaceNames
128
     * @param list<string>       $excludedNamespaceRegexes
129
     */
130
    public function __construct(
131
        bool $exposeGlobalConstants,
132
        bool $exposeGlobalClasses,
133
        bool $exposeGlobalFunctions,
134 15
        array $originalElements,
135
        array $exposedSymbols,
136
        array $exposedConstants,
137
        array $exposedSymbolsPatterns,
138
        array $excludedNamespaceNames,
139
        array $excludedNamespaceRegexes
140
    ) {
141
        $this->exposeGlobalConstants = $exposeGlobalConstants;
142
        $this->exposeGlobalClasses = $exposeGlobalClasses;
143
        $this->exposeGlobalFunctions = $exposeGlobalFunctions;
144 15
        $this->originalElements = $originalElements;
0 ignored issues
show
Documentation Bug introduced by
It seems like $originalElements of type array is incompatible with the declared type Humbug\PhpScoper\list of property $originalElements.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
145 15
        $this->exposedSymbols = $exposedSymbols;
146 15
        $this->exposedConstants = $exposedConstants;
147 15
        $this->exposedSymbolsPatterns = $exposedSymbolsPatterns;
0 ignored issues
show
Documentation Bug introduced by
It seems like $exposedSymbolsPatterns of type array is incompatible with the declared type Humbug\PhpScoper\list of property $exposedSymbolsPatterns.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
148 15
        $this->excludedNamespaces = NamespaceRegistry::create(
149 15
            $excludedNamespaceRegexes,
150 15
            $excludedNamespaceNames,
151 15
        );
152
    }
153
154 521
    public function belongsToExcludedNamespace(string $name): bool
155
    {
156 521
        return $this->excludedNamespaces->belongsToRegisteredNamespace($name);
157
    }
158 521
159 41
    public function isExcludedNamespace(string $name): bool
160 37
    {
161
        return $this->excludedNamespaces->isRegisteredNamespace($name);
162
    }
163
164 492
    /**
165
     * @internal
166
     */
167 561
    public function getExcludedNamespaces(): NamespaceRegistry
168
    {
169 561
        return $this->excludedNamespaces;
170
    }
171 561
172
    /**
173
     * @internal
174
     */
175 561
    public function getExposedSymbols(): array
176 41
    {
177 20
        return array_keys($this->exposedSymbols);
178
    }
179
180 21
    /**
181 9
     * @internal
182
     */
183
    public function getExposedConstants(): array
184 17
    {
185 17
        return array_keys($this->exposedConstants);
186
    }
187 17
188 17
    /**
189 2
     * @internal
190
     */
191
    public function getExposedSymbolsPatterns(): array
192
    {
193 15
        return $this->exposedSymbolsPatterns;
194
    }
195
196 529
    /**
197
     * @internal
198
     */
199
    public function exposeGlobalFunctions(): bool
200
    {
201
        return $this->exposeGlobalFunctions;
202 551
    }
203
204 551
    public function isExposedFunctionFromGlobalNamespace(string $functionName): bool
205
    {
206
        return $this->exposeGlobalFunctions && !strpos($functionName, '\\');
207 109
    }
208
209 109
    public function recordWhitelistedFunction(FullyQualified $original, FullyQualified $alias): void
210
    {
211
        $this->whitelistedFunctions[(string) $original] = [(string) $original, (string) $alias];
212 24
    }
213
214 24
    public function getRecordedWhitelistedFunctions(): array
215
    {
216
        return array_values($this->whitelistedFunctions);
217 555
    }
218
219 555
    /**
220
     * @internal
221
     */
222
    public function exposeGlobalConstants(): bool
223
    {
224
        return $this->exposeGlobalConstants;
225 551
    }
226
227 551
    public function isExposedConstantFromGlobalNamespace(string $constantName): bool
228
    {
229
        return $this->exposeGlobalConstants && !strpos($constantName, '\\');
230 81
    }
231
232 81
    /**
233
     * @internal
234
     */
235
    public function exposeGlobalClasses(): bool
236
    {
237
        return $this->exposeGlobalClasses;
238 6
    }
239
240 6
    public function isExposedClassFromGlobalNamespace(string $className): bool
241
    {
242
        return $this->exposeGlobalClasses && !strpos($className, '\\');
243 324
    }
244
245 324
    public function recordWhitelistedClass(FullyQualified $original, FullyQualified $alias): void
246
    {
247
        $this->whitelistedClasses[(string) $original] = [(string) $original, (string) $alias];
248 82
    }
249
250 82
    public function getRecordedWhitelistedClasses(): array
251
    {
252
        return array_values($this->whitelistedClasses);
253 555
    }
254
255 555
    /**
256
     * Tells if a given symbol is exposed. Note however that it does not account for when:
257
     *
258
     * - The symbol belongs to the global namespace and the symbols of the global namespace of this type are exposed
259
     * - Belongs to an excluded namespace
260
     *
261
     * @param bool $constant Unlike other symbols, constants _can_ be case insensitive but 99% are not so we leave out
262
     *                       the case where they are not case sensitive.
263
     */
264
    public function isSymbolExposed(string $name, bool $constant = false): bool
265
    {
266
        if (!$constant && array_key_exists(strtolower($name), $this->exposedSymbols)) {
267 442
            return true;
268
        }
269 442
270 94
        if ($constant && array_key_exists(self::lowerCaseConstantName($name), $this->exposedConstants)) {
271
            return true;
272
        }
273 400
274 24
        foreach ($this->exposedSymbolsPatterns as $pattern) {
275
            $pattern = !$constant ? $pattern.'i' : $pattern;
276
277 385
            if (1 === native_preg_match($pattern, $name)) {
278 18
                return true;
279
            }
280 18
        }
281 11
282
        return false;
283
    }
284
285 378
    /**
286
     * @return string[]
287
     */
288
    public function toArray(): array
289
    {
290
        return $this->originalElements;
291
    }
292
293
    public function count(): int
294
    {
295
        return count($this->whitelistedFunctions) + count($this->whitelistedClasses);
296
    }
297
298
    public function registerFromRegistry(SymbolsRegistry $registry): void
299
    {
300
        foreach ($registry->getRecordedClasses() as [$original, $alias]) {
301
            $this->recordWhitelistedClass(
302
                new FullyQualified($original),
303 554
                new FullyQualified($alias),
304
            );
305 554
        }
306
307
        foreach ($registry->getRecordedFunctions() as [$original, $alias]) {
308
            $this->recordWhitelistedFunction(
309
                new FullyQualified($original),
310
                new FullyQualified($alias),
311
            );
312
        }
313
    }
314
315
    private static function assertValidElement(string $element): void
316
    {
317
        if ('' !== $element) {
318
            return;
319
        }
320 124
321
        throw new InvalidArgumentException(
322 124
            sprintf(
323
                'Invalid whitelist element "%s": cannot accept an empty string',
324 124
                $element,
325
            ),
326 124
        );
327
    }
328 124
329
    private static function createExposePattern(string $element): string
330 124
    {
331
        self::assertValidPattern($element);
332
333 521
        return sprintf(
334
            '/^%s$/u',
335 521
            str_replace(
336
                '\\',
337 521
                '\\\\',
338 23
                str_replace(
339
                    '*',
340
                    '.*',
341 521
                    $element,
342
                ),
343 521
            ),
344
        );
345 521
    }
346
347
    private static function assertValidPattern(string $element): void
348
    {
349
        if (1 !== native_preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) {
350
            throw new InvalidArgumentException(sprintf('Invalid whitelist pattern "%s".', $element));
351
        }
352
    }
353
354
    /**
355
     * Transforms the constant FQ name "Acme\Foo\X" to "acme\foo\X" since the namespace remains case insensitive for
356
     * constants regardless of whether or not constants actually are case insensitive.
357
     */
358
    private static function lowerCaseConstantName(string $name): string
359
    {
360
        $parts = explode('\\', $name);
361
362
        $lastPart = array_pop($parts);
363
364
        $parts = array_map('strtolower', $parts);
365
366
        $parts[] = $lastPart;
367
368
        return implode('\\', $parts);
369
    }
370
}
371