Passed
Pull Request — master (#519)
by Théo
02:25
created

Whitelist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

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

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