volodymyr-hordiienko / plain-to-class
将数据集转换为结构化对象的 Class-transformer
Requires
- php: ^8.0
Requires (Dev)
- mockery/mockery: 1.5.1
- phpbench/phpbench: 1.2.8
- phpunit/phpunit: 10.2.1
- roave/infection-static-analysis-plugin: 1.32
- squizlabs/php_codesniffer: 3.7.1
- symfony/var-dumper: 6.3
- vimeo/psalm: 5.12.0
README
很遗憾,我不会说英语,文档是通过谷歌翻译编译的 :( 如果你能帮助我更准确地描述文档,我将很高兴 :)
这个库将允许你轻松地将任何数据集转换为所需的对象。你不需要改变类的结构,从外部模块继承等。无需跳舞的鼓,只需数据和正确的类。
编写不依赖于第三方包和框架的代码被认为是良好的实践。代码被划分为服务、领域区域、各种层等。
为了在层之间传输数据,通常使用 数据传输对象 (DTO) 模板。DTO 是用于封装数据并将数据从一个应用程序子系统发送到另一个应用程序子系统的对象。
因此,服务和方法与特定的对象及其所需的数据一起工作。同时,数据是从哪里获取的并不重要,它可以是 http 请求、数据库、文件等。
相应地,每次调用服务时,我们都需要初始化这个 DTO。但是,手动比较数据并不有效,这会影响代码的可读性,尤其是如果对象很复杂。
这就是这个包发挥作用的地方,它负责所有与映射和初始化必要的 DTO 相关的工作。
安装
该包可以通过 composer 安装
composer require yzen.dev/plain-to-class
注意:该包的当前版本仅支持 PHP 8.0 +。
对于 PHP 7.4 版本,您可以在 版本 v0.* 中阅读文档。
用法
常见用例
基本
namespace DTO; class CreateUserDTO { public string $email; public float $balance; }
$data = [ 'email' => 'test@mail.com', 'balance' => 128.41, ]; $dto = (new Hydrator())->create(CreateUserDTO::class, $data); // or static init $dto = Hydrator::init()->create(CreateUserDTO::class, $data); var_dump($dto);
结果
object(\CreateUserDTO) 'email' => string(13) "test@mail.com" 'balance' => float(128.41)
对于 PHP 8,也可以传递命名参数
$dto = Hydrator::init()->create(CreateUserDTO::class, email: 'test@mail.com', balance: 128.41 );
如果属性不是标量类型,而是允许 DTO 类的另一个属性,它也会自动转换。
class ProductDTO { public int $id; public string $name; } class PurchaseDTO { public ProductDTO $product; public float $cost; } $data = [ 'product' => ['id' => 1, 'name' => 'phone'], 'cost' => 10012.23, ]; $purchaseDTO = Hydrator::init()->create(PurchaseDTO::class, $data); var_dump($purchaseDTO);
输出
object(PurchaseDTO) public ProductDTO 'product' => object(ProductDTO) public int 'id' => int 1 public string 'name' => string 'phone' (length=5) public float 'cost' => float 10012.23
集合
如果您有一个特定类的对象数组,则必须指定该数组的 ConvertArray 属性,将其传递到您需要将元素转换为的类。
您也可以在 PHP DOC 中指定一个类,但这时您需要写出该类的完整路径 array <\DTO\ProductDTO>
。这是为了确切知道需要创建哪个实例。因为反射并不提供现成的函数来获取 use *
文件。除了 use *
,您还可以指定别名,这将更难追踪。示例
class ProductDTO { public int $id; public string $name; } class PurchaseDTO { #[ConvertArray(ProductDTO::class)] public array $products; } $data = [ 'products' => [ ['id' => 1, 'name' => 'phone',], ['id' => 2, 'name' => 'bread',], ], ]; $purchaseDTO = Hydrator::init()->create(PurchaseDTO::class, $data);
匿名数组
如果您需要将数据数组转换为类对象数组,可以使用 transformCollection
方法实现。
$data = [ ['id' => 1, 'name' => 'phone'], ['id' => 2, 'name' => 'bread'], ]; $products = Hydrator::init()->createCollection(ProductDTO::class, $data);
执行此操作的结果,您将得到 ProductDTO 对象数组
array(2) { [0]=> object(ProductDTO) { ["id"]=> int(1) ["name"]=> string(5) "phone" } [1]=> object(ProductDTO) { ["id"]=> int(2) ["name"]=> string(5) "bread" } }
您可能还需要对数组进行部分转换。在这种情况下,您可以传递一个类数组,然后可以轻松地展开。
$userData = ['id' => 1, 'email' => 'test@test.com', 'balance' => 10012.23]; $purchaseData = [ 'products' => [ ['id' => 1, 'name' => 'phone',], ['id' => 2, 'name' => 'bread',], ], 'user' => ['id' => 3, 'email' => 'fake@mail.com', 'balance' => 10012.23,], ]; $result = Hydrator::init()->createMultiple([UserDTO::class, PurchaseDTO::class], [$userData, $purchaseData]); [$user, $purchase] = $result; var_dump($user); var_dump($purchase);
结果
object(UserDTO) (3) { ["id"] => int(1) ["email"]=> string(13) "test@test.com" ["balance"]=> float(10012.23) } object(PurchaseDTO) (2) { ["products"]=> array(2) { [0]=> object(ProductDTO)#349 (3) { ["id"]=> int(1) ["name"]=> string(5) "phone" } [1]=> object(ProductDTO)#348 (3) { ["id"]=> int(2) ["name"]=> string(5) "bread" } } ["user"]=> object(UserDTO)#332 (3) { ["id"]=> int(3) ["email"]=> string(13) "fake@mail.com" ["balance"]=> float(10012.23) } }
编写风格
经常遇到编写风格问题,例如,在数据库中是 snake_case,在 camelCase 代码中是。它们需要不断地进行某种转换。该包会处理这个问题,您只需在属性上指定 WritingStyle 属性即可
class WritingStyleSnakeCaseDTO { #[WritingStyle(WritingStyle::STYLE_CAMEL_CASE, WritingStyle::STYLE_SNAKE_CASE)] public string $contact_fio; #[WritingStyle(WritingStyle::STYLE_CAMEL_CASE)] public string $contact_email; } $data = [ 'contactFio' => 'yzen.dev', 'contactEmail' => 'test@mail.com', ]; $model = Hydrator::init()->create(WritingStyleSnakeCaseDTO::class, $data); var_dump($model);
RESULT: object(WritingStyleSnakeCaseDTO) (2) { ["contact_fio"]=> string(8) "yzen.dev" ["contact_email"]=> string(13) "test@mail.com" }
别名
可以为属性设置各种可能的别名,这些别名也会在数据源中进行搜索。如果DTO是从不同的数据源生成的,这将非常有用。
class WithAliasDTO { #[FieldAlias('userFio')] public string $fio; #[FieldAlias(['email', 'phone'])] public string $contact; }
自定义设置器
如果一个字段在初始化时需要额外的处理,你可以使用mutator。要定义一个mutator,在你的类中定义一个set{Attribute}Attribute方法,其中{Attribute}是你想访问的属性的驼峰命名。当尝试设置模型上的real_address属性的值时,这个mutator将被自动调用。
class UserDTO { public int $id; public string $real_address; public function setRealAddressAttribute(string $value) { $this->real_address = strtolower($value); } }
转换后
在类内部,你可以创建一个afterTransform
方法,这个方法将在转换完成后立即调用。在其中,我们可以通过已经与对象的状态工作来描述我们的附加验证或转换逻辑。
class UserDTO { public int $id; public float $balance; public function afterTransform() { $this->balance = 777; } }
自定义转换
如果你需要完全自行转换,那么你可以在类中创建一个transform方法。在这种情况下,不会调用库处理,转换的所有责任都传递到你的类。
class CustomTransformUserDTOArray { public string $email; public string $username; public function transform($args) { $this->email = $args['login']; $this->username = $args['fio']; } }
缓存
该包支持类缓存机制以避免反射的成本。这个功能仅在您有大量类或存在多个实体的循环转换时推荐使用。在普通的轻量级DTO上,这将只有5-10%,这将是文件系统中的不必要的访问。
您可以通过将配置传递给hydrator构造函数来启用缓存。
(new Hydrator(new HydratorConfig(true))) ->create(PurchaseDto::class, $data);
比较
我还对当前类似的产品进行了比较,以下是主要缺点:
- 仅适用于特定框架。
- 强制继承或更改您当前的类结构。
- 转换需要更长时间。
以下是我的基准比较示例: