Passed
Push — master ( 40b894...ddcf42 )
by Théo
02:15
created

Whitelist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

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