Passed
Push — master ( 483c26...9bdb2a )
by Théo
01:56
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

Importance

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