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.
Strengths:
user.hasPermission() or order.isEligibleForDiscount()).Compiler::repr thread-safety) ensure stability in concurrent environments (e.g., Laravel queues or Horizon jobs).ExpressionLanguage service binding).Potential Friction Points:
ExpressionLanguage compiler cache).try-catch blocks with context logging.['user', 'request']).Laravel Compatibility:
AppServiceProvider:
$this->app->singleton(ExpressionLanguage::class, function ($app) {
$language = new ExpressionLanguage();
$language->addProvider(new FunctionProvider([
'user_has_permission' => [$app['auth']->user(), 'hasPermission'],
]));
return $language;
});
Expression facade for concise syntax (e.g., Expression::evaluate('user.isAdmin()')).ExpressionLanguage into controllers, policies, or middleware:
public function handle(Request $request, ExpressionLanguage $expressionLanguage) {
$allowed = $expressionLanguage->evaluate(
'user.role === "admin" || user.hasPermission("edit_post")',
['user' => $request->user()]
);
}
Database-Backed Rules:
rules table (e.g., pricing_rules, feature_flags) and hydrate them dynamically:
$rule = Rule::where('entity_type', 'order')->where('entity_id', $order->id)->first();
$discount = $expressionLanguage->evaluate($rule->expression, [
'order' => $order,
'user' => $order->user,
]);
retrieved, saved).Policy/Middleware Integration:
if chains in App\Policies\UserPolicy with expressions:
public function update(User $user, User $model)
{
return $this->expressionLanguage->evaluate(
'user.id === model.id || user.hasPermission("edit_any_user")',
compact('user', 'model')
);
}
Route::get('/admin', function () {
return $this->expressionLanguage->evaluate(
'user.isAdmin() && request.path.startsWith("/admin")',
['user' => auth()->user(), 'request' => request()]
);
})->middleware(function ($request, $next) {
if (!$this->expressionLanguage->evaluate(
'user.isAdmin() && request.path.startsWith("/admin")',
['user' => $request->user(), 'request' => $request]
)) {
abort(403);
}
return $next($request);
});
Security Risks:
ExpressionLanguage::addProvider() to restrict available functions/objects.^[a-zA-Z0-9_\.\+\-\*\/\%\&\|\!\=\<\>\(\)\?\:\s]+$) before evaluation.ExpressionLanguage::compile()) to avoid runtime parsing.user_id, rule_id) for auditing.$language = new ExpressionLanguage();
$language->addProvider(new FunctionProvider([
'user_has_permission' => [$user, 'hasPermission'],
'order_is_eligible' => [$order, 'isEligibleForDiscount'],
]));
Performance Risks:
Cache::remember() or Symfony’s ExpressionLanguage cache).Compatibility Risks:
authorize() methods work with injected ExpressionLanguage.Rule::expression('input.length > 5')).ExpressionLanguage service in tests?ExpressionLanguage as a singleton with preconfigured providers/functions.Expression facade or ExpressionHelper trait for concise syntax.ExpressionLanguage with Symfony’s Validator for dynamic validation rules.pricing_rules.expression).Cache::remember('expression:user_policy', 60, fn() => $language->compile($expression))).// Before
public function update(User $user, Post $post) {
return $user->isAdmin() || $user->id === $post->user_id;
}
// After
public function update(User $user, Post $post, ExpressionLanguage $language) {
return $language->evaluate(
'
How can I help you explore Laravel packages today?