Laravel应用DDD:Repository模式下将持久化对象转为非Laravel实体
在Laravel中实现DDD实体与stdClass的转换方案
在Laravel落地DDD时,领域实体不继承Eloquent Model(解耦基础设施层)的场景下,将Repository查询返回的stdClass转换为领域实体,推荐以下两种规范且可维护的方案:
1. 实体工厂模式(DDD推荐方案)
把实体的创建逻辑封装到实体工厂中,让工厂负责处理从stdClass/数组到实体的转换,同时保证值对象的验证逻辑被正确触发。
代码示例:
// 领域层 - User实体(包含值对象) class UserEntity { public function __construct( public readonly UserId $id, public readonly Username $username, public readonly Email $email ) {} } // 领域层 - User实体工厂 class UserEntityFactory { // 从stdClass创建实体 public static function createFromStdClass(\stdClass $data): UserEntity { return new UserEntity( UserId::fromString($data->id), Username::fromString($data->username), Email::fromString($data->email) ); } // 兼容数组格式(可选,适配不同数据源) public static function createFromArray(array $data): UserEntity { return new UserEntity( UserId::fromString($data['id']), Username::fromString($data['username']), Email::fromString($data['email']) ); } } // 基础设施层 - Repository实现 class EloquentUserRepository implements UserRepositoryInterface { public function findById(string $id): ?UserEntity { $userData = DB::table('users')->where('id', $id)->first(); if (!$userData) { return null; } return UserEntityFactory::createFromStdClass($userData); } }
优势:
- 符合DDD规范:实体的创建逻辑集中在工厂,Repository只负责数据查询,职责单一
- 保证值对象的业务验证:比如
Email::fromString()可以内部做格式校验,避免非法数据进入实体 - 扩展性强:后续新增实体属性或值对象,只需修改工厂逻辑,无需改动Repository
2. 实体映射器模式
如果转换逻辑复杂(比如涉及多关联实体转换),可以用映射器单独封装转换逻辑,进一步解耦实体创建与数据映射。
代码示例:
// 领域层 - User实体映射器 class UserEntityMapper { public function map(\stdClass $data): UserEntity { return new UserEntity( $this->mapUserId($data->id), $this->mapUsername($data->username), $this->mapEmail($data->email) ); } // 拆分单个值对象的映射逻辑(复杂场景下更清晰) private function mapUserId(string $id): UserId { return UserId::fromString($id); } private function mapUsername(string $username): Username { return Username::fromString($username); } private function mapEmail(string $email): Email { return Email::fromString($email); } } // 基础设施层 - Repository实现(注入映射器) class EloquentUserRepository implements UserRepositoryInterface { public function __construct(private UserEntityMapper $mapper) {} public function findById(string $id): ?UserEntity { $userData = DB::table('users')->where('id', $id)->first(); if (!$userData) { return null; } return $this->mapper->map($userData); } }
优势:
- 单一职责:映射器只处理数据到实体的转换,逻辑更清晰
- 可测试性:单独测试映射器的转换逻辑,无需依赖数据库查询
- 复杂场景适配:当需要转换关联实体(比如用户关联订单)时,映射器可以集中处理关联数据的转换
不推荐的方案
- 反射自动转换:虽然能减少代码,但会隐藏实体的依赖关系,且无法触发值对象的业务验证,不符合DDD显式创建实体的原则
- Repository内硬编码转换:会导致Repository职责混乱,代码重复,难以维护
内容的提问来源于stack exchange,提问作者Juan José Ruiz Muñoz
相关产品推荐
相关产品推荐

