Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace PhpParser;
4
 
5
use function array_merge;
6
use PhpParser\Node\Expr;
7
use PhpParser\Node\Scalar;
8
 
9
/**
10
 * Evaluates constant expressions.
11
 *
12
 * This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
13
 * evaluated without further context. If a subexpression is not of this type, a user-provided
14
 * fallback evaluator is invoked. To support all constant expressions that are also supported by
15
 * PHP (and not already handled by this class), the fallback evaluator must be able to handle the
16
 * following node types:
17
 *
18
 *  * All Scalar\MagicConst\* nodes.
19
 *  * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
20
 *  * Expr\ClassConstFetch nodes.
21
 *
22
 * The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
23
 *
24
 * The evaluation is dependent on runtime configuration in two respects: Firstly, floating
25
 * point to string conversions are affected by the precision ini setting. Secondly, they are also
26
 * affected by the LC_NUMERIC locale.
27
 */
28
class ConstExprEvaluator
29
{
30
    private $fallbackEvaluator;
31
 
32
    /**
33
     * Create a constant expression evaluator.
34
     *
35
     * The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
36
     * class doc comment for more information.
37
     *
38
     * @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
39
     */
40
    public function __construct(callable $fallbackEvaluator = null) {
41
        $this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
42
            throw new ConstExprEvaluationException(
43
                "Expression of type {$expr->getType()} cannot be evaluated"
44
            );
45
        };
46
    }
47
 
48
    /**
49
     * Silently evaluates a constant expression into a PHP value.
50
     *
51
     * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
52
     * The original source of the exception is available through getPrevious().
53
     *
54
     * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
55
     * constructor will be invoked. By default, if no fallback is provided, an exception of type
56
     * ConstExprEvaluationException is thrown.
57
     *
58
     * See class doc comment for caveats and limitations.
59
     *
60
     * @param Expr $expr Constant expression to evaluate
61
     * @return mixed Result of evaluation
62
     *
63
     * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
64
     */
65
    public function evaluateSilently(Expr $expr) {
66
        set_error_handler(function($num, $str, $file, $line) {
67
            throw new \ErrorException($str, 0, $num, $file, $line);
68
        });
69
 
70
        try {
71
            return $this->evaluate($expr);
72
        } catch (\Throwable $e) {
73
            if (!$e instanceof ConstExprEvaluationException) {
74
                $e = new ConstExprEvaluationException(
75
                    "An error occurred during constant expression evaluation", 0, $e);
76
            }
77
            throw $e;
78
        } finally {
79
            restore_error_handler();
80
        }
81
    }
82
 
83
    /**
84
     * Directly evaluates a constant expression into a PHP value.
85
     *
86
     * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
87
     * into a ConstExprEvaluationException.
88
     *
89
     * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
90
     * constructor will be invoked. By default, if no fallback is provided, an exception of type
91
     * ConstExprEvaluationException is thrown.
92
     *
93
     * See class doc comment for caveats and limitations.
94
     *
95
     * @param Expr $expr Constant expression to evaluate
96
     * @return mixed Result of evaluation
97
     *
98
     * @throws ConstExprEvaluationException if the expression cannot be evaluated
99
     */
100
    public function evaluateDirectly(Expr $expr) {
101
        return $this->evaluate($expr);
102
    }
103
 
104
    private function evaluate(Expr $expr) {
105
        if ($expr instanceof Scalar\LNumber
106
            || $expr instanceof Scalar\DNumber
107
            || $expr instanceof Scalar\String_
108
        ) {
109
            return $expr->value;
110
        }
111
 
112
        if ($expr instanceof Expr\Array_) {
113
            return $this->evaluateArray($expr);
114
        }
115
 
116
        // Unary operators
117
        if ($expr instanceof Expr\UnaryPlus) {
118
            return +$this->evaluate($expr->expr);
119
        }
120
        if ($expr instanceof Expr\UnaryMinus) {
121
            return -$this->evaluate($expr->expr);
122
        }
123
        if ($expr instanceof Expr\BooleanNot) {
124
            return !$this->evaluate($expr->expr);
125
        }
126
        if ($expr instanceof Expr\BitwiseNot) {
127
            return ~$this->evaluate($expr->expr);
128
        }
129
 
130
        if ($expr instanceof Expr\BinaryOp) {
131
            return $this->evaluateBinaryOp($expr);
132
        }
133
 
134
        if ($expr instanceof Expr\Ternary) {
135
            return $this->evaluateTernary($expr);
136
        }
137
 
138
        if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
139
            return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
140
        }
