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