Symfony中自动设置实体createdBy/modifiedBy属性的最佳实践
最佳实践:用Doctrine事件订阅者自动设置createdBy/modifiedBy
不要在实体中注入Security服务——实体应该是纯领域对象,不依赖框架服务,否则会破坏实体的独立性,增加测试复杂度。更优雅的实现是用Doctrine事件订阅者,将用户赋值逻辑从实体中剥离出来。
步骤1:定义通用接口(实现逻辑复用)
先创建一个接口,标记需要自动设置创建/修改用户的实体:
// src/Contract/BlameableInterface.php namespace App\Contract; use App\Entity\User; interface BlameableInterface { public function setCreatedBy(?User $user): void; public function setModifiedBy(?User $user): void; }
步骤2:实体实现接口
让需要自动赋值的实体实现这个接口,只保留基础属性和setter方法:
// src/Entity/Post.php namespace App\Entity; use App\Contract\BlameableInterface; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Post implements BlameableInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: false)] private ?User $createdBy = null; #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: false)] private ?User $modifiedBy = null; // 其他业务属性与getter方法... public function setCreatedBy(?User $user): void { $this->createdBy = $user; } public function setModifiedBy(?User $user): void { $this->modifiedBy = $user; } }
步骤3:创建Doctrine事件订阅者
编写一个事件订阅者,注入Security服务,在prePersist/preUpdate事件中为实体赋值:
// src/EventListener/BlameableSubscriber.php namespace App\EventListener; use App\Contract\BlameableInterface; use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; use Doctrine\ORM\Events; use Doctrine\Persistence\Event\LifecycleEventArgs; use Symfony\Component\Security\Core\Security; class BlameableSubscriber implements EventSubscriberInterface { public function __construct(private Security $security) {} // 声明要监听的Doctrine事件 public function getSubscribedEvents(): array { return [ Events::prePersist, Events::preUpdate, ]; } // 实体首次持久化时,同时设置createdBy和modifiedBy public function prePersist(LifecycleEventArgs $args): void { $entity = $args->getObject(); if (!$entity instanceof BlameableInterface) { return; } $currentUser = $this->security->getUser(); if ($currentUser) { $entity->setCreatedBy($currentUser); $entity->setModifiedBy($currentUser); } } // 实体更新时,仅更新modifiedBy public function preUpdate(LifecycleEventArgs $args): void { $entity = $args->getObject(); if (!$entity instanceof BlameableInterface) { return; } $currentUser = $this->security->getUser(); if ($currentUser) { $entity->setModifiedBy($currentUser); } } }
步骤4:注册订阅者
Symfony会自动识别实现EventSubscriberInterface的类并注册为服务,无需额外配置。若需手动配置,可在config/services.yaml中添加:
services: App\EventListener\BlameableSubscriber: tags: - { name: doctrine.event_subscriber }
方案优势
- 实体纯净:实体仅负责业务逻辑,不依赖框架服务,符合领域驱动设计原则,测试和序列化更简单。
- 逻辑复用:所有需要自动赋值的实体只需实现接口,无需重复编写生命周期回调代码。
- 集中维护:用户赋值逻辑统一在订阅者中,修改时只需改动一处,降低维护成本。
可选快捷方案
如果不想手动实现,可使用StofDoctrineExtensionsBundle的Blameable行为,它已封装好整套逻辑,仅需简单配置即可使用,但手动实现订阅者会更轻量、可控。
内容的提问来源于stack exchange,提问作者Corepex
相关产品推荐
相关产品推荐

