symfony/expression-language
Symfony's ExpressionLanguage component compiles and evaluates one-line expressions that return a value, usually a boolean. It's ideal for dynamic rules in configuration, security, and workflows, offering a safe parser and evaluator with no eval required.
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).rule_id, user_id).['user', 'request']).SyntaxError/parse exceptions at evaluation time. Callers must handle these explicitly, or rules fail open/closed unpredictably.v5.4.45...v5.4.54. If your lockfile sits at 5.4.45, the upgrade absorbs nine patch releases at once — review the intermediate entries, not just the headline security fix.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::evaluate('user.isAdmin()') for concise call sites.public function handle(Request $request, ExpressionLanguage $language) {
return $language->evaluate(
'user.role === "admin" || user.hasPermission("edit_post")',
['user' => $request->user()]
);
}
rules table (pricing_rules, feature_flags) keyed by entity, and hydrate on demand:
$rule = Rule::where('entity_type', 'order')->where('entity_id', $order->id)->first();
$value = $language->evaluate($rule->expression, ['order' => $order, 'user' => $order->user]);
retrieved, saved).if chains; keep the PHP branch behind a flag during shadow mode.Security Risks:
addProvider(). Never expose arbitrary object graphs.symfony/expression-language to >=5.4.54 before enabling any database-backed rule authoring.[SECURITY] but carries no CVE identifier in the changelog text. CVE-driven alerting alone may miss this class of fix; watch the upstream release feed directly.^[a-zA-Z0-9_\.\+\-\*\/\%\&\|\!\=\<\>\(\)\?\:\s]+$) before evaluation.user_id, rule_id, expression hash) for audit.Correctness Risks (new):
Performance Risks:
Compatibility Risks:
composer show symfony/expression-language and composer why symfony/expression-language. A transitive pull-in of a stale 5.4.x is the most likely way this lands unnoticed.ExpressionLanguage, Form Requests with expression-derived rules, and Nova extensions driven by expressions.>=5.4.54, and which dependency pulls it in? Does the 6.4/7.x line we plan to move to carry the equivalent fixes?ExpressionLanguage be mocked in unit tests?Expression facade or helper trait; injection into controllers, policies, middleware, jobs.ExpressionLanguage with Symfony’s Validator for dynamic rules.Cache::remember('expression:rule:'.$id, ...)) backed by Redis in distributed environments. Key on rule ID with an expression hash for invalidation — keying on rule ID alone is a common defect that serves stale logic after an edit.>=5.4.54 as a standalone, low-blast-radius change before any feature work. Resolve transitive pins that may block it.// After
public function update(User $user, Post $post, ExpressionLanguage $language) {
return $language->evaluate('user.isAdmin() || user.id === post.user_id', compact('user', 'post'));
}
composer.lock for transitive dependents (validator, workflow, Laravel packages) pinning older ranges.AppServiceProvider.expression-language security releases as same-week patching.| Mode | Trigger | Symptom | Mitigation |
|---|---|---|---|
| Parse exception | Malformed expression | Rule silently unevaluated | Explicit try/catch, fail-closed for auth / fail-open for cosmetic, alert on rate |
| Depth-cap rejection (new) | Expression exceeds nesting bound (#65690) or node-tree depth (#66723) post-upgrade | Rule that previously "worked" now throws | Corpus audit pre-upgrade; publish authoring depth guidance |
| Silent logic drift | Chained-ternary associativity fixed (#66720) | Rule output changes with no error | Golden-file comparison; shadow mode in staging |
| Multi-patch jump | Lockfile at 5.4.45, jumping to 5.4.54 | Accumulated behaviour changes attributed to a single fix | Review all intermediate changelog entries; stage the bump |
| Cache staleness | Compiled expression cached without hash invalidation | Users see pre-edit behaviour | Hash-keyed invalidation on save |
| Context mismatch | Variable renamed in code, not in rule | Missing-variable error at runtime | Contract test between rule context and code |
| Performance cliff | Unbounded evaluations per request | p95 latency regression | Per-request evaluation budget; cache warmup |
How can I help you explore Laravel packages today?