141
 
142
        if ($expr instanceof Expr\ConstFetch) {
143
            return $this->evaluateConstFetch($expr);
144
        }
145
 
146
        return ($this->fallbackEvaluator)($expr);
147
    }
148
 
149
    private function evaluateArray(Expr\Array_ $expr) {
150
        $array = [];
151
        foreach ($expr->items as $item) {
152
            if (null !== $item->key) {
153
                $array[$this->evaluate($item->key)] = $this->evaluate($item->value);
154
            } elseif ($item->unpack) {
155
                $array = array_merge($array, $this->evaluate($item->value));
156
            } else {
157
                $array[] = $this->evaluate($item->value);
158
            }
159
        }
160
        return $array;
161
    }
162
 
163
    private function evaluateTernary(Expr\Ternary $expr) {
164
        if (null === $expr->if) {
165
            return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
166
        }
167
 
168
        return $this->evaluate($expr->cond)
169
            ? $this->evaluate($expr->if)
170
            : $this->evaluate($expr->else);
171
    }
172
 
173
    private function evaluateBinaryOp(Expr\BinaryOp $expr) {
174
        if ($expr instanceof Expr\BinaryOp\Coalesce
175
            && $expr->left instanceof Expr\ArrayDimFetch
176
        ) {
177
            // This needs to be special cased to respect BP_VAR_IS fetch semantics
178
            return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
179
                ?? $this->evaluate($expr->right);
180
        }
181
 
182
        // The evaluate() calls are repeated in each branch, because some of the operators are
183
        // short-circuiting and evaluating the RHS in advance may be illegal in that case
184
        $l = $expr->left;
185
        $r = $expr->right;
186
        switch ($expr->getOperatorSigil()) {
187
            case '&':   return $this->evaluate($l) &   $this->evaluate($r);
188
            case '|':   return $this->evaluate($l) |   $this->evaluate($r);
189
            case '^':   return $this->evaluate($l) ^   $this->evaluate($r);
190
            case '&&':  return $this->evaluate($l) &&  $this->evaluate($r);
191
            case '||':  return $this->evaluate($l) ||  $this->evaluate($r);
192
            case '??':  return $this->evaluate($l) ??  $this->evaluate($r);
193
            case '.':   return $this->evaluate($l) .   $this->evaluate($r);
194
            case '/':   return $this->evaluate($l) /   $this->evaluate($r);
195
            case '==':  return $this->evaluate($l) ==  $this->evaluate($r);
196
            case '>':   return $this->evaluate($l) >   $this->evaluate($r);
197
            case '>=':  return $this->evaluate($l) >=  $this->evaluate($r);
198
            case '===': return $this->evaluate($l) === $this->evaluate($r);
199
            case 'and': return $this->evaluate($l) and $this->evaluate($r);
200
            case 'or':  return $this->evaluate($l) or  $this->evaluate($r);
201
            case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
202
            case '-':   return $this->evaluate($l) -   $this->evaluate($r);
203
            case '%':   return $this->evaluate($l) %   $this->evaluate($r);
204
            case '*':   return $this->evaluate($l) *   $this->evaluate($r);
205
            case '!=':  return $this->evaluate($l) !=  $this->evaluate($r);
206
            case '!==': return $this->evaluate($l) !== $this->evaluate($r);
207
            case '+':   return $this->evaluate($l) +   $this->evaluate($r);
208
            case '**':  return $this->evaluate($l) **  $this->evaluate($r);
209
            case '<<':  return $this->evaluate($l) <<  $this->evaluate($r);
210
            case '>>':  return $this->evaluate($l) >>  $this->evaluate($r);
211
            case '<':   return $this->evaluate($l) <   $this->evaluate($r);
212
            case '<=':  return $this->evaluate($l) <=  $this->evaluate($r);
213
            case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
214
        }
215
 
216
        throw new \Exception('Should not happen');
217
    }
218
 
219
    private function evaluateConstFetch(Expr\ConstFetch $expr) {
220
        $name = $expr->name->toLowerString();
221
        switch ($name) {
222
            case 'null': return null;
223
            case 'false': return false;
224
            case 'true': return true;
225
        }
226
 
227
        return ($this->fallbackEvaluator)($expr);
228
    }
229
}