symfony/expression-language
Symfony ExpressionLanguage provides a fast engine to evaluate and compile simple one-line expressions (often boolean rules). Use it to embed dynamic conditions, calculations, and access to variables/functions in a safe, extensible way.
Install the Package:
composer require symfony/expression-language
For Laravel, no additional configuration is needed—it’s a standalone component.
Basic Evaluation:
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$expressionLanguage = new ExpressionLanguage();
$result = $expressionLanguage->evaluate(
"'user' instanceOf App\Models\User && user.isActive()",
['user' => $user]
);
First Use Case:
Replace a hardcoded policy check in UserPolicy:
// Before: Static logic in code
public function update(User $user, User $model)
{
return $user->isAdmin() && $model->isActive();
}
// After: Dynamic expression (stored in DB/cache)
$expression = 'user.isAdmin() && model.isActive()';
$allowed = $expressionLanguage->evaluate($expression, [
'user' => $user,
'model' => $model,
]);
policies table (e.g., user_can_update column).// Database record: { "expression": "'user' instanceOf App\Models\User && user.canEdit(model)" }
$policyExpression = Policy::find($id)->expression;
$canUpdate = $expressionLanguage->evaluate($policyExpression, [
'user' => auth()->user(),
'model' => $model,
]);
$expressionLanguage = app(ExpressionLanguage::class);
$compiled = $expressionLanguage->compile(
"'user' instanceOf App\Models\User && user.hasPermission('edit_post')"
);
// Reuse compiled expression in middleware
$compiled->evaluate([
'user' => auth()->user(),
'post' => $post,
]);
validation_rules table.$rule = ValidationRule::where('field', 'email')->first();
$isValid = $expressionLanguage->evaluate($rule->expression, [
'input' => $request->input('email'),
]);
$flag = FeatureFlag::where('name', 'new_ui')->first();
$isEnabled = $expressionLanguage->evaluate($flag->expression, [
'user' => auth()->user(),
'request' => request(),
]);
Service Provider Binding:
$this->app->singleton(ExpressionLanguage::class, function ($app) {
$language = new ExpressionLanguage();
// Register custom functions (see below)
return $language;
});
Policy Integration:
Extend Authorizable to use expressions:
public function authorize($ability, $model = null)
{
$expression = PolicyExpression::for($ability, $model)->get();
return $this->expressionLanguage->evaluate($expression, [
'user' => $this,
'model' => $model,
]);
}
Form Request Validation:
public function rules()
{
$customRules = CustomValidationRule::all()->pluck('expression');
return array_merge(
['email' => 'required'],
$customRules->mapWithKeys(fn ($expr) => ['custom' => $expr])
);
}
Compile Once, Reuse: Cache compiled expressions for high-frequency use (e.g., middleware, policies):
$compiled = $expressionLanguage->compile($expression);
cache()->remember("compiled_{$expression}", now()->addHours(1), fn() => $compiled);
Variable Whitelisting: Restrict variables to trusted objects only:
$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->addProvider(new UserProvider()); // Custom provider for 'user' variable
Custom Functions:
$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->addFunction('is_vip', function ($user) {
return $user->subscription === 'vip';
});
// Usage: `is_vip(user)`
Custom Operators:
$expressionLanguage->addOperator(new InstanceOfOperator());
// Usage: `'user' instanceOf App\Models\User`
Variable Scope Leaks:
$_SERVER, app()).$expressionLanguage->evaluate($expression, ['user' => $user], ['user']);
Null References:
null variables can cause errors (e.g., user->method()).?.) or coalesce:
// Expression: `user?.isActive()`
// Or: `user is null ? false : user.isActive()`
Compilation Cache Invalidation:
cache()->forget("compiled_{$expression}_v{$version}");
Complex Expressions and Readability:
// Expression: `is_premium(user) && (is_weekend(date) || is_holiday(date))`
Thread Safety:
Enable Debug Mode:
$expressionLanguage->setDebug(true); // Logs parsed expressions
Inspect Parsed AST:
$parser = new Parser();
$node = $parser->parse($expression);
dump($node); // Debug structure
Test Edge Cases:
"" or " ".user..method() (double dot).a && b || c && d (operator precedence).Provider Order Matters:
$expressionLanguage->addProvider(new UserProvider());
$expressionLanguage->addProvider(new RequestProvider());
Reserved Keywords:
new, instanceof) as variable names.'new'.PHP Version Compatibility:
match expressions require PHP 8.0+.Custom Providers:
class UserProvider implements ProviderInterface {
public function getVariables(array $variables, array $names) {
return ['user' => auth()->user()];
}
}
Node Visitors:
$visitor = new class implements NodeVisitorInterface {
public function enterNode(Node $node) { /* ... */ }
public function leaveNode(Node $node) { /* ... */ }
};
$expressionLanguage->addNodeVisitor($visitor);
Error Handling:
$expressionLanguage->setErrorHandler(function (ParseError $error) {
return "Invalid expression: {$error->getMessage()} (at line {$error->getLine()})";
});
Compilation Hooks:
$expressionLanguage->addCompilerPass(new class implements CompilerPassInterface {
public function onCompile(Node $node, Compiler $compiler) {
$compiler->addDebugInfo("Custom debug info");
}
});
How can I help you explore Laravel packages today?