yzen.dev / 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>
。这样做是为了确切知道您需要创建哪个实例。由于反射(Reflection)没有提供开箱即用的获取 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);
比较
我还对当前类似产品进行了比较,以下是主要缺点:
- 仅适用于特定框架
- 迫使继承或更改你的当前类结构
- 转换时间更长
以下是我的基准比较示例