spatie/data-transfer-object

此包已废弃,不再维护。作者建议使用 spatie/laravel-data 包。

内置电池的数据传输对象

3.9.1 2022-09-16 13:34 UTC

README

警告 我们 决定 停止维护此包。

考虑迁移到 spatie/laravel-datacuyz/valinor

请随意fork我们的代码,并根据您的需求进行修改。

内置电池的数据传输对象

Latest Version on Packagist Test Total Downloads

安装

您可以通过composer安装此包

composer require spatie/data-transfer-object
  • 注意: 此包的v3版本仅支持 php:^8.0。如果您需要旧版本,请查看 v2

支持我们

68747470733a2f2f6769746875622d6164732e73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f646174612d7472616e736665722d6f626a6563742e6a70673f743d31

我们投入了大量资源来创建 一流的开放源代码包。您可以通过 购买我们的付费产品之一 来支持我们。

我们非常感谢您从您的家乡寄来明信片,说明您正在使用我们的哪些包。您可以在 我们的联系页面 找到我们的地址。我们将在 我们的虚拟明信片墙 上发布所有收到的明信片。

使用

此包的目标是尽可能简化从(序列化)数据数组构造对象的过程。以下是一个DTO的示例

use Spatie\DataTransferObject\Attributes\MapFrom;
use Spatie\DataTransferObject\DataTransferObject;

class MyDTO extends DataTransferObject
{
    public OtherDTO $otherDTO;
    
    public OtherDTOCollection $collection;
    
    #[CastWith(ComplexObjectCaster::class)]
    public ComplexObject $complexObject;
    
    public ComplexObjectWithCast $complexObjectWithCast;
    
    #[NumberBetween(1, 100)]
    public int $a;
    
    #[MapFrom('address.city')]
    public string $city;
}

您可以这样构建这个DTO

$dto = new MyDTO(
    a: 5,
    collection: [
        ['id' => 1],
        ['id' => 2],
        ['id' => 3],
    ],
    complexObject: [
        'name' => 'test',
    ],
    complexObjectWithCast: [
        'name' => 'test',
    ],
    otherDTO: ['id' => 5],
);

让我们逐一讨论所有可能性。

命名参数

可以使用命名参数来构建DTO。同时,您仍然可以使用旧的数组表示法。此示例与上面的示例等价。

$dto = new MyDTO([
    'a' => 5,
    'collection' => [
        ['id' => 1],
        ['id' => 2],
        ['id' => 3],
    ],
    'complexObject' => [
        'name' => 'test',
    ],
    'complexObjectWithCast' => [
        'name' => 'test',
    ],
    'otherDTO' => ['id' => 5],
]);

值转换

如果DTO具有属性是另一个DTO或DTO集合,则包将自动处理将数据数组转换为这些DTO。

$dto = new MyDTO(
    collection: [ // This will become an object of class OtherDTOCollection
        ['id' => 1],
        ['id' => 2], // Each item will be an instance of OtherDTO
        ['id' => 3],
    ],
    otherDTO: ['id' => 5], // This data will be cast to OtherDTO
);

自定义转换器

您可以构建自己的转换器类,这些类将接受它们所接受的所有输入,并将输入转换为所需的结果。

看看这个 ComplexObject

class ComplexObject
{
    public string $name;
}

及其转换器 ComplexObjectCaster

use Spatie\DataTransferObject\Caster;

class ComplexObjectCaster implements Caster
{
    /**
     * @param array|mixed $value
     *
     * @return mixed
     */
    public function cast(mixed $value): ComplexObject
    {
        return new ComplexObject(
            name: $value['name']
        );
    }
}

类特定转换器

您不仅可以在每个属性上指定应使用哪个转换器,还可以在目标类本身上定义该转换器

class MyDTO extends DataTransferObject
{
    public ComplexObjectWithCast $complexObjectWithCast;
}
#[CastWith(ComplexObjectWithCastCaster::class)]
class ComplexObjectWithCast
{
    public string $name;
}

默认转换器

您可以在DTO类本身上定义默认转换器。当在DTO类中遇到具有特定类型的属性时,将使用这些转换器。

#[
    DefaultCast(DateTimeImmutable::class, DateTimeImmutableCaster::class),
    DefaultCast(MyEnum::class, EnumCaster::class),
]
abstract class BaseDataTransferObject extends DataTransferObject
{
    public MyEnum $status; // EnumCaster will be used
    
    public DateTimeImmutable $date; // DateTimeImmutableCaster will be used
}

使用自定义转换器参数

任何转换器都可以传递自定义参数,内置的ArrayCaster实现是这种用法的一个很好的例子。

在传递输入到您的转换器时使用命名参数可以帮助使您的代码更清晰,但这不是必需的。

例如

    /** @var \Spatie\DataTransferObject\Tests\Foo[] */
    #[CastWith(ArrayCaster::class, itemType: Foo::class)]
    public array $collectionWithNamedArguments;
    
    /** @var \Spatie\DataTransferObject\Tests\Foo[] */
    #[CastWith(ArrayCaster::class, Foo::class)]
    public array $collectionWithoutNamedArguments;

请注意,传递给转换器构造函数的第一个参数始终是要转换的值的类型(s)的数组。所有其他参数都将作为在CastWith属性中传递的额外参数。

验证

此包不提供任何特定的验证功能,但它提供了一种构建自己的验证属性的方法。例如,NumberBetween是一个用户实现的验证属性

class MyDTO extends DataTransferObject
{
    #[NumberBetween(1, 100)]
    public int $a;
}

它的工作原理如下

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class NumberBetween implements Validator
{
    public function __construct(
        private int $min,
        private int $max
    ) {
    }

    public function validate(mixed $value): ValidationResult
    {
        if ($value < $this->min) {
            return ValidationResult::invalid("Value should be greater than or equal to {$this->min}");
        }

        if ($value > $this->max) {
            return ValidationResult::invalid("Value should be less than or equal to {$this->max}");
        }

        return ValidationResult::valid();
    }
}

