Chris@13
|
1 Constant expression evaluation
|
Chris@13
|
2 ==============================
|
Chris@13
|
3
|
Chris@13
|
4 Initializers for constants, properties, parameters, etc. have limited support for expressions. For
|
Chris@13
|
5 example:
|
Chris@13
|
6
|
Chris@13
|
7 ```php
|
Chris@13
|
8 <?php
|
Chris@13
|
9 class Test {
|
Chris@13
|
10 const SECONDS_IN_HOUR = 60 * 60;
|
Chris@13
|
11 const SECONDS_IN_DAY = 24 * self::SECONDS_IN_HOUR;
|
Chris@13
|
12 }
|
Chris@13
|
13 ```
|
Chris@13
|
14
|
Chris@13
|
15 PHP-Parser supports evaluation of such constant expressions through the `ConstExprEvaluator` class:
|
Chris@13
|
16
|
Chris@13
|
17 ```php
|
Chris@13
|
18 <?php
|
Chris@13
|
19
|
Chris@13
|
20 use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
Chris@13
|
21
|
Chris@13
|
22 $evalutator = new ConstExprEvaluator();
|
Chris@13
|
23 try {
|
Chris@13
|
24 $value = $evalutator->evaluateSilently($someExpr);
|
Chris@13
|
25 } catch (ConstExprEvaluationException $e) {
|
Chris@13
|
26 // Either the expression contains unsupported expression types,
|
Chris@13
|
27 // or an error occurred during evaluation
|
Chris@13
|
28 }
|
Chris@13
|
29 ```
|
Chris@13
|
30
|
Chris@13
|
31 Error handling
|
Chris@13
|
32 --------------
|
Chris@13
|
33
|
Chris@13
|
34 The constant evaluator provides two methods, `evaluateDirectly()` and `evaluateSilently()`, which
|
Chris@13
|
35 differ in error behavior. `evaluateDirectly()` will evaluate the expression as PHP would, including
|
Chris@13
|
36 any generated warnings or Errors. `evaluateSilently()` will instead convert warnings and Errors into
|
Chris@13
|
37 a `ConstExprEvaluationException`. For example:
|
Chris@13
|
38
|
Chris@13
|
39 ```php
|
Chris@13
|
40 <?php
|
Chris@13
|
41
|
Chris@13
|
42 use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
Chris@13
|
43 use PhpParser\Node\{Expr, Scalar};
|
Chris@13
|
44
|
Chris@13
|
45 $evaluator = new ConstExprEvaluator();
|
Chris@13
|
46
|
Chris@13
|
47 // 10 / 0
|
Chris@13
|
48 $expr = new Expr\BinaryOp\Div(new Scalar\LNumber(10), new Scalar\LNumber(0));
|
Chris@13
|
49
|
Chris@13
|
50 var_dump($evaluator->evaluateDirectly($expr)); // float(INF)
|
Chris@13
|
51 // Warning: Division by zero
|
Chris@13
|
52
|
Chris@13
|
53 try {
|
Chris@13
|
54 $evaluator->evaluateSilently($expr);
|
Chris@13
|
55 } catch (ConstExprEvaluationException $e) {
|
Chris@13
|
56 var_dump($e->getPrevious()->getMessage()); // Division by zero
|
Chris@13
|
57 }
|
Chris@13
|
58 ```
|
Chris@13
|
59
|
Chris@13
|
60 For the purposes of static analysis, you will likely want to use `evaluateSilently()` and leave
|
Chris@13
|
61 erroring expressions unevaluated.
|
Chris@13
|
62
|
Chris@13
|
63 Unsupported expressions and evaluator fallback
|
Chris@13
|
64 ----------------------------------------------
|
Chris@13
|
65
|
Chris@13
|
66 The constant expression evaluator supports all expression types that are permitted in constant
|
Chris@13
|
67 expressions, apart from the following:
|
Chris@13
|
68
|
Chris@13
|
69 * `Scalar\MagicConst\*`
|
Chris@13
|
70 * `Expr\ConstFetch` (only null/false/true are handled)
|
Chris@13
|
71 * `Expr\ClassConstFetch`
|
Chris@13
|
72
|
Chris@13
|
73 Handling these expression types requires non-local information, such as which global constants are
|
Chris@13
|
74 defined. By default, the evaluator will throw a `ConstExprEvaluationException` when it encounters
|
Chris@13
|
75 an unsupported expression type.
|
Chris@13
|
76
|
Chris@13
|
77 It is possible to override this behavior and support resolution for these expression types by
|
Chris@13
|
78 specifying an evaluation fallback function:
|
Chris@13
|
79
|
Chris@13
|
80 ```php
|
Chris@13
|
81 <?php
|
Chris@13
|
82
|
Chris@13
|
83 use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
Chris@13
|
84 use PhpParser\Node\Expr;
|
Chris@13
|
85
|
Chris@13
|
86 $evalutator = new ConstExprEvaluator(function(Expr $expr) {
|
Chris@13
|
87 if ($expr instanceof Expr\ConstFetch) {
|
Chris@13
|
88 return fetchConstantSomehow($expr);
|
Chris@13
|
89 }
|
Chris@13
|
90 if ($expr instanceof Expr\ClassConstFetch) {
|
Chris@13
|
91 return fetchClassConstantSomehow($expr);
|
Chris@13
|
92 }
|
Chris@13
|
93 // etc.
|
Chris@13
|
94 throw new ConstExprEvaluationException(
|
Chris@13
|
95 "Expression of type {$expr->getType()} cannot be evaluated");
|
Chris@13
|
96 });
|
Chris@13
|
97
|
Chris@13
|
98 try {
|
Chris@13
|
99 $evalutator->evaluateSilently($someExpr);
|
Chris@13
|
100 } catch (ConstExprEvaluationException $e) {
|
Chris@13
|
101 // Handle exception
|
Chris@13
|
102 }
|
Chris@13
|
103 ```
|
Chris@13
|
104
|
Chris@13
|
105 Implementers are advised to ensure that evaluation of indirect constant references cannot lead to
|
Chris@13
|
106 infinite recursion. For example, the following code could lead to infinite recursion if constant
|
Chris@13
|
107 lookup is implemented naively.
|
Chris@13
|
108
|
Chris@13
|
109 ```php
|
Chris@13
|
110 <?php
|
Chris@13
|
111 class Test {
|
Chris@13
|
112 const A = self::B;
|
Chris@13
|
113 const B = self::A;
|
Chris@13
|
114 }
|
Chris@13
|
115 ``` |