symfony/expression-language
Symfony ExpressionLanguage provides a fast engine to evaluate or compile short expressions into PHP. Use it to compute values or boolean rules, with custom functions and variables, for dynamic logic in apps and components.
Install via Composer:
composer require symfony/expression-language
First Use Case: Dynamic Feature Flags
Define a Feature Flag Model:
// app/Models/FeatureFlag.php
use Illuminate\Database\Eloquent\Model;
class FeatureFlag extends Model
{
protected $fillable = ['name', 'expression', 'enabled'];
}
Create a Service to Evaluate Flags:
// app/Services/FeatureFlagEvaluator.php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
class FeatureFlagEvaluator
{
public function __construct(
private ExpressionLanguage $expressionLanguage
) {}
public function isEnabled(string $flagName, array $variables): bool
{
$flag = FeatureFlag::where('name', $flagName)->firstOrFail();
return $flag->enabled &&
$this->expressionLanguage->evaluate(
$flag->expression,
$variables
);
}
}
Register the Service in AppServiceProvider:
public function register()
{
$this->app->singleton(FeatureFlagEvaluator::class, function ($app) {
return new FeatureFlagEvaluator(new ExpressionLanguage());
});
}
Use in a Controller:
public function dashboard(Request $request, FeatureFlagEvaluator $evaluator)
{
$variables = [
'user' => $request->user(),
'request' => $request,
'date' => new DateTime(),
];
if ($evaluator->isEnabled('new_ui', $variables)) {
return view('new_dashboard');
}
return view('old_dashboard');
}
Store an Expression in Database:
INSERT INTO feature_flags (name, expression, enabled)
VALUES ('new_ui', 'user.isBetaTester() && date.isWeekend()', true);
Key Files to Reference:
Pattern: Pass variables as an associative array to evaluate().
$expression = 'user.role === "admin" && order.total > 100';
$variables = [
'user' => $user,
'order' => $order,
];
$result = $expressionLanguage->evaluate($expression, $variables);
Laravel Integration: Bind common objects (e.g., Request, User) automatically:
$expressionLanguage->addProvider(new Provider\LaravelProvider());
$result = $expressionLanguage->evaluate('request.path() === "/admin"', [
'request' => $request,
]);
Pattern: Compile once, evaluate many times (e.g., in loops or high-traffic endpoints).
$compiled = $expressionLanguage->compile($expression);
foreach ($orders as $order) {
$result = $compiled->evaluate([
'order' => $order,
'user' => $user,
]);
}
Caching: Cache compiled expressions in Laravel’s cache:
$cacheKey = 'expr:'.$expression;
$compiled = Cache::remember($cacheKey, 60, function () use ($expressionLanguage, $expression) {
return $expressionLanguage->compile($expression);
});
Pattern: Store expressions in a database and fetch them dynamically.
$rule = Rule::where('name', 'discount_eligible')->first();
$isEligible = $expressionLanguage->evaluate($rule->expression, [
'user' => $user,
'cart' => $cart,
]);
Laravel Eloquent Accessor:
// app/Models/Rule.php
public function getEvaluatedAttribute()
{
return app(ExpressionLanguage::class)->evaluate(
$this->expression,
$this->variables
);
}
Pattern: Extend functionality with custom providers (e.g., for Laravel-specific helpers).
// app/Providers/ExpressionLanguageServiceProvider.php
use Symfony\Component\ExpressionLanguage\Provider\ProviderInterface;
class LaravelProvider implements ProviderInterface
{
public function getFunctions()
{
return [
'auth' => [$this, 'authFunction'],
'route' => [$this, 'routeFunction'],
];
}
public function authFunction(array $arguments)
{
return auth()->check();
}
public function routeFunction(array $arguments)
{
return request()->route()->getName();
}
}
Register Provider:
$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->addProvider(new LaravelProvider());
Pattern: Replace if chains in policies with database-driven expressions.
// app/Policies/UserPolicy.php
public function update(User $user, User $model)
{
$expression = 'user.id === model.id || user.isAdmin()';
return app(ExpressionLanguage::class)->evaluate($expression, [
'user' => $user,
'model' => $model,
]);
}
Dynamic Policy Evaluation:
$policyExpression = Policy::where('action', 'update')->where('model', 'User')->first();
$allowed = $expressionLanguage->evaluate($policyExpression->expression, [
'user' => $user,
'model' => $model,
]);
// app/Http/Middleware/DynamicAuth.php
public function handle(Request $request, Closure $next)
{
$expression = 'user.hasPermission("access_dashboard") && !request.isMobile()';
if ($expressionLanguage->evaluate($expression, [
'user' => $request->user(),
'request' => $request,
])) {
return $next($request);
}
abort(403);
}
Service Container Binding:
Bind ExpressionLanguage as a singleton in AppServiceProvider:
$this->app->singleton(ExpressionLanguage::class, function ($app) {
$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->addProvider(new LaravelProvider());
return $expressionLanguage;
});
Artisan Commands for Testing:
// app/Console/Commands/TestExpression.php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
class TestExpression extends Command
{
protected $signature = 'expr:test {expression} {--variables=}';
protected $description = 'Test an expression';
public function handle(ExpressionLanguage $expressionLanguage)
{
$variables = json_decode($this->option('variables'), true) ?: [];
$result = $expressionLanguage->evaluate(
$this->argument('expression'),
$variables
);
$this->info($result);
}
}
Nova/Filament Integration:
// Nova Toolbar
Toolbar::make()
->addField(
new Button('Approve', 'btn-primary')
->canSee(fn () => $expressionLanguage->evaluate(
'user.canApprove() && record.status === "pending"',
['user' => $request->user(), 'record' => $resource]
))
);
Event Listeners:
// app/Listeners/EvaluateWorkflowRule.php
public function handle(OrderApproved $event)
{
$expression = 'order.amount > 1000 && user.isPremium()';
if ($expressionLanguage->evaluate($expression, [
'order' => $event->order,
'user' => $event->order->user,
])) {
// Trigger premium workflow
}
}
Precompile Expressions:
Cache compiled expressions in a compiled_expressions table with TTL.
$compiled = Cache::remember("expr:{$expression}", 3600, function () use ($expressionLanguage, $expression) {
return $expressionLanguage->compile($expression);
});
Batch Evaluation: For bulk operations (e.g., updating records), compile once and reuse:
$compiled = $expressionLanguage->compile('user.id === record.user_id && record.status === "pending"');
foreach ($users as $user) {
How can I help you explore Laravel packages today?