映射

您可以使用#[MapFrom]属性从具有不同名称的源属性将DTO属性映射过来。

它可以与“点”表示法属性名或索引一起使用。

class PostDTO extends DataTransferObject
{
    #[MapFrom('postTitle')]
    public string $title;
    
    #[MapFrom('user.name')]
    public string $author;
}

$dto = new PostDTO([
    'postTitle' => 'Hello world',
    'user' => [
        'name' => 'John Doe'
    ]
]);
class UserDTO extends DataTransferObject
{

    #[MapFrom(0)]
    public string $firstName;
    
    #[MapFrom(1)]
    public string $lastName;
}

$dto = new UserDTO(['John', 'Doe']);

有时您还希望在转换过程中映射它们。一个典型的用例是将驼峰式转换为蛇形。为此,您可以使用#[MapTo]属性。

class UserDTO extends DataTransferObject
{

    #[MapFrom(0)]
    #[MapTo('first_name')]
    public string $firstName;
    
    #[MapFrom(1)]
    #[MapTo('last_name')]
    public string $lastName;
}

$dto = new UserDTO(['John', 'Doe']);
$dto->toArray() // ['first_name' => 'John', 'last_name'=> 'Doe'];
$dto->only('first_name')->toArray() // ['first_name' => 'John'];

严格DTO

此包的早期版本添加了FlexibleDataTransferObject类,允许您忽略DTO上不存在的属性。这种行为已经改变,现在所有DTO都是默认灵活的,但您可以使用#[Strict]属性使它们变得严格

class NonStrictDto extends DataTransferObject
{
    public string $name;
}

// This works
new NonStrictDto(
    name: 'name',
    unknown: 'unknown'
);
use \Spatie\DataTransferObject\Attributes\Strict;

#[Strict]
class StrictDto extends DataTransferObject
{
    public string $name;
}

// This throws a \Spatie\DataTransferObject\Exceptions\UnknownProperties exception
new StrictDto(
    name: 'name',
    unknown: 'unknown'
);

辅助函数

还提供了一些辅助函数,用于同时处理多个属性。

$postData->all();

$postData
    ->only('title', 'body')
    ->toArray();
    
$postData
    ->except('author')
    ->toArray();

请注意,all()将简单地返回所有属性,而toArray()将还将嵌套DTO转换为数组。

您可以链式调用except()only()方法

$postData
    ->except('title')
    ->except('body')
    ->toArray();

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

不可变DTO和克隆

此包不强制不可变对象,因为PHP不支持它们,但您始终鼓励保持您的DTO不可变。为了帮助您,每个DTO都有一个接受数据以覆盖的clone方法

$clone = $original->clone(other: ['name' => 'a']);

请注意,$original中的任何数据都没有更改。

DTO集合

此版本删除了DataTransferObjectCollection类。相反,您可以使用简单的转换器和您自己的集合类。

以下是将DTO集合转换为DTO数组的一个示例

class Bar extends DataTransferObject
{
    /** @var \Spatie\DataTransferObject\Tests\Foo[] */
    #[CastWith(FooArrayCaster::class)]
    public array $collectionOfFoo;
}

class Foo extends DataTransferObject
{
    public string $name;
}
class FooArrayCaster implements Caster
{
    public function cast(mixed $value): array
    {
        if (! is_array($value)) {
            throw new Exception("Can only cast arrays to Foo");
        }

        return array_map(
            fn (array $data) => new Foo(...$data),
            $value
        );
    }
}

如果您不想使用冗余的类型提示,或者需要扩展的集合功能;您可以使用任何集合实现来创建自己的集合类。在这个示例中,我们使用Laravel的

class Bar extends DataTransferObject
{
    #[CastWith(FooCollectionCaster::class)]
    public CollectionOfFoo $collectionOfFoo;
}

class Foo extends DataTransferObject
{
    public string $name;
}
use Illuminate\Support\Collection;

class CollectionOfFoo extends Collection
{
    // Add the correct return type here for static analyzers to know which type of array this is 
    public function offsetGet($key): Foo
    {
        return parent::offsetGet($key);
    }
}
class FooCollectionCaster implements Caster
{
    public function cast(mixed $value): CollectionOfFoo
    {
        return new CollectionOfFoo(array_map(
            fn (array $data) => new Foo(...$data),
            $value
        ));
    }
}

简单DTO数组

对于简单的DTO数组,或者实现PHP内置的ArrayAccess的对象,可以考虑使用需要提供项目类型的ArrayCaster

class Bar extends DataTransferObject
{
    /** @var \Spatie\DataTransferObject\Tests\Foo[] */
    #[CastWith(ArrayCaster::class, itemType: Foo::class)]
    public array $collectionOfFoo;
}

测试

composer test

变更日志

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

贡献

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

安全

如果您发现有关安全性的错误,请通过[email protected]发送邮件,而不是使用问题跟踪器。

Postcardware

您可以使用此软件包,但如果它进入了您的生产环境,我们非常感谢您从家乡寄给我们一张明信片,注明您正在使用我们的哪些软件包。

我们的地址是:Spatie,Kruikstraat 22,2018 安特卫普,比利时。

我们将所有收到的明信片发布在我们的公司网站上

外部工具

  • json2dto:一个将JSON对象转换为DTO类的图形界面(支持嵌套)。还提供了一个CLI工具用于本地使用。
  • 数据传输对象工厂:智能生成DTO实例,根据属性名称和类型使用正确的内容。

致谢

我们的Arr类包含从Laravel的Arr辅助函数中复制的函数。

许可

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