本文目录导读:

在 Symfony 项目中,表达式(Expression) 和 策略(Strategy) 是两个强大的概念,常用于实现动态访问控制、业务规则或配置决策,理解它们的区别、应用场景以及如何结合使用,是 Symfony 高级开发的关键。
核心概念对比
| 特性 | Expression(表达式) | Strategy(策略) |
|---|---|---|
| 本质 | 轻量级、动态的逻辑片段(DSL) | 面向对象、可复用的算法/行为封装 |
| 执行方式 | 由 Symfony ExpressionLanguage 引擎解释执行 | 通过 PHP 类/对象方法调用 |
| 灵活性 | 极高(可运行时构建、传递字符串) | 中等(需预编码类,但可通过注入参数调整) |
| 性能 | 中等(解析+执行开销) | 高(直接 PHP 执行) |
| 可读性 | 字符串形式,简洁但弱类型 | 类方法,强类型、IDE 友好、可单元测试 |
| 典型用途 | 访问控制(@Security)、验证规则、动态配置 |
复杂业务算法、多重处理逻辑、可互换的行为 |
| Symfony 支持 | ExpressionLanguage 组件 + 安全认证集成 | 无内置组件,但可结合依赖注入 + 设计模式实现 |
Expression(表达式)实战
1 访问控制中的表达式
// src/Controller/ArticleController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\ExpressionLanguage\Expression;
class ArticleController extends AbstractController
{
#[Route('/article/{id}/edit')]
#[IsGranted(
new Expression(
'is_granted("ROLE_ADMIN") ' .
'or (user and user === subject.getAuthor())'
),
subject: 'article'
)]
public function edit(Article $article): Response
{
// 只有管理员或文章作者可以编辑
}
}
2 自定义表达式函数
// src/Expression/UserAgeExpressionProvider.php
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
class UserAgeExpressionProvider implements ExpressionFunctionProviderInterface
{
public function getFunctions(): array
{
return [
new ExpressionFunction('user_age',
fn(string $user) => sprintf('(%s)->getAge()', $user),
fn(array $variables, $user) => $user->getAge()
),
];
}
}
3 模板中的表达式
{% if is_granted(expression('is_authenticated() and user.hasRole("EDITOR")')) %}
<a href="{{ path('article_edit', {id: article.id}) }}">编辑</a>
{% endif %}
Strategy(策略模式)实战
1 基础价格策略接口
// src/Pricing/Strategy/PricingStrategyInterface.php
namespace App\Pricing\Strategy;
use App\Entity\Order;
interface PricingStrategyInterface
{
public function calculate(Order $order): float;
public function supports(Order $order): bool;
}
2 具体策略实现
// src/Pricing/Strategy/StandardPricing.php
namespace App\Pricing\Strategy;
class StandardPricing implements PricingStrategyInterface
{
public function calculate(Order $order): float
{
return array_sum(array_map(
fn($item) => $item->getProduct()->getPrice() * $item->getQuantity(),
$order->getItems()->toArray()
));
}
public function supports(Order $order): bool
{
return $order->getTotalItems() < 100;
}
}
// src/Pricing/Strategy/BulkDiscountPricing.php
class BulkDiscountPricing implements PricingStrategyInterface
{
public function __construct(private float $discountPercent = 0.15) {}
public function calculate(Order $order): float
{
$subtotal = (new StandardPricing())->calculate($order);
return $subtotal * (1 - $this->discountPercent);
}
public function supports(Order $order): bool
{
return $order->getTotalItems() >= 100;
}
}
3 策略上下文与服务集成
// src/Pricing/Strategy/PricingContext.php
namespace App\Pricing\Strategy;
class PricingContext
{
/** @var PricingStrategyInterface[] */
private iterable $strategies;
public function __construct(iterable $strategies)
{
$this->strategies = $strategies;
}
public function calculate(Order $order): float
{
foreach ($this->strategies as $strategy) {
if ($strategy->supports($order)) {
return $strategy->calculate($order);
}
}
throw new \RuntimeException('No suitable pricing strategy found');
}
}
4 服务标签与自动装配
# config/services.yaml
services:
App\Pricing\Strategy\:
resource: '../src/Pricing/Strategy/'
tags: ['app.pricing.strategy']
App\Pricing\Strategy\PricingContext:
arguments:
$strategies: !tagged_iterator app.pricing.strategy
混合使用:Expression 驱动策略选择
在某些高级场景中,可以用 Expression 动态评估策略选择条件:
// src/Config/DynamicStrategyResolver.php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use App\Pricing\Strategy\PricingStrategyInterface;
class DynamicStrategyResolver
{
public function __construct(
private ExpressionLanguage $expressionLanguage,
private array $strategyMap // ['expr' => StrategyServiceId]
) {}
public function resolve(Order $order): PricingStrategyInterface
{
foreach ($this->strategyMap as $expression => $serviceId) {
if ($this->expressionLanguage->evaluate($expression, [
'order' => $order,
'total' => $order->getTotalItems(),
'user' => $order->getUser(),
])) {
return $this->container->get($serviceId);
}
}
return $this->container->get('default_strategy');
}
}
最佳实践选择指南
选用 Expression 当:
- 动态规则:规则经常变化(如 A/B 测试条件)
- 非技术用户:需要给管理员提供可编辑的规则字符串
- 简单逻辑:
&&、、in等布尔组合 - 快速原型:不想为简单条件创建独立类
选用 Strategy 当:
- 复杂算法:超过 5-10 行逻辑或需要生命周期
- 可测试性:需要独立单元测试每个分支
- 多重实现:有多种互斥的处理方式(如支付网关、通知渠道)
- 依赖注入:需要注入服务、仓库或其他依赖
- 性能关键:表达式引擎的解析开销不可忽略
实际案例:文档工作流
// Expression 用于简单条件
class DocumentApprovalController extends AbstractController
{
#[Route('/document/{id}/approve')]
#[IsGranted(new Expression(
'(is_granted("ROLE_MANAGER") and document.getAmount() < 10000) ' .
'or (is_granted("ROLE_DIRECTOR") and document.getAmount() < 50000) ' .
'or is_granted("ROLE_CEO")'
), subject: 'document')]
public function approve(Document $document): Response
{
return $this->render('document/approved.html.twig');
}
}
// Strategy 用于审批流程中的发送通知
class NotificationStrategyContext
{
public function __construct(
private EmailNotificationStrategy $email,
private SlackNotificationStrategy $slack,
private SmsNotificationStrategy $sms,
private ExpressionLanguage $expr
) {}
public function notify(NotificationEvent $event): void
{
// 根据表达式选择通知方式,但策略处理具体实现
if ($this->expr->evaluate('event.urgency > 0.9', ['event' => $event])) {
$this->sms->send($event); // 高紧急:短信+Slack
$this->slack->send($event);
} elseif ($event->getUser()->getPreferences()['notify_email']) {
$this->email->send($event);
}
}
}
| 用法对比 | Expression | Strategy |
|---|---|---|
| 声明位置 | 注解/属性/YAML 配置文件 | 服务定义 / 工厂类 |
| 调试 | 困难(eval-like 字符串) | 容易(断点、日志、单元测试) |
| 扩展性 | 通过 ExpressionFunctionProvider 扩展 | 通过接口 + 依赖注入容器扩展 |
| 安全性 | 字符串注入风险(需沙箱) | 类型安全,无字符串执行风险 |
| 适用场景 | 授权逻辑、表单验证约束、动态路由处理器 | 支付处理、通知策略、计算引擎、格式转换 |
综合建议:
- 优先使用 Strategy 处理核心业务逻辑,保持代码可维护性
- 在需要 动态性(如 admin 面板配置规则)或 简单布尔逻辑(如权限控制)时,使用 Expression
- 两者可以完美结合:Expression 做决策判断 → Strategy 执行具体实现