如何在 Shopware 5.4.* 中监听订单变更事件?
结论
Shopware 5.4.* 没有统一的“订单内容已变更”业务事件,无法同时覆盖订单项、收货地址、账单地址和支付方式的修改。
如果这些修改通过 Shopware 的 Doctrine ORM 完成,建议监听 Doctrine 的 onFlush 和 postFlush 生命周期事件,再检查订单相关实体的变更集。订单状态和支付状态事件只反映对应状态的变化,无法覆盖订单明细和地址实体的增删改。
推荐方案:监听 Doctrine UnitOfWork
Shopware 5.4 的订单数据分布在多个实体中:
- 订单主体:
Shopware\Models\Order\Order - 订单项:
Shopware\Models\Order\Detail - 账单地址:
Shopware\Models\Order\Billing - 收货地址:
Shopware\Models\Order\Shipping
添加、删除或修改订单项,对应的是 Detail 实体的插入、删除或更新。地址变更也是如此。支付方式的修改通常体现为 Order 实体上的支付关联或支付 ID 发生变化。
可以编写一个 Doctrine subscriber:
<?php
namespace MyPlugin\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use Shopware\Models\Order\Billing;
use Shopware\Models\Order\Detail;
use Shopware\Models\Order\Order;
use Shopware\Models\Order\Shipping;
class OrderChangeSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return [
Events::onFlush,
];
}
public function onFlush(OnFlushEventArgs $args)
{
$entityManager = $args->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Detail) {
$this->handleChange('order_item_added', $entity, []);
}
}
foreach ($unitOfWork->getScheduledEntityUpdates() as $entity) {
$changeSet = $unitOfWork->getEntityChangeSet($entity);
if ($entity instanceof Detail) {
$this->handleChange('order_item_changed', $entity, $changeSet);
continue;
}
if ($entity instanceof Billing) {
$this->handleChange('billing_address_changed', $entity, $changeSet);
continue;
}
if ($entity instanceof Shipping) {
$this->handleChange('shipping_address_changed', $entity, $changeSet);
continue;
}
if ($entity instanceof Order) {
$this->handleOrderChange($entity, $changeSet);
}
}
foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof Detail) {
$this->handleChange('order_item_removed', $entity, []);
}
}
}
private function handleOrderChange(Order $order, array $changeSet)
{
/*
* 实际字段名应以当前 Shopware 5.4 补丁版本中的
* Order 实体映射为准。
*/
$paymentFields = [
'payment',
'paymentId',
];
foreach ($paymentFields as $field) {
if (array_key_exists($field, $changeSet)) {
$this->handleChange(
'payment_method_changed',
$order,
[$field => $changeSet[$field]]
);
}
}
}
private function handleChange($type, $entity, array $changeSet)
{
// 在这里记录变更,或先放入待处理队列。
// 不建议直接在 onFlush 中再次执行 EntityManager::flush()。
}
}
还需要在插件启动阶段,把订阅器注册到 Shopware 使用的 Doctrine EventManager:
$subscriber = new \MyPlugin\Subscriber\OrderChangeSubscriber();
Shopware()
->Models()
->getEventManager()
->addEventSubscriber($subscriber);
注册代码的具体位置取决于插件采用旧式 Bootstrap 结构,还是 Shopware 5.4 支持的新插件结构。无论采用哪种结构,都要确保每个请求只注册一次,否则同一次变更可能被重复处理。
如何读取变更内容
更新实体时,getEntityChangeSet() 通常会返回以下格式的数据:
[
'firstname' => ['Old name', 'New name'],
'city' => ['Berlin', 'Hamburg'],
]
每个字段都包含旧值和新值,可以据此判断地址中的哪些内容发生了变化。
替换订单项通常不会触发单独的“replace”事件,而会表现为以下两种情况之一:
- 删除原有
Detail,同时插入新的Detail - 更新原有
Detail的商品编号、数量、价格等字段
因此,insertion、update 和 deletion 都要处理,不能只监听 update。
后续处理最好放到 postFlush
onFlush 适合读取完整的 Doctrine 变更计划,但不宜直接执行复杂的业务操作,尤其不能在其中再次调用 flush(),否则可能导致递归刷新或 UnitOfWork 状态异常。
可以按以下方式处理:
- 在
onFlush中收集变更。 - 在
postFlush中写入审计日志、投递消息或调用后续服务。 - 为外部系统同步失败设置重试机制。
如果还要保存新的 Doctrine 实体,需要谨慎处理 persist()、变更集计算和事务边界。简单场景可以优先把任务投递到独立队列,或者写入不经过 ORM 的日志。
是否可以监听后台控制器事件
也可以订阅类似下面的后台控制器事件:
Enlight_Controller_Action_PostDispatchSecure_Backend_Order
再根据 action 和请求参数判断后台执行的操作。不过,这种方式只能覆盖通过 Shopware 后台订单控制器完成的修改,无法可靠捕获:
- 自定义插件直接修改订单实体
- API 写入
- 定时任务修改
- 其他服务通过 Doctrine ORM 修改
后台 action 名称和参数属于具体实现细节,升级时也更容易变化。因此,控制器事件适合扩展后台界面,不适合作为完整的订单变更审计入口。
注意事项
- Doctrine 监听只能捕获当前 EntityManager 管理的实体变更。直接执行 SQL、DBAL 更新或批量 DQL 时,可能不会触发普通的实体生命周期事件。
- 支付方式在 change set 中对应的字段名要根据实际实体映射确认,不能只依赖示例中的
payment或paymentId。 - 删除订单项时,要在实体仍然可读时保存订单号、商品编号和明细 ID 等所需数据,以免之后访问已经删除的实体或尚未初始化的关联。
- 如果现有逻辑已经监听订单状态和支付状态事件,需要做好去重,因为 Doctrine subscriber 也可能捕获这些字段的变化。
- Shopware 5.4 已经是较旧的版本,不同补丁版本和第三方插件可能会改变订单保存流程。上线前应分别测试后台编辑、API 修改、插件修改和订单重新保存等入口。
备注:内容仅供参考。
