Passed
Pull Request — master (#582)
by Théo
02:12
created

Whitelist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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

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_map;
24
use function array_pop;
25
use function array_unique;
26
use function array_values;
27
use function count;
28
use function explode;
29
use function implode;
30
use function ltrim;
31
use function preg_match as native_preg_match;
32
use function Safe\array_flip;
33
use function Safe\sprintf;
34
use function Safe\substr;
35
use function str_replace;
36
use function strpos;
37
use function strtolower;
38
use function trim;
39
40
final class Whitelist implements Countable
41
{
42
    /**
43
     * @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...
44
     */
45
    private array $originalElements;
46
47
    /**
48
     * @var array<string, mixed>
49
     */
50
    private array $exposedSymbols;
51
52 15
    /**
53
     * @var array<string, mixed>
54
     */
55
    private array $exposedConstants;
56
57
    /**
58 15
     * @var list<string>
59 15
     */
60 15
    private array $exposedSymbolsPatterns;
61 15
62 15
    private bool $exposeGlobalConstants;
63
    private bool $exposeGlobalClasses;
64 15
    private bool $exposeGlobalFunctions;
65 12
66 2
    private array $whitelistedFunctions = [];
67
    private array $whitelistedClasses = [];
68
69 12
    private NamespaceRegistry $excludedNamespaces;
70
71
    /**
72
     * @param string[] $excludedNamespaceRegexes
73
     * @param string[] $excludedNamespaceNames
74
     */
75
    public static function create(
76
        bool $exposeGlobalConstants = false,
77
        bool $exposeGlobalClasses = false,
78 12
        bool $exposeGlobalFunctions = false,
79
        array $excludedNamespaceRegexes = [],
80 12
        array $excludedNamespaceNames = [],
81 2
        string ...$exposedElements
82 11
    ): self {
83 3
        $exposedSymbols = [];
84 9
        $exposedConstants = [];
85
        $exposedSymbolsPatterns = [];
86
        $originalElements = [];
87
        $excludedNamespaceNames = array_map('strtolower', $excludedNamespaceNames);
88
89
        foreach ($exposedElements as $element) {
90
            $element = ltrim(trim($element), '\\');
91
92
            self::assertValidElement($element);
93
94
            $originalElements[] = $element;
95
96
            if ('\*' === substr($element, -2)) {
97
                $excludedNamespaceNames[] = strtolower(substr($element, 0, -2));
98
            } elseif ('*' === $element) {
99
                $excludedNamespaceNames[] = '';
100 9
            } elseif (false !== strpos($element, '*')) {
101 9
                $exposedSymbolsPatterns[] = self::createExposePattern($element);
102
            } else {
103
                $exposedSymbols[] = strtolower($element);
104
                $exposedConstants[] = self::lowerCaseConstantName($element);
105 15
            }
106 15
        }
107 15
108 15
        return new self(
109 15
            $exposeGlobalConstants,
110 15
            $exposeGlobalClasses,
111 15
            $exposeGlobalFunctions,
112 15
            array_unique($originalElements),
113 15
            array_flip($exposedSymbols),
114
            array_flip($exposedConstants),
115
            array_unique($exposedSymbolsPatterns),
116
            array_unique($excludedNamespaceNames),
117
            array_unique($excludedNamespaceRegexes),
118
        );
119
    }
120
121
    /**
122
     * @param list<string>       $originalElements
123
     * @param array<string, int> $exposedSymbols
124
     * @param array<string, int> $exposedConstants
125
     * @param list<string>       $exposedSymbolsPatterns
126
     * @param list<string>       $excludedNamespaceNames
127
     * @param list<string>       $excludedNamespaceRegexes
128
     */
129
    public function __construct(
130
        bool $exposeGlobalConstants,
131
        bool $exposeGlobalClasses,
132
        bool $exposeGlobalFunctions,
133
        array $originalElements,
134 15
        array $exposedSymbols,
135
        array $exposedConstants,
136
        array $exposedSymbolsPatterns,
137
        array $excludedNamespaceNames,
138
        array $excludedNamespaceRegexes
139
    ) {
140
        $this->exposeGlobalConstants = $exposeGlobalConstants;
141
        $this->exposeGlobalClasses = $exposeGlobalClasses;
142
        $this->exposeGlobalFunctions = $exposeGlobalFunctions;
143
        $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...
144 15
        $this->exposedSymbols = $exposedSymbols;
145 15
        $this->exposedConstants = $exposedConstants;
146 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...
147 15
        $this->excludedNamespaces = NamespaceRegistry::create(
148 15
            $excludedNamespaceRegexes,
149 15
            $excludedNamespaceNames,
150 15
        );
151 15
    }
152
153
    public function belongsToExcludedNamespace(string $name): bool
154 521
    {
155
        return $this->excludedNamespaces->belongsToRegisteredNamespace($name);
156 521
    }
157
158 521
    public function isExcludedNamespace(string $name): bool
159 41
    {
160 37
        return $this->excludedNamespaces->isRegisteredNamespace($name);
161
    }
162
163
    /**
164 492
     * @internal
165
     */
166
    public function exposeGlobalFunctions(): bool
167 561
    {
168
        return $this->exposeGlobalFunctions;
169 561
    }
170
171 561
    public function isExposedFunctionFromGlobalNamespace(string $functionName): bool
172
    {
173
        return $this->exposeGlobalFunctions && !strpos($functionName, '\\');
174
    }
175 561
176 41
    public function recordWhitelistedFunction(FullyQualified $original, FullyQualified $alias): void
177 20
    {
178
        $this->whitelistedFunctions[(string) $original] = [(string) $original, (string) $alias];
179
    }
180 21
181 9
    public function getRecordedWhitelistedFunctions(): array
182
    {
183
        return array_values($this->whitelistedFunctions);
184 17
    }
185 17
186
    /**
187 17
     * @internal
188 17
     */
189 2
    public function exposeGlobalConstants(): bool
190
    {
191
        return $this->exposeGlobalConstants;
192
    }
193 15
194
    public function isExposedConstantFromGlobalNamespace(string $constantName): bool
195
    {
196 529
        return $this->exposeGlobalConstants && !strpos($constantName, '\\');
197
    }
198
199
    /**
200
     * @internal
201
     */
202 551
    public function exposeGlobalClasses(): bool
203
    {
204 551
        return $this->exposeGlobalClasses;
205
    }
206
207 109
    public function isExposedClassFromGlobalNamespace(string $className): bool
208
    {
209 109
        return $this->exposeGlobalClasses && !strpos($className, '\\');
210
    }
211
212 24
    public function recordWhitelistedClass(FullyQualified $original, FullyQualified $alias): void
213
    {
214 24
        $this->whitelistedClasses[(string) $original] = [(string) $original, (string) $alias];
215
    }
216
217 555
    public function getRecordedWhitelistedClasses(): array
218
    {
219 555
        return array_values($this->whitelistedClasses);
220
    }
221
222
    /**
223
     * Tells if a given symbol is exposed. Note however that it does not account for when:
224
     *
225 551
     * - The symbol belongs to the global namespace and the symbols of the global namespace of this type are exposed
226
     * - Belongs to an excluded namespace
227 551
     *
228
     * @param bool $constant Unlike other symbols, constants _can_ be case insensitive but 99% are not so we leave out
229
     *                       the case where they are not case sensitive.
230 81
     */
231
    public function isSymbolExposed(string $name, bool $constant = false): bool
232 81
    {
233
        if (!$constant && array_key_exists(strtolower($name), $this->exposedSymbols)) {
234
            return true;
235
        }
236
237
        if ($constant && array_key_exists(self::lowerCaseConstantName($name), $this->exposedConstants)) {
238 6
            return true;
239
        }
240 6
241
        foreach ($this->exposedSymbolsPatterns as $pattern) {
242
            $pattern = !$constant ? $pattern.'i' : $pattern;
243 324
244
            if (1 === native_preg_match($pattern, $name)) {
245 324
                return true;
246
            }
247
        }
248 82
249
        return false;
250 82
    }
251
252
    /**
253 555
     * @return string[]
254
     */
255 555
    public function toArray(): array
256
    {
257
        return $this->originalElements;
258
    }
259
260
    public function count(): int
261
    {
262
        return count($this->whitelistedFunctions) + count($this->whitelistedClasses);
263
    }
264
265
    public function registerFromRegistry(SymbolsRegistry $registry): void
266
    {
267 442
        foreach ($registry->getRecordedClasses() as [$original, $alias]) {
268
            $this->recordWhitelistedClass(
269 442
                new FullyQualified($original),
270 94
                new FullyQualified($alias),
271
            );
272
        }
273 400
274 24
        foreach ($registry->getRecordedFunctions() as [$original, $alias]) {
275
            $this->recordWhitelistedFunction(
276
                new FullyQualified($original),
277 385
                new FullyQualified($alias),
278 18
            );
279
        }
280 18
    }
281 11
282
    private static function assertValidElement(string $element): void
283
    {
284
        if ('' !== $element) {
285 378
            return;
286
        }
287
288
        throw new InvalidArgumentException(
289
            sprintf(
290
                'Invalid whitelist element "%s": cannot accept an empty string',
291
                $element,
292
            ),
293
        );
294
    }
295
296
    private static function createExposePattern(string $element): string
297
    {
298
        self::assertValidPattern($element);
299
300
        return sprintf(
301
            '/^%s$/u',
302
            str_replace(
303 554
                '\\',
304
                '\\\\',
305 554
                str_replace(
306
                    '*',
307
                    '.*',
308
                    $element,
309
                ),
310
            ),
311
        );
312
    }
313
314
    private static function assertValidPattern(string $element): void
315
    {
316
        if (1 !== native_preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) {
317
            throw new InvalidArgumentException(sprintf('Invalid whitelist pattern "%s".', $element));
318
        }
319
    }
320 124
321
    /**
322 124
     * Transforms the constant FQ name "Acme\Foo\X" to "acme\foo\X" since the namespace remains case insensitive for
323
     * constants regardless of whether or not constants actually are case insensitive.
324 124
     */
325
    private static function lowerCaseConstantName(string $name): string
326 124
    {
327
        $parts = explode('\\', $name);
328 124
329
        $lastPart = array_pop($parts);
330 124
331
        $parts = array_map('strtolower', $parts);
332
333 521
        $parts[] = $lastPart;
334
335 521
        return implode('\\', $parts);
336
    }
337
}
338