Laravel数据映射到DTO怎么实现

wen PHP项目 1

本文目录导读:

Laravel数据映射到DTO怎么实现

  1. 基础 DTO 类实现
  2. 使用 Spatie Data Transfer Object 包
  3. 使用 Laravel Eloquent API Resources
  4. 使用 AutoMapper 模式
  5. 高级实现 - 通用 DTO 映射器
  6. 使用 PHP 8 Attributes 自动映射
  7. 使用 Factory 模式
  8. 推荐方案

在 Laravel 中实现数据映射到 DTO(Data Transfer Object)有多种方式,我来介绍几种常用的实现方法:

基础 DTO 类实现

<?php
namespace App\DTOs;
class UserDTO
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $phone,
        public readonly ?\DateTime $createdAt
    ) {}
    public static function fromArray(array $data): self
    {
        return new self(
            id: $data['id'],
            name: $data['name'],
            email: $data['email'],
            phone: $data['phone'] ?? null,
            createdAt: isset($data['created_at']) 
                ? new \DateTime($data['created_at']) 
                : null
        );
    }
    public static function fromModel($model): self
    {
        return new self(
            id: $model->id,
            name: $model->name,
            email: $model->email,
            phone: $model->phone,
            createdAt: $model->created_at
        );
    }
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
            'created_at' => $this->createdAt?->format('Y-m-d H:i:s')
        ];
    }
}

使用 Spatie Data Transfer Object 包

安装包:

composer require spatie/data-transfer-object
<?php
namespace App\DTOs;
use Spatie\DataTransferObject\DataTransferObject;
class UserDTO extends DataTransferObject
{
    public int $id;
    public string $name;
    public string $email;
    public ?string $phone;
    public ?\DateTimeInterface $createdAt;
    // 可选:添加验证规则
    public function getValidationRules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email'],
        ];
    }
}
// 使用示例
$userDTO = new UserDTO([
    'id' => 1,
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'phone' => null,
    'createdAt' => now(),
]);

使用 Laravel Eloquent API Resources

<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
            'created_at' => $this->created_at,
            'roles' => RoleResource::collection($this->whenLoaded('roles')),
        ];
    }
}
// 控制器中使用
public function show(User $user): UserResource
{
    return new UserResource($user);
}
// 集合
public function index(): ResourceCollection
{
    return UserResource::collection(User::paginate());
}

使用 AutoMapper 模式

<?php
namespace App\Mappers;
use App\DTOs\UserDTO;
use App\Models\User;
class UserMapper
{
    public function toDTO(User $user): UserDTO
    {
        return new UserDTO(
            id: $user->id,
            name: $user->name,
            email: $user->email,
            phone: $user->phone,
            createdAt: $user->created_at
        );
    }
    public function toDTOs($users): array
    {
        return $users->map(fn($user) => $this->toDTO($user))->toArray();
    }
    public function toModel(UserDTO $dto): User
    {
        $user = new User();
        $user->name = $dto->name;
        $user->email = $dto->email;
        $user->phone = $dto->phone;
        return $user;
    }
    public function updateFromDTO(User $user, UserDTO $dto): User
    {
        $user->name = $dto->name;
        $user->email = $dto->email;
        $user->phone = $dto->phone;
        return $user;
    }
}

高级实现 - 通用 DTO 映射器

<?php
namespace App\DTOs;
use Illuminate\Support\Str;
abstract class BaseDTO
{
    public static function fromArray(array $data): static
    {
        $dto = new static();
        foreach ($data as $key => $value) {
            $property = Str::camel($key);
            if (property_exists($dto, $property)) {
                $dto->$property = $value;
            }
        }
        return $dto;
    }
    public static function fromModel($model): static
    {
        return static::fromArray($model->toArray());
    }
    public function toArray(): array
    {
        $data = [];
        foreach (get_object_vars($this) as $property => $value) {
            $data[Str::snake($property)] = $value;
        }
        return $data;
    }
}
// 使用
class UserDTO extends BaseDTO
{
    public int $id;
    public string $name;
    public string $email;
    public ?string $phone;
    public ?string $createdAt;
}
// 控制器中使用
$userDTO = UserDTO::fromModel($user);
$usersDTOs = User::all()->map(fn($user) => UserDTO::fromModel($user));

使用 PHP 8 Attributes 自动映射

<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class MapFrom
{
    public function __construct(
        public string $source,
        public ?string $callback = null
    ) {}
}
// DTO 实现
class AdvancedUserDTO
{
    #[MapFrom('id')]
    public int $id;
    #[MapFrom('name')]
    public string $name;
    #[MapFrom('email')]
    public string $email;
    #[MapFrom('phone')]
    public ?string $phone;
    #[MapFrom('created_at', 'formatDate')]
    public ?string $createdAt;
    public static function fromModel($model): self
    {
        $dto = new self();
        $reflection = new \ReflectionClass($dto);
        foreach ($reflection->getProperties() as $property) {
            $attributes = $property->getAttributes(MapFrom::class);
            if (!empty($attributes)) {
                $mapFrom = $attributes[0]->newInstance();
                $sourceProperty = $mapFrom->source;
                $value = $model->$sourceProperty ?? null;
                if ($mapFrom->callback && method_exists($dto, $mapFrom->callback)) {
                    $value = $dto->{$mapFrom->callback}($value);
                }
                $property->setValue($dto, $value);
            }
        }
        return $dto;
    }
    protected function formatDate($value): ?string
    {
        return $value ? $value->format('Y-m-d H:i:s') : null;
    }
}

使用 Factory 模式

<?php
namespace App\Factories;
use App\DTOs\UserDTO;
use App\Models\User;
class UserDTOFactory
{
    public function makeFromRequest(array $requestData): UserDTO
    {
        return new UserDTO(
            id: $requestData['id'] ?? null,
            name: $requestData['name'],
            email: $requestData['email'],
            phone: $requestData['phone'] ?? null,
            createdAt: now()
        );
    }
    public function makeFromModel(User $user): UserDTO
    {
        return new UserDTO(
            id: $user->id,
            name: $user->name,
            email: $user->email,
            phone: $user->phone,
            createdAt: $user->created_at
        );
    }
    public function makeCollection($users): array
    {
        return $users->map(fn($user) => $this->makeFromModel($user))->toArray();
    }
}
// 注册为服务提供者
public function register()
{
    $this->app->singleton(UserDTOFactory::class, function ($app) {
        return new UserDTOFactory();
    });
}
// 控制器中使用
public function index(UserDTOFactory $factory)
{
    $users = User::all();
    return $factory->makeCollection($users);
}

推荐方案

对于新项目,我推荐使用 Spatie Data Transfer Object 包,它提供了:

  • 类型安全
  • 自动验证
  • 序列化和反序列化
  • 不变性(可选)
  • 嵌套 DTO 支持

选择哪种方案取决于你的具体需求:

  • 简单项目:基础 DTO 类
  • 需要验证:Spatie DTO 包
  • API 响应:Laravel API Resources
  • 复杂映射:AutoMapper 模式

抱歉,评论功能暂时关闭!