eve / dto
简单、灵活的数据传输对象库
Requires
- php: ^7.4|~8
Requires (Dev)
- eve/coding-standard: ^1.0
- phpunit/phpunit: ^9.5
README
这是一个用PHP编写的简单、灵活的数据传输对象库。
为什么?
在层之间传递数据通常使用关联数组,这可以说是常见做法。例如,创建新用户的业务方法可能如下所示
// UserService.php public function createUser(array $attributes): User { return User::create($attributes); }
可以通过以下方式调用此方法,例如,控制器
// UserController.php public function store(CreateUserRequest $request) { $this->userService->create($request->toArray()); }
这种方法可以工作,但存在一些缺点
- 关联数组几乎是未结构化的——几乎没有对可以放入其中的内容或每个元素的数据类型进行限制。这使得代码难以推理(无法确切知道数组中有什么),并且可能导致严重的安全问题。
- 始终需要参考(如果存在)文档以了解数组的“形状”。这降低了可重用性和生产力。
- 静态代码分析和IDE自动补全支持受到很大阻碍。
现在想象一下,如果我们不是使用任意数组,而是使用具有类型属性的对象
// UserCreationData.php class UserCreationData { public string $email; public string $password; public ?int $age; } // UserService.php public function createUser(UserCreationData $data): User { return User::create($data->toArray()); } // UserController.php public function store(CreateUserRequest $request) { $this->userService->create(UserCreationData::fromRequest($request)); }
采用这种方法,我们可以清楚地了解预期的字段,它们的类型和其他限制,并可以享受所有类型提示、自动补全、静态分析等。这正是eve/dto允许您做的。
需求和安装
您可以通过Composer安装eve/dto
composer require eve/dto
此软件包需要PHP ≥7.4。
从v1.x迁移
此库的v1.x版本包含严格的类型检查,例如,将字符串分配给布尔属性将引发错误。虽然这个特性很有用,但它不属于DTO的范围,已经被从v2中删除。您被鼓励使用PHPStan或Psalm这样的静态分析工具来完成任务。
用法
基本用法
按照上面的示例,首先,我们让UserCreationData
扩展Eve\DTO\DataTransferObject
并定义所有属性为公共属性
class UserCreationData extends \Eve\DTO\DataTransferObject { public string $email; public string $password; public ?int $age; }
要构造一个新的UserCreationData
实例,请使用带有参数数组的make
$data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'age' => 30, ]);
或者,您可以显式设置属性。上面的代码本质上与以下相同
$data = UserCreationData::make(); $data->email = 'alice@company.tld'; $data->password = 'SoSecureWow'; $data->age = 30;
或者您可以使用流畅的set
方法,它可以接受关联数组或两个分离的$name
、$value
参数
$data = UserCreationData::make() ->set('email', 'alice@company.tld') ->set([ 'password' => 'SoSecureWow', 'age' => 30, ]);
如果传递的任何属性在类定义中不存在,将抛出异常
UserCreationData::make(['nope' => 'bar']); // throws "Public property $nope does not exist in class UserCreationData"
然后我们可以调用toArray()
方法将对象转换为关联数组
$arr = $data->toArray(); // ['email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'age' => 30]
请注意,未设置的属性将不会包含在输出数组中
$data = UserCreationData::make(); // Only setting email now $data->email = 'alice@company.tld'; $arr = $data->toArray(); // ['email' => 'alice@company.tld']
这在例如您有一个修补数据库记录的方法时特别方便,因为它允许操作完全灵活——您可以选择修补所有属性或仅修补它们的子集。
嵌套DTO
嵌套DTO将转换为相应的数组
class UserCreationData extends \Eve\DTO\DataTransferObject { public string $email; public string $password; public UserInformationData $information; } class UserInformationData extends \Eve\DTO\DataTransferObject { public int $age; } $data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'information' => UserInformationData::make(['age' => 30]), ]); $data->toArray(); // ['email' => 'alice@company.tld', 'password' => 'SoSecureWow', ['information' => ['age' => 30]]
辅助函数
-
DataTransferObject::only(string ...$names): static
返回只包含$names
的输出数组的对象。$data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'age' => 30, ]); $data->only('email', 'password')->toArray(); // ['email' => 'alice@company.tld', 'password' => 'SoSecureWow']
-
DataTransferObject::except(string ...$names): static
返回排除$names
的输出数组的对象。$data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'age' => 30, ]); $data->except('email', 'password')->toArray(); // ['age' => 30]
-
DataTransferObject::compact(): static
返回只包含值不为NULL
的属性的输出数组的对象。$data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', 'age' => null, ]); $data->compact()->toArray(); // ['email' => 'alice@company.tld', 'password' => 'SoSecureWow']
-
DataTransferObject::get(string $name, $default = null): mixed
返回$name
属性的值。如果$name
在类定义中不存在,将抛出异常。如果$name
存在但没有初始化,将返回$default
。重要:PHP 将未指定类型的属性,例如
public $prop
,视为已用 NULL 初始化。$data = UserCreationData::make([ 'email' => 'alice@company.tld', 'password' => 'SoSecureWow', ]); $data->get('email'); // 'alice@company.tld' $data->password; // 'SoSecureWow' $data->age; // throws "UserCreationData::$age must not be accessed before initialization." $data->get('age', 30); // 30 $data->get('nope'); // throws "Public property $nope does not exist in class UserCreationData." $data->nope; // throws "Public property $nope does not exist in class UserCreationData."
与 spatie/data-transfer-object 的区别
尽管 eve/dto 受到 spatie/data-transfer-object 的启发并具有一些相似之处,但这两个包存在某些区别,其中最显著的区别如下
- spatie/data-transfer-object 要求所有非空属性必须在实例化时提供。这种行为并不总是可行或理想(参考上面的数据修补示例)。eve/dto 采用一种更加宽容的方法,允许使用属性子集创建 DTO。
- spatie/data-transfer-object 无法检测或阻止你直接分配一个不存在的属性(例如,
$userData->non_existent = 'foo'
),而 eve/dto 做这一点是为了确保对象的完整性。 - spatie/data-transfer-object 实现了诸如“数据传输对象集合”和“灵活数据传输对象”等特性。为了保持简单和简洁,eve/dto 没有这些实现。
许可证
MIT