PHP项目Symfony expression与策略

wen PHP项目 2

本文目录导读:

PHP项目Symfony expression与策略

  1. 核心概念对比
  2. Expression(表达式)实战
  3. Strategy(策略模式)实战
  4. 混合使用:Expression 驱动策略选择
  5. 最佳实践选择指南
  6. 实际案例:文档工作流

在 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 执行具体实现

抱歉,评论功能暂时关闭!