d3jmc / data-transfer-object
轻松管理PHP中的数据传输对象。
Requires
- php: ^8.0
README
轻松管理PHP中的数据传输对象。
这个DTO库,为PHP 8及以上版本设计,通过严格的类型提示属性、嵌套数据映射和值操作,旨在保持项目中的数据一致性。
作者
- Dom McLaughlin @d3jmc
安装
我们建议通过Composer安装此库。
composer require d3jmc/data-transfer-object
或者,如果您愿意,可以将此存储库克隆到您的项目中。
使用方法
每个开发人员或项目在创建和管理DTO时都有自己的偏好。请随意使用您喜欢的任何方法。这些只是指导方针。
在这个例子中,我们将创建一个简单的User DTO,它将存储所有用户配置文件信息、GDPR偏好和角色。
创建User DTO类
我们首先需要创建一个用于用户的DTO类。此类将包含我们想要存储的所有属性以及我们需要用于修改传入数据的任何自定义逻辑。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserDto extends DataTransferObject { public ?string $firstName = null; public ?string $lastName = null; }
属性名称应始终为camelCase。当将数据填充到DTO中时,您可以使用snake_case,这将被自动转换。如果您正在将数据传递到DTO中,但输出不是您期望的,这可能是由于名称不匹配。
填充User DTO类
将数据填充到DTO类非常简单。您可以将数据传递给构造函数或使用fill方法。
请注意,数据必须是一个数组。
<?php $data = [ 'first_name' => 'John', 'last_name' => 'Doe', ]; // via the constructor $userDto = new App\Dto\UserDto($data); // via the `fill` method $userDto = new App\Dto\UserDto(); $userDto->fill($data);
输出User DTO类的属性
您可以使用两种方法来返回DTO类内的属性。这些是get和toArray。前者将返回DTO类的实例,而toArray将按照其名称所描述的将属性转换为数组。
<?php // App\Dto\UserDto {#293 ▼ // +firstName: "John" // +lastName: "Doe" // } $userDto->get(); // array:5 [▼ // "firstName" => "John" // "lastName" => "Doe" // ] $userDto->toArray();
将另一个DTO类类型提示到一个属性
假设我们想要存储用户的GDPR偏好。虽然我们可以在User DTO类中为每个设置创建一个单独的属性,但将它们组合在自己的DTO类中会更好。这不仅会使一次性提取所有GDPR属性变得更容易,我们还可以在其他地方重用此类。
所以,让我们创建一个新的用于GDPR偏好的DTO类。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserGdprDto extends DataTransferObject { public bool $email = false; public bool $post = false; public bool $sms = false; }
现在,在User DTO类中,我们需要创建一个属性。请注意对$gdpr属性的类型提示。这是必要的,以便在填充数据时,它将被自动映射到UserGdprDto类。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserDto extends DataTransferObject { public ?string $firstName = null; public ?string $lastName = null; public ?UserGdprDto $gdpr = null; }
最后,在我们的数据数组中,我们可以添加用户的GDPR偏好。
<?php $data = [ 'first_name' => 'John', 'last_name' => 'Doe', 'gdpr' => [ 'email' => true, 'post' => false, 'sms' => false, ], ];
输出可能如下所示
<?php print_r($userDto->get(), true); // App\Dto\UserDto {#293 ▼ // +firstName: "John" // +lastName: "Doe" // +gdpr: App\Dto\UserGdprDto {#298 ▼ // +email: true // +post: false // +sms: false // } // }
将另一个DTO类类型提示到一个属性作为数组
在这个例子中,我们想要存储用户的角色,但用户可以有多个角色,因此此属性需要是一个数组。就像在之前的例子中一样,我们可能会在其他地方的项目中引用角色,因此创建一个单独的UserRoleDto类会更好。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserRoleDto extends DataTransferObject { public ?string $ident = null; public ?string $name = null; public ?string $description = null; }
现在,在我们的主要User DTO类中,我们需要创建一个用于角色的属性。这与我们的$gdpr属性略有不同,因为我们需要创建一个文档注释来告诉库每个数组应该是UserRoleDto类的实例。
这必须指定为完整的命名空间。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserDto extends DataTransferObject { public ?string $firstName = null; public ?string $lastName = null; public ?UserGdprDto $gdpr = null; /** * @var array<App\Dto\UserRoleDto> */ public array $roles = []; }
现在,在我们的数据数组中,我们可以添加角色。
<?php $data = [ 'first_name' => 'John', 'last_name' => 'Doe', 'gdpr' => [ 'email' => true, 'post' => false, 'sms' => false, ], 'roles' => [ [ 'ident' => '*', 'name' => 'Super Admin', 'description' => 'Unrestricted access to the system', ], ], ];
输出可能如下所示
<?php print_r($userDto->get(), true); // App\Dto\UserDto {#293 ▼ // +firstName: "John" // +lastName: "Doe" // +gdpr: App\Dto\UserGdprDto {#298 ▼ // +email: true // +post: false // +sms: true // } // +roles: array:1 [▼ // 0 => App\Dto\UserRoleDto {#300 ▼ // +ident: "*" // +name: "Super Admin" // +description: "Unrestricted access to the system" // } // ] // }
此功能也适用于多层嵌套数组,例如,如果您在角色中有一个权限数组,只需重复此过程即可。
数据映射
在某些情况下,您发送给DTO类的数据可能不会与您设置的属性匹配。一个常见的例子是在处理API数据时。在您的项目中,您将用户的姓氏称为'last_name',而API将其称为'surname'。这可以通过创建映射来解决。
<?php $apiData = [ 'first_name' => 'John', 'surname' => 'Doe', ]; $userDto = new App\Dto\UserDto($apiData);
上述代码的输出将返回John作为名字,但姓氏为null,因为我们的DTO类没有'surname'属性。
映射只是一个键值数组,其中键是DTO类中的属性名,值是您要使用的数据的键。
<?php $map = [ // last_name is our DTO property // surname is what is returned by the API 'last_name' => 'surname', ];
您的DTO类可以通过构造函数或使用map函数来接受映射。如果您选择使用函数,请确保它在fill函数之前触发,否则映射将不起作用。
<?php // via the constructor $userDto = new App\Dto\UserDto($apiData, $map); // via the `map` method $userDto = new App\Dto\UserDto(); $userDto->map($map); $userDto->fill($apiData);
映射也适用于类型提示的DTO属性。
<?php $apiData = [ 'gdpr' => [ 'by_email' => true, 'by_post' => false, 'by_sms' => false, ], 'roles' => [ 'id' => '*', ], ]; $map = [ 'gdpr' => [ 'email' => 'by_email', 'post' => 'by_post', 'sms' => 'by_sms', ], 'roles' => [ 'ident' => 'id', ], ]; $userDto = new App\Dto\UserDto($apiData, $map);
数据处理
有时,您在DTO类中接收到的数据不是您想要的格式,或者您可能希望在值为null时创建一个回退,或者甚至根据其他属性值填充整个新属性。
您可以通过在DTO类中使用魔法set函数来实现这一点。您可以通过调用setPropertyName()来覆盖任何属性值。该函数将包括一个默认值参数。
在这个例子中,我们想要创建一个displayName属性,该属性将使用firstName和lastName的值,或者如果数据中提供了一个displayName值,则使用原始的displayName值。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserDto extends DataTransferObject { public ?string $firstName = null; public ?string $lastName = null; public ?string $displayName = null; public function setDisplayName(string $displayName = null): void { $this->displayName = $displayName ?: "{$this->firstName} {$this->lastName}"; } }
另一个例子是在我们的UserRoleDto类中,我们想要指定他们是否是超级用户。
<?php namespace App\Dto; use D3jmc\DataTransferObject\DataTransferObject; class UserRoleDto extends DataTransferObject { public ?string $ident = null; public ?bool $isSu = false; public function setIsSu(bool $isSu = false): void { $this->isSu = ($isSu ?: (bool) ($this->ident === '*')); } }
贡献
欢迎贡献!
请提交您的PR,我们将尽快进行审查。