You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 03:16:14