Skip to main content

Error handling

Every exception the engine raises lives under Qanna\ExpressionEngine\Exceptions and extends \RuntimeException.

Expressions on a low-code surface are authored by people (or stored config) that can get them wrong, so evaluating untrusted expressions should always be wrapped:

use Qanna\ExpressionEngine\Exceptions\LexerException;
use Qanna\ExpressionEngine\Exceptions\ParserException;
use Qanna\ExpressionEngine\Exceptions\EvaluatorException;

try {
$value = $engine->evaluate($storedExpression, $context);
} catch (LexerException | ParserException $e) {
// the expression string is malformed
} catch (EvaluatorException $e) {
// the expression is well-formed but failed to run against this data
}

The exceptions

LexerException

The expression contains a character or token the language doesn't recognise — an unterminated string, a stray symbol, a single = (did you mean ==?), a single & or |.

Extends \RuntimeException directly (not EvaluatorException). The message includes position N.

ParserException

The tokens are valid but their arrangement isn't — an unbalanced parenthesis, a missing : in a ternary, a trailing operator, an unexpected token.

Extends \RuntimeException directly. The message includes position N, which the playground uses to draw a caret and you can use the same way in an editor UI.

EvaluatorException

The base class for everything that goes wrong while running a well-formed expression. Raised directly for:

  • division or modulo by zero (the / and % operators)
  • an operator applied to incompatible types (e.g. -"abc")
  • an unknown function name
  • a date string that can't be parsed
  • env() called with a non-whitelisted key
  • sqrt() of a negative number

UnknownMethodException — extends EvaluatorException

A method name that isn't registered for any type.

Unknown method 'uppercase' on type 'string'.

TypeMismatchException — extends EvaluatorException

A method that exists, but not for the type it was called on — e.g. .sum() on a string.

Method 'sum' does not support type 'string'. Expected 'array'.

UnresolvedPathException — extends EvaluatorException

Only thrown in strict mode: a path couldn't be resolved from the context.

Path 'trigger.user' could not be resolved from context.

In the default (non-strict) mode, an unresolved path is null and this is never thrown.

Catching everything

LexerException and ParserException do not extend EvaluatorException, so a single catch (EvaluatorException $e) misses syntax errors. To handle any engine failure in one place, catch all three base types, or \RuntimeException:

try {
$value = $engine->evaluate($storedExpression, $context);
} catch (\RuntimeException $e) {
report($e);
$value = null;
}

A note on non-engine errors

div() and mod() by zero raise PHP's native \DivisionByZeroError (not an engine exception). If you evaluate expressions that can reach those methods with untrusted input, catch \Throwable rather than only the engine's own hierarchy.