Repository navigation
Expand file tree
/
Copy pathConstantFunctionProvider.php
More file actions
122 lines (101 loc) · 4.63 KB
/
Copy pathConstantFunctionProvider.php
File metadata and controls
122 lines (101 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ExpressionLanguage;
/**
* Provides the constant() and enum() functions, restricted to a list of allowed constants.
*
* Each entry is a constant name, such as "PHP_EOL" or "App\Security\Roles::ROLE_ADMIN", in which "*" matches any sequence of characters except "::".
* For example, "App\Security\Roles::ROLE_*" allows the constants of that class whose name starts with "ROLE_", and "App\Enum\*::*" allows the cases of every enum in that namespace and in its sub-namespaces.
* Names are matched case-sensitively, and "self::", "static::" and "parent::" are never allowed since they resolve against the scope the expression runs in.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ConstantFunctionProvider implements ExpressionFunctionProviderInterface
{
private string $regexp;
/**
* @param string[] $allowedConstants
*/
public function __construct(array $allowedConstants)
{
$patterns = [];
foreach ($allowedConstants as $constant) {
if (!str_contains($constant, '::') && !str_contains($constant, '*') && (class_exists($constant) || interface_exists($constant))) {
throw new \InvalidArgumentException(\sprintf('"%1$s" is a class name, use "%1$s::*" to allow its constants.', $constant));
}
$pattern = str_replace('\*', '[^:]*', preg_quote(ltrim($constant, '\\')));
$patterns[] = str_ends_with($pattern, '[^:]*') ? $pattern.'+' : $pattern;
}
$this->regexp = $patterns ? '{^(?!\\\\?(?i:self|static|parent)::)\\\\?(?:'.implode('|', $patterns).')$}D' : '{(?!)}';
}
public function getFunctions(): array
{
return [
new ExpressionFunction('constant', $this->compileConstant(...), $this->evaluateConstant(...)),
new ExpressionFunction('enum', $this->compileEnum(...), $this->evaluateEnum(...)),
];
}
/**
* @internal
*/
public static function getConstant(string $name, string $regexp): mixed
{
if (!preg_match($regexp, $name)) {
throw new \RuntimeException(\sprintf('Constant "%s" is not allowed.', $name));
}
return \constant($name);
}
/**
* @internal
*/
public static function getEnumCase(string $name, string $regexp): \UnitEnum
{
if (!preg_match($regexp, $name)) {
throw new \RuntimeException(\sprintf('Enum case "%s" is not allowed.', $name));
}
if (!($value = \constant($name)) instanceof \UnitEnum) {
throw new \TypeError(\sprintf('The string "%s" is not the name of a valid enum case.', $name));
}
return $value;
}
private function compileConstant(string $name): string
{
if (null !== $literal = self::parseStringLiteral($name)) {
return preg_match($this->regexp, $literal) ? \sprintf('\constant(%s)', $name) : self::compileNotAllowed('Constant', $literal);
}
return \sprintf('\%s::getConstant(%s, %s)', self::class, $name, var_export($this->regexp, true));
}
private function evaluateConstant(array $arguments, string $name): mixed
{
return self::getConstant($name, $this->regexp);
}
private function compileEnum(string $name): string
{
if (null !== $literal = self::parseStringLiteral($name)) {
return preg_match($this->regexp, $literal) ? \sprintf('(\constant(%1$s) instanceof \UnitEnum ? \constant(%1$s) : throw new \TypeError(%2$s))', $name, var_export(\sprintf('The string "%s" is not the name of a valid enum case.', $literal), true)) : self::compileNotAllowed('Enum case', $literal);
}
return \sprintf('\%s::getEnumCase(%s, %s)', self::class, $name, var_export($this->regexp, true));
}
private function evaluateEnum(array $arguments, string $name): \UnitEnum
{
return self::getEnumCase($name, $this->regexp);
}
/**
* Returns the value of a string literal generated by Compiler::string(), or null for any other code.
*/
private static function parseStringLiteral(string $code): ?string
{
return preg_match('{^"((?:[^"$\\\\]++|\\\\\\\\)*+)"$}D', $code, $m) ? str_replace('\\\\', '\\', $m[1]) : null;
}
private static function compileNotAllowed(string $kind, string $name): string
{
return \sprintf('(throw new \RuntimeException(%s))', var_export(\sprintf('%s "%s" is not allowed.', $kind, $name), true));
}
}