larapie / data-transfer-object
带有验证的数据传输对象
Requires
- php: ^7.1
- doctrine/annotations: ^1.6|^2.0
- phpdocumentor/reflection-docblock: ~4.0
- symfony/validator: ^4.2
Requires (Dev)
- jchook/phpunit-assert-throws: ^1.0
- larapack/dd: ^1.0
- phpunit/phpunit: ^7.0
README
注意
尽管大部分代码库已经被完全重写。该项目最初是从 (https://github.com/spatie/data-transfer-object) 分支出来的。基础仓库仍然由 spatie (https://github.com/spatie) 维护。我们的目标是提高性能(主要通过反射属性的缓存)并添加更多功能来改进此包(见下文)。
改进
-
提高了不可变性。
-
反射属性 & 注解缓存(以提高性能)。
-
完全限定名称解析。
-
不可变属性(通过注解)。
-
验证(通过注解)。
-
覆盖 & 添加属性。
-
可选属性支持。
-
注解/验证继承。
安装
您可以通过 composer 安装此包
composer require larapie/data-transfer-object
您有没有过…
…与从请求、CSV文件或JSON API检索的数组数据一起工作;并想知道其中有什么内容?
这里有一个例子
public function handleRequest(array $dataFromRequest) { $dataFromRequest[/* what to do now?? */]; }
此包的目的是将“非结构化数据”结构化,这些数据通常存储在关联数组中。通过将这些数据结构化为对象,我们可以获得几个优点
- 我们可以对数据传输对象进行类型提示,而不是仅仅调用它们
array。 - 通过使我们的对象上的所有属性可类型化,我们可以确保它们的值永远不会是我们不期望的。
- 由于属性类型化,我们可以静态分析它们并获得自动完成。
让我们看看JSON API调用的例子
$post = $api->get('posts', 1); [ 'title' => '…', 'body' => '…', 'author_id' => '…', ]
处理此数组很困难,因为我们始终需要参考文档才能知道它确切的内容。此包允许您创建数据传输对象定义,类,这些类将以结构化的方式表示数据。
我们已尽力使语法和开销尽可能少
class PostData extends DataTransferObject { /** @var string */ public $title; /** @var string */ public $body; /** @var \Author */ public $author; }
现在可以像这样构建 PostData 对象
$postData = new PostData([ 'title' => '…', 'body' => '…', 'author_id' => '…', ]);
现在您可以以结构化的方式使用这些数据
$postData->title; $postData->body; $postData->author_id;
当然,您可以在 PostData 上添加静态构造函数
class PostData extends DataTransferObject { // … public static function fromRequest(Request $request): self { return new self([ 'title' => $request->get('title'), 'body' => $request->get('body'), 'author' => Author::find($request->get('author_id')), ]); } }
通过在我们的属性上添加文档块,它们的值将与给定的类型进行验证;如果值不符合给定的类型,则将抛出 TypeError。
以下是声明类型的可能方法
class PostData extends DataTransferObject { /** * Built in types: * * @var string */ public $property; /** * Classes with their FQCN: * * @var \App\Models\Author */ public $property; /** * Lists of types: * * @var \App\Models\Author[] */ public $property; /** * Union types: * * @var string|int */ public $property; /** * Nullable types: * * @var string|null */ public $property; /** * Mixed types: * * @var mixed|null */ public $property; /** * No type, which allows everything */ public $property; }
当PHP 7.4引入类型属性时,您只需删除文档块并用新的内置语法类型化属性即可。
不可变性
如果您想使数据对象永不改变(在某些情况下这是一个好主意),您可以使其不可变
class PostData extends DataTransferObject { use MakeImmutable; /** @var string */ public $name; }
如果您只想使某个属性不可变,您可以在变量上对其进行注释。
class PostData extends DataTransferObject { /** * @Immutable * @var string $name */ public $name; }
尝试在构建后更改 $postData 的属性将导致 ImmutableDtoException。
可选属性
默认情况下,所有 dto 属性都是必需的。如果您想使 dto 上的某些属性可选
class PostData extends DataTransferObject { /** * @Optional * @var string $name */ public $name; }
注意
当属性被注释为可选时,将不会设置默认值!
附加属性
默认情况下,只能设置 dto 的属性。尝试输入未在 dto 中声明为公共属性的任何数据将抛出 UnknownPropertiesDtoException 异常。如果您想允许额外的属性,可以通过实现 AdditionalProperties 或 WithAdditionalProperties 接口来完成。
AdditionalProperties
class PostData extends DataTransferObject implements AdditionalProperties { /** * @var string $name */ public $name; } $dto = new PostData(["name" => "foo", "address" => "bar"]); $dto->toArray(); returns: ["name" => "foo"]
WithAdditionalProperties
class PostData extends DataTransferObject implements WithAdditionalProperties { /** * @var string $name */ public $name; } $dto = new PostData(["name" => "foo", "address" => "bar"]); $dto->toArray(); returns: ["name" => "foo", "address" => "bar"]
重写 & 添加属性
如果您想在 dto 中添加或重写某个值,可以按以下方式操作
添加数据
public function create(PostData $data, User $user) { $data->with('user_id', $user->id); return $this->repository->create($data->toArray()); }
重写属性
public function create(PostData $data, User $user) { if($this->user->isAdmin()){ $data->override('name', 'admin'); } $data->with('user_id', $user->id); return $this->repository->create($data->toArray()); }
注意
-
您不能在不可变 dto 上添加或重写数据。您也不能重写不可变属性。
-
您不能使用 with 方法添加在 dto 中声明为公共属性的属性。
验证
约束
如果您想验证属性的输入,可以通过注解来完成。
class PostData extends DataTransferObject { /** * @var string * @Assert\NotBlank() * @Assert\Length(min = 3, max = 20) */ public $name; }
继承
如果您想扩展 dto 并添加额外的约束或可选注解,可以通过添加 Inherit 注解来完成。这将合并父类中所有现有的约束。如果当前类未指定类型,它也将继承父 dto 的类型。
class UpdatePostData extends PostData { /** * @Optional * @Inherit */ public $name; }
注意
- 如果您正在使用 phpstorm,您可以安装此插件:https://plugins.jetbrains.com/plugin/7320-php-annotations 以进行注解的类型提示。
Optional注解将不会从父类继承。这是为了确保您始终对 dto 中所需的值有清晰的了解。- 验证不是在对象构造时进行的,而是在访问变量时进行。这可以通过魔法 __get 方法
$dto->property或通过通过toArray()或all()方法输出数组值来完成。您还可以手动调用validate()方法。如果 dto 无效,将抛出ValidatorException。 ValidatorException消息将包括所有属性名称以及特定属性上失败的约束。例如:属性 'author_name' 是必需的。- 为实现此功能,使用了优秀的
symfony\validation库。有关更多信息,请参阅 https://symfony.com.cn/doc/current/validation.html。
与集合一起工作
如果您正在处理 DTO 集合,您可能还希望对您的集合进行自动完成和适当的类型验证。此包添加了一个简单的集合实现,您可以从它扩展。
use \Spatie\DataTransferObject\DataTransferObjectCollection; class PostCollection extends DataTransferObjectCollection { public function current(): PostData { return parent::current(); } }
通过重写 current 方法,您将在 IDE 中获得自动完成,并像这样使用集合。
foreach ($postCollection as $postData) { $postData-> // … your IDE will provide autocompletion. } $postCollection[0]-> // … and also here.
当然,您可以自由实现自己的静态构造函数
class PostCollection extends DataTransferObjectCollection { public static function create(array $data): PostCollection { $collection = []; foreach ($data as $item) { $collection[] = PostData::create($item); } return new self($collection); } }
嵌套 DTO 的自动转换
如果您有嵌套 DTO 字段,传递给父 DTO 的数据将自动进行转换。
class PostData extends DataTransferObject { /** @var \AuthorData */ public $author; }
PostData 现在可以按以下方式构建
$postData = new PostData([ 'author' => [ 'name' => 'Foo', ], ]);
嵌套数组 DTO 的自动转换
与上述类似,嵌套数组 DTO 将自动进行转换。
class TagData extends DataTransferObject { /** @var string */ public $name; } class PostData extends DataTransferObject { /** @var \TagData[] */ public $tags; }
PostData 将自动构建标签,如下所示
$postData = new PostData([ 'tags' => [ ['name' => 'foo'], ['name' => 'bar'] ] ]);
辅助函数
还提供了一些辅助函数,用于一次性处理多个属性。
$postData->all(); $postData ->only('title', 'body') ->toArray(); $postData ->except('author') ->toArray();
您还可以链接这些方法
$postData ->except('title') ->except('body') ->toArray();
请注意,except 和 only 是不可变的,它们不会更改原始数据传输对象。
异常处理
除了属性类型验证之外,您还可以确保整个数据传输对象始终有效。在从数据传输对象(通过all()和toArray()方法以及访问dto的属性时,例如$dto->name)输出数据时,我们会验证所有必需的属性是否已设置,约束条件是否满足以及每个属性是否为正确的类型。如果不符合条件,将会抛出Larapie\DataTransferObject\Exceptions\ValidatorException异常。
同样,如果您尝试设置未定义的属性,将会收到Larapie\DataTransferObject\Exceptions\UnknownPropertiesDtoException异常。
测试
composer test
变更日志
有关最近更改的详细信息,请参阅变更日志。
贡献
有关详细信息,请参阅贡献指南。
鸣谢
许可
MIT许可(MIT)。有关更多信息,请参阅许可文件。