更新现有实体:能否像Doctrine一样直接持久化经DAL修改的Category实体?
核心结论
Shopware 的数据抽象层(DAL)做不到和Doctrine ORM完全一致的、实体修改后自动flush持久化的效果,两者从设计底层就存在本质差异。
为什么无法实现和Doctrine一致的自动持久化
DAL是纯无状态的仓储实现,你从search接口拿到的Category实体,本质就是个存储了数据的普通PHP对象,从返回结果的那一刻起就和DAL内部的写逻辑脱钩了:
- 你调用它的
setParentId()之类的修改方法,改的只是内存里的对象属性,DAL根本不知道你做了哪些变更,也不会主动帮你把修改内容同步到数据库 - 它没有Doctrine那种贯穿整个请求生命周期的UnitOfWork托管机制,不存在“实体变更加入队列、请求结束统一flush”的逻辑,只要你不主动调用仓储的写方法,数据库不会产生任何变更
贴近Doctrine使用体验的实现方案(无需全量转换实体为数组)
如果不想手动把整个实体转成大数组拼入upsert参数,有两种符合DAL设计规范、且接近Doctrine操作体验的实现方式:
单/少字段修改场景
如果仅需要修改1-2个属性,连实体都不需要预先加载,直接调用仓储update方法传入目标字段即可,性能最优,和修改实体后持久化的效果完全一致:
$this->categoryRepository->update([ [ 'id' => $randomId, 'parentId' => 'new-parent-id' ] ], Context::createDefaultContext());
多属性零散修改场景
如果已经按照习惯对实体做了多处setXxx()操作,不需要手动逐字段映射回数组,可以直接利用Entity基类内置的变更记录能力,仅提取实际修改过的字段传给写方法:
$randomId = 'an-existing-category-id'; $result = $this->categoryRepository->search( (new Criteria())->addFilter(new EqualsAnyFilter('id', [$randomId])), Context::createDefaultContext() ); /** @var Category $entity */ $entity = $result->get($randomId); // 和现有代码逻辑完全一致,正常修改实体属性即可 $entity->setParentId('new-parent-id'); $entity->setName('更新后的分类名称'); $entity->setVisibleInMenu(true); // 提取实体内部记录的所有已变更字段,避免全量序列化实体 $changedValues = $entity->getChanged(); if (empty($changedValues)) { return; } // 仅将包含变更的单条数据传给update方法完成持久化 $this->categoryRepository->update([ array_merge(['id' => $entity->getId()], $changedValues) ], Context::createDefaultContext());
这里用到的
getChanged()是所有Shopware实体基类的内置方法:实体被实例化时会存储所有字段的原始值,每次调用setter修改属性时,它会自动把新值存入变更集合,最终提取的内容只会包含实际修改过的字段,不会携带未变动的内容,无全量更新的额外性能开销。
需要避开的常见误区
- 别图省事把整个实体序列化后直接传入
upsert/update方法,全量传字段不仅会触发不必要的校验,还可能覆盖实体加载完成后、被其他业务逻辑修改过的数据库字段,引发数据不一致 - 别指望在请求结束时DAL会自动把内存中的实体变更同步到数据库,所有写操作必须显式调用对应的仓储方法才能生效
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

