larapie/data-transfer-object

带有验证的数据传输对象

3.1.2 2019-05-02 22:15 UTC

README

Latest Stable Version Build Status Scrutinizer Code Quality Code Coverage StyleCI Total Downloads

注意

尽管大部分代码库已经被完全重写。该项目最初是从 (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 异常。如果您想允许额外的属性,可以通过实现 AdditionalPropertiesWithAdditionalProperties 接口来完成。

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();

请注意,exceptonly 是不可变的,它们不会更改原始数据传输对象。

异常处理

除了属性类型验证之外,您还可以确保整个数据传输对象始终有效。在从数据传输对象(通过all()toArray()方法以及访问dto的属性时,例如$dto->name)输出数据时,我们会验证所有必需的属性是否已设置,约束条件是否满足以及每个属性是否为正确的类型。如果不符合条件,将会抛出Larapie\DataTransferObject\Exceptions\ValidatorException异常。

同样,如果您尝试设置未定义的属性,将会收到Larapie\DataTransferObject\Exceptions\UnknownPropertiesDtoException异常。

测试

composer test

变更日志

有关最近更改的详细信息,请参阅变更日志

贡献

有关详细信息,请参阅贡献指南

鸣谢

许可

MIT许可(MIT)。有关更多信息,请参阅许可文件