symfony/expression-language
Symfony ExpressionLanguage provides an engine to compile and evaluate one-line expressions that return values (often booleans). Use it to embed simple, safe business rules and conditions in your app, with support for custom functions and variables.
Installation:
composer require symfony/expression-language
For Laravel, add to composer.json under require or use laravel/symfony-expression-language if available.
Basic Usage:
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$expressionLanguage = new ExpressionLanguage();
$result = $expressionLanguage->evaluate(
'user.role === "admin" || user.hasPermission("edit")',
['user' => $user]
);
First Use Case:
Replace a simple if condition in a Laravel policy:
// Before: App/Policies/UserPolicy.php
public function update(User $user, User $model)
{
return $user->isAdmin() || $user->id === $model->id;
}
// After:
$expressionLanguage = app(ExpressionLanguage::class);
$canUpdate = $expressionLanguage->evaluate(
'user.isAdmin() || user.id === model.id',
['user' => $user, 'model' => $model]
);
config/app.php:
'providers' => [
// ...
Symfony\Component\ExpressionLanguage\ExpressionLanguage::class,
],
Or use a package like laravel/symfony-expression-language for pre-configured bindings.Pattern: Replace verbose if chains in policies with expressions stored in a database.
// Database: policies table
// id | model | expression
// 1 | User | 'user.id === model.id || user.role === "admin"'
// Policy.php
public function update(User $user, User $model)
{
$expression = Policy::where('model', 'User')->first()->expression;
return app(ExpressionLanguage::class)->evaluate($expression, [
'user' => $user,
'model' => $model,
]);
}
Pattern: Route users based on expressions (e.g., feature flags, A/B tests).
// Kernel.php
protected function routeMiddleware()
{
return [
// ...
\App\Http\Middleware\DynamicRoute::class,
];
}
// DynamicRoute.php
public function handle($request, Closure $next)
{
$expression = config('features.user_dashboard');
if (app(ExpressionLanguage::class)->evaluate($expression, [
'user' => $request->user(),
'request' => $request,
])) {
return redirect('/dashboard');
}
return $next($request);
}
Pattern: Store validation rules in a validation_rules table and apply them dynamically.
// ValidationRule.php (Model)
public function validate($data)
{
$expression = $this->expression;
return app(ExpressionLanguage::class)->evaluate($expression, [
'data' => $data,
'rule' => $this,
]);
}
// Example rule: 'data.email.endsWith("@company.com") || data.phone.startsWith("+1")'
Pattern: Enable/disable features per user segment.
// config/features.php
'new_ui' => 'user.region === "EU" || user.isBetaTester()',
// FeatureService.php
public function isEnabled(string $feature, User $user)
{
return app(ExpressionLanguage::class)->evaluate(
config("features.$feature"),
['user' => $user]
);
}
Service Binding:
Bind the ExpressionLanguage instance in a service provider:
public function register()
{
$this->app->singleton(ExpressionLanguage::class, function ($app) {
$el = new ExpressionLanguage();
$el->addProvider(new UserProvider()); // Custom provider for user methods
return $el;
});
}
Variable Providers:
Extend ProviderInterface to expose custom objects/methods:
use Symfony\Component\ExpressionLanguage\Provider\ProviderInterface;
class UserProvider implements ProviderInterface
{
public function getParameters()
{
return ['user'];
}
public function getMethods()
{
return ['isAdmin', 'hasPermission'];
}
}
Caching Compiled Expressions: Cache compiled expressions for performance-critical paths (e.g., policies):
$cacheKey = 'expr:' . md5($expression);
$compiled = Cache::remember($cacheKey, 60, function () use ($expression) {
return app(ExpressionLanguage::class)->compile($expression);
});
ExpressionLanguage in controllers/services:
$this->expressionLanguage->evaluate(
'product.price > 100 && product.stock > 0',
['product' => $product]
);
ExpressionLanguage in tests:
$expressionLanguage = $this->createMock(ExpressionLanguage::class);
$expressionLanguage->method('evaluate')->willReturn(true);
$this->app->instance(ExpressionLanguage::class, $expressionLanguage);
Variable Whitelisting:
// Fails: 'user' is not defined
$el->evaluate('user.isAdmin()'); // Throws Error\UndefinedVariableException
$el->evaluate('user.isAdmin()', ['user' => $user]);
Null Safety:
?.) may not work as expected with older Symfony versions.
// May fail in Symfony <7.3
$el->evaluate('user?.profile.name');
$el->evaluate('user !== null && user.profile !== null && user.profile.name');
Thread Safety:
// Risky in Symfony <7.3
$compiled = $el->compile('1 + 1'); // May cause issues in concurrent environments
PHP Version Requirements:
Complex Expressions:
// Hard to debug
$el->evaluate('(user.group === "admin" && (order.amount > 1000 || order.isUrgent())) ? true : false');
// Allowed in Symfony 7.1+
$el->evaluate('# Admin can override limits
user.group === "admin" ||
# Regular users with large/urgent orders
(order.amount > 1000 || order.isUrgent())');
Enable Debug Mode:
$el = new ExpressionLanguage();
$el->setDebug(true); // Shows compilation errors
Log Compiled Expressions:
$compiled = $el->compile('user.isAdmin()');
\Log::debug('Compiled expression:', ['code' => $compiled]);
Use repr() for Variable Inspection:
$repr = $el->repr($user); // Converts object to a string representation
\Log::debug('User object:', ['repr' => $repr]);
Test with Simple Expressions First: Start with basic expressions (e.g., `'1 + 1
How can I help you explore Laravel packages today?