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