middag/blueprint

强类型数据传输对象

1.11.0 2020-01-03 14:23 UTC

README

Build Status

安装

您可以通过Composer安装此软件包

composer require middag/blueprint

为什么使用这个库

在许多情况下,您的应用程序依赖于外部数据:API、CSV文件等等。如果您喜欢类型属性(以及IDE自动完成)则数据传输对象(DTO)是最佳选择,但外部系统往往会发生变化。这通常意味着您的DTO不再包含您期望的数据,从而导致应用程序的其他部分出现故障。

这就是Blueprint发挥作用的地方。使用Blueprint,您可以像定义任何其他PHP类一样定义DTO,但内置了额外的检查。Blueprint检查您的DTO中的类型定义,并可选地使用Symfony Validator组件执行额外的验证。底层,Blueprint使用Symfony的PropertyInfoPropertyAccess组件。

如何使用

您只需将您的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缓存池来加速反射。此外,WriterValidator将作为服务可用。

致谢

这个库受到了Spatie团队出色工作的启发 🙌 请确保查看他们的spatie/data-transfer-object项目。