eventjet / ausdruck
A small expression engine for PHP
Requires
- php: >=8.3
- ext-ctype: *
Requires (Dev)
- amphp/dns: ^2.4
- amphp/socket: ^2.3.1
- beberlei/assert: ^3.3.3
- composer/pcre: ^3.3
- eventjet/coding-standard: ^3.12
- friendsofphp/php-cs-fixer: ^3.88
- infection/infection: ^0.31.2
- maglnet/composer-require-checker: ^4.16
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^2.1.29
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.3
- psalm/plugin-phpunit: ^0.19.5
- roave/backward-compatibility-check: ^8.14
- vimeo/psalm: ^6.13
This package is auto-updated.
Last update: 2026-07-15 06:48:05 UTC
README
A small expression engine for PHP.
Quick start
composer require eventjet/ausdruck
use Eventjet\Ausdruck\Parser\ExpressionParser; use Eventjet\Ausdruck\Parser\Types; use Eventjet\Ausdruck\Type; $expression = ExpressionParser::parse( 'joe:MyPersonType.name:string()', new Types(['MyPersonType' => Type::listOf(Type::string())]), ); $scope = new Scope( // Passing values to the expression ['joe' => ['joe']], // Custom function definitions ['name' => static fn (array $person): string => $person[0]], ); $name = $expression->evaluate($scope); assert($name === 'Joe');
Documentation
Accessing scope variables
Syntax: varName:type
Scope variables are passed from PHP when it calls evaluate() on the expression:
use Eventjet\Ausdruck\Parser\ExpressionParser; use Eventjet\Ausdruck\Scope; $x = ExpressionParser::parse('foo:int') ->evaluate(new Scope(['foo' => 123])); assert($x === 123);
Examples
foo:int, foo:list<string>
See Types
Literals
123: Integer"foo": String1.23: Float[1, myInt:int, 3]: List of integers["foo", myString:string, "bar"]: List of strings
Operators
Both operands must be of the same type.
| Operator | Description | Example | Note |
|---|---|---|---|
=== |
Equality | foo:string === "bar" |
|
- |
Subtraction | foo:int - bar:int |
Operands must be of type int or float |
> |
Greater than | foo:int > bar:int |
Operands must be of type int or float |
|| |
Logical OR | foo:bool || bar:bool |
Operands must be of type bool |
| && | Logical AND | foo:bool && bar:bool |
Operands must be of type bool |
Where's the rest? We're implementing more as we need them.
Precedence
Operators bind from tightest to loosest in this order:
| Operator | Associativity |
|---|---|
. (field, method) |
Left |
- (negation) |
Right |
- (subtraction) |
Left |
===, > |
Non-associative |
&& |
Left |
|| |
Left |
As in most languages, && binds tighter than ||, so a:bool && b:bool || c:bool means
(a:bool && b:bool) || c:bool, and a:int - b:int - c:int means (a:int - b:int) - c:int.
=== and > are non-associative: a:int > b:int > c:int is a syntax error rather than a comparison against the
bool that the first comparison produces. Chain with && instead.
There are no grouping parentheses yet, so you can't override the precedence.
Anywhere an expression is expected, it can be a whole one, not only at the top level. List items, struct field values, function arguments, and lambda bodies are all full expressions, so operators, calls, and field access are available in all of them:
[a:int - 1, 10]
{total: a:int - b:int}
foo:string.substr(a:int - 1, 2)
names:list<string>.contains:bool(user:{ name: string }.name)
{matches: needle:string === haystack:string.substr:string(0, 1) || always:bool}
Types
The following types are supported:
int: Integerstring: Stringbool: Booleanfloat: Floating point numberlist<T>: List of type Tmap<K, V>: Map with key type K and value type V- Any other type will be treated as an alias that you will have to provide when parsing the expression:
use Eventjet\Ausdruck\Parser\ExpressionParser; use Eventjet\Ausdruck\Type; ExpressionParser::parse('foo:MyType', ['MyType' => Type::alias(Type::listOf(Type::string()))]);
Functions
Syntax: target.functionName:returnType(arg1, arg2, ...)
The target can be any expression. It will be passed as the first argument to the function.
Example
haystack:list<string>.contains:bool(needle:string)
Built-In Functions
| Function | Description | Example |
|---|---|---|
count |
Returns the number of elements in a list | foo:list<string>.count:int() |
contains |
Returns whether a list contains a value | foo:list<string>.contains:bool("bar") |
head |
Returns the first element of a list as an Option |
foo:list<string>.head:Option<string>() |
isSome |
Takes an Option and returns whether it is Some |
foo:Option<int>.isSome:bool() |
map |
Returns a new list with the results of applying a function | foo:list<int>.map:list<int>(|i| i:int - 2) |
some |
Returns whether any element matches a predicate | foo:list<int>.some:bool(|item| item:int > 5) |
substr |
Returns a substring of a string | foo:string.substr:string(0, 5) |
tail |
Returns all elements of a list except the first | foo:list<string>.tail:list<string>() |
take |
Returns the first n elements of a list | foo:list<string>.take:list<string>(5) |
unique |
Returns a list with duplicate elements removed | foo:list<string>.unique:list<string>() |
Custom Functions
You can pass custom functions along with the scope variables:
use Eventjet\Ausdruck\Parser\ExpressionParser;use Eventjet\Ausdruck\Scope; $scope = new Scope( ['foo' => 'My secret'], ['mask' => fn (string $str, string $mask) => str_repeat($mask, strlen($str))] ); $result = ExpressionParser::parse('foo:string.mask("x")')->evaluate($scope); assert($result === 'xxxxxxxxx');
The target of the function/method call (foo:string in the example above) will be passed as the first argument to the
function.
Lambdas
Syntax: |arg1, arg2, ... | expression
To access an argument, you must specify its type, just like when accessing scope variables.
Example
|item| item:int > 5