middag / blueprint
强类型数据传输对象
Requires
- php: >=7.1
- symfony/property-access: ^4.0
- symfony/property-info: ^4.0
Requires (Dev)
- doctrine/annotations: ^1.6
- doctrine/cache: ^1.8
- friendsofphp/php-cs-fixer: ^2.13
- ocramius/proxy-manager: ^2.1
- phpstan/phpstan: ^0.11.1
- phpstan/phpstan-strict-rules: ^0.11.0
- phpunit/phpunit: ^7.5
- psr/cache: ^1.0
- symfony/framework-bundle: ^4.2
- symfony/serializer: ^4.1
- symfony/validator: ^4.1
- symfony/var-dumper: ^4.1
Suggests
- ocramius/proxy-manager: for working with ghost Data Transfer Objects
- symfony/validator: for performing extensive validation on properties
README
安装
您可以通过Composer安装此软件包
composer require middag/blueprint
为什么使用这个库
在许多情况下,您的应用程序依赖于外部数据:API、CSV文件等等。如果您喜欢类型属性(以及IDE自动完成)则数据传输对象(DTO)是最佳选择,但外部系统往往会发生变化。这通常意味着您的DTO不再包含您期望的数据,从而导致应用程序的其他部分出现故障。
这就是Blueprint发挥作用的地方。使用Blueprint,您可以像定义任何其他PHP类一样定义DTO,但内置了额外的检查。Blueprint检查您的DTO中的类型定义,并可选地使用Symfony Validator组件执行额外的验证。底层,Blueprint使用Symfony的PropertyInfo和PropertyAccess组件。
如何使用
您只需将您的DTO从DataTransferObject
扩展即可。考虑以下DTO
use MIDDAG\Blueprint\DataTransferObject; final class Invoice extends DataTransferObject { /** * @var int */ public $id; /** * @var float */ public $amount; /** * @var string|null */ public $notes; /** * @var InvoiceLine[] */ public $lines = []; }
上面的定义确保$id
始终是整数,$amount
是浮点数,而$lines
始终是InvoiceLine
的实例。$notes
应该是字符串或null。
您可以通过向maker传递值来实例化对象
$invoice = Invoice::make([ 'id' => 100, 'amount' => 25.00, 'notes' => 'Optional notes', ]);
嵌套DTO
如果您有一个由其他DTO组成的DTO,您可以传递嵌套数组中的数据
$invoice = Invoice::make([ 'id' => 100, 'amount' => 25.00, 'notes' => 'Optional notes', 'lines' => [ ['id' => 101, 'description' => 'Invoice line 1'], // Is turned into an InvoiceLine object ['id' => 102, 'description' => 'Invoice line 2'], // Is turned into an InvoiceLine object ] ]);
使用Symfony Validator
首先,安装Symfony组件及其依赖项以处理注解
composer require symfony/validator doctrine/annotations doctrine/cache
在配置AnnotationLoader之后,您可以在DTO属性上使用注解以执行额外的检查
/** * @Assert\GreaterThan(value=0) * * @var float */ public $amount;
更改属性
您可以像任何其他对象一样更改属性(例如$object->foo = 'bar'
),但这不会执行我们所有的花哨检查。您有两个选项
选项1:写入并验证对象
$dto = SomeDtoObject::make(['foo' => 'bar']); $dto->otherProperty = 'value'; // Perform validation $dto->validate();
选项2:使用Writer
use MIDDAG\Blueprint\Writer; $writer = Writer::create(); $dto = SomeDtoObject::make(['foo' => 'bar']); // Write to the object $writer->write($dto, 'otherProperty', 'value');
设置器
由于Blueprint使用Symfony PropertyAccess组件来写入DTO属性,因此您也可以为DTO添加自定义设置器。这允许您在验证之前进行最后时刻的更改
final class Invoice extends DataTransferObject { /** * @var float */ public $amount; /** * @param mixed $input */ public function setAmount($input): void { // Turn empty values into 0.00 $this->amount = (float) $input ?: 0.00; } }
请注意,自定义设置器仅在构建DTO或使用写入器时才起作用,而不是直接写入属性时(例如:$object->property = 'foo'
)。
脏对象
尽管听起来有些矛盾,但在某些情况下,您可能需要故意使用尚未包含所有数据的DTO。这就是为什么Blueprint支持所谓的“脏”DTO。
$invoice = Invoice::dirty();
脏DTO不能被规范化,并且您不能在不抛出异常的情况下对它们调用->toArray()
。这是为了确保您不会意外地依赖于实际上不在有效状态下的DTO。您可以通过调用->isDirty()
来检查DTO是否为脏的。
您可以像往常一样修改DTO。一旦您调用->validate()
并执行验证,DTO就会变成一个普通的DTO。
延迟加载幽灵对象
让我们通过将Blueprint DTO转换为延迟加载幽灵对象来使事情变得更加酷。
Blueprint可以与ProxyManager一起使用,这样DTO就变成了幽灵对象或代理。这允许您在需要时动态获取某些属性(并且仅在访问时),并且只需访问一次。
DTO的验证与以前完全相同,但现在您可以为基类属性提供一个集合,然后在访问时获取附加属性。这是通过一个可调用对象完成的:初始化器。此初始化器可以从任何地方获取数据:API或任何您想要的东西。
让我们以前面的Invoice
DTO为例。首先,确保它从GhostDataTransferObject
扩展。然后,使用Invoice::proxy()
而不是常规的new Invoice(/* ... */)
来创建DTO。
$factory = new LazyLoadingGhostFactory(); class Invoice extends GhostDataTransferObject { // ... } $invoice = Invoice::proxy([ 'id' => 100, 'amount' => 25.00, ], $factory, function (Invoice $proxy) { // TODO: fetch the invoice lines from a remote source to make things more exciting $proxy->lines[] = [ InvoiceLine::make([/* ... */]); ]; // You can write to any property in $proxy here }); // Prints "100" and does not call our initializer print $invoice->id; // Silently calls our initializer and prints the invoice lines print_r($invoice->lines); // Won't call the initializer again and prints the invoice lines print_r($invoice->lines);
使用Symfony?
如果您加载了BlueprintBundle
,Blueprint将使用cache.system
缓存池来加速反射。此外,Writer
和Validator
将作为服务可用。
致谢
这个库受到了Spatie团队出色工作的启发 🙌 请确保查看他们的spatie/data-transfer-object
项目。