arnapou/dto

库 - 从数组创建对象以及反向操作的简单库。

v4.4 2024-06-25 23:04 UTC

This package is auto-updated.

Last update: 2024-09-17 06:38:40 UTC


README

pipeline coverage

这个库是一个简单的工具,可以从数组创建对象,反之亦然。

安装

composer require arnapou/dto

packagist 👉️ arnapou/dto

特性

这个库旨在保持 简单

“简单”意味着: 行为易于理解,没有魔法,通过使用 PHP 语言的内核,没有属性,也没有 phpdoc 等 ...

这基本上实现了以下转换

  • [data] 到 object = fromData =~ 反规范化
  • object 到 [data] = toData =~ 规范化
interface DataConverter
{
    public function fromData(array|string|float|int|bool|null $data, string $class): Success|Unsupported;

    public function toData(object $object): array|string|float|int|bool|Unsupported|null;
}

关于 fromDatatoData 返回值的说明

  • 如果值不受支持,转换器必须返回一个 Unsupported 对象。
  • fromData 可以返回一个 Success 对象,该对象携带结果。
  • 如果有错误,将引发一个 ConverterException
  • 如果您不想管理它,可以使用 ConverterWrapper 便利对象。

⚠️ 重要

这个库不允许您做您无法直接从用户空间做的事情:这是它的优势。

如果您需要在名称映射、私有属性的访问权限、getter/setter 调用、管理没有本地默认值的属性的默认值等方面进行大量自定义,那么我建议您查看类似

属性

  • 仅使用公共属性
  • 管理只读或提升的属性
  • 没有私有/受保护的魔法注入

方法

  • 创建对象时仅使用构造函数参数
  • 不使用其他方法(getter/setter 等)

名称映射

  • 默认情况下不进行映射
  • 如果您想映射某些内容,您需要实现一个专门的 DataConverter

值映射

关于数组和列表的说明

  • 数组在 PHP 中是多态的,因为它们可以是哈希表或列表
  • 无法确定何时将数组转换为对象列表
  • 因此,您必须

嵌套对象

  • 是的,当然,这是这个库的一个优势
  • 为了帮助管理嵌套对象,您可以使用一个BaseConverter,它遍历一个注入的DataConverter列表

性能

通过SchemaConverter完成的反射完全解耦到一个专门的接口Schema,该接口负责检索每个类的模式。

这可以由您来实现,或者您可以让ReflectionSchema执行其工作。所有元数据对象都是不可变的值对象,如果您想将其作为数组缓存,也可以使用此库进行转换。

如果您有很多(成百个?)为最终对象指定的转换器,并希望有一个更快的BaseConverter用于您的转换器,我建议您使用ClassMappingConverter

如何开始

我建议从DtoConverter开始,它是一个BaseConverter,具有自动设置嵌套转换器的功能

在进一步实现自己的转换器之前,先玩玩它:这应该满足您80%的需求。

您可能想查看此存储库的“example”目录中的运行示例

示例

use Arnapou\Dto\Converter\Result\Success;
use Arnapou\Dto\Converter\Result\Unsupported;

// Immutable DTO
readonly class ImmutableUser
{
    public function __construct(
        public int $id,
        public string $name,
        public DateTimeImmutable $createdAt = new DateTimeImmutable(),
    ) {
    }
}

// Array data
$data = [
    'id' => '1',
    'name' => 'Arnaud',
];

// Conversion
$converter = new \Arnapou\Dto\DtoConverter();
$result = $converter->fromData($data, ImmutableUser::class); 
$user = $result->get();

print_r($result->get());
// ImmutableUser Object
// (
//     [id] => 1
//     [name] => Arnaud
//     [createdAt] => DateTimeImmutable Object
//         (
//             [date] => 2023-09-03 18:29:48.118875
//             [timezone_type] => 3
//             [timezone] => Europe/Paris
//         )
// )

用例

当您想要

  • 严格的类型,因为性能和代码量较少很重要
  • 对象,因为数组占用内存较大,对象性能更好
  • 简单的类型验证
  • 想要丢弃任何无用的数据,只关注所需的输入

Json输入

$json = file_get_contents('php://input');
$data = json_decode($json, associative: true);

$myJsonPayload = $converter->fromData($data, MyJsonPayload::class);

查询、请求

$myQuery = $converter->fromData($_GET, MyQuery::class);
$myRequest = $converter->fromData($_POST, MyRequest::class);

实体、记录集

/** @var PDO $pdo */
$rows = $pdo->query($selectQuery, \PDO::FETCH_ASSOC)->fetch();

// Manual entities: beware of memory and performance if a lot of items.
$myEntities = [];
foreach ($rows as $row) {
    $myEntities[] = $converter->fromData($row, MyEntity::class);
}

// Automatic entities: better memory using iterators and on-demand conversion.
$myEntities = new \Arnapou\Dto\ObjectIterator($converter, $rows, MyEntity::class);

嵌套对象 & 递归

嵌套对象的转换入口点是BaseConverter。此类遍历一个转换器列表,以尝试转换您的数据/对象。

由于递归可能导致循环依赖的无限循环,我们通过简单的深度检查来管理它(见Depth)。

为了执行此检查,需要递归的提供转换器需要在其构造函数中提供一个BaseConverter

如果您需要对其进行专门化,可以扩展BaseConverter以覆盖构造函数。这是DtoConverter的情况。但由于继承可能导致问题,这是唯一可以覆盖的方法,所有其他方法都是最终的。

DataDecorator

如果您想在转换前后对数据进行自定义变异,您需要实现自己的 DataConverter,但考虑到递归,设计对象变得非常困难。

因此,我建议简单地使用一个 BaseConverter,并使用自定义的 DataDecorator

DataDecorator 是一个接口,允许在所有内部转换器的 fromDatatoData 之前或之后变异数据。然后它在递归的每一层执行,无需更改整个对象设计。

此接口可以帮助您管理项目生命周期的对象变化,需要时可以随时插入或拔出。

示例 设置 DataDecorator

// The DtoConverter is a specialized BaseConverter
$converter = new \Arnapou\Dto\DtoConverter();
$converter->dataDecorator = new MyCustomDataDecorator();

示例 实现一个 DataConverter,它自身实现了一个 DataDecorator 来将 - 替换为 _,因为属性名不能包含短横线

use Arnapou\Dto\DtoConverter;
use Arnapou\Dto\Converter\DataConverter;

final class MyConverter extends DtoConverter implements DataDecorator
{
    public function __construct()
    {
        parent::__construct();
        $this->dataDecorator = $this;
    }

    public function decorateFromData(float|int|bool|array|string|null $data, string $class): array|string|float|int|bool|null
    {
        if (!\is_array($data)) {
            return $data;
        }

        $modified = [];
        foreach ($data as $key => $value) {
            if (\is_string($key)) {
                $modified[str_replace('-', '_', $key)] = $value;
            } else {
                $modified[$key] = $value;
            }
        }

        return $modified;
    }

    public function decorateToData(float|int|bool|array|string|null $data, string $class): array|string|float|int|bool|null
    {
        return $data;
    }
}

PHP 版本

日期参考8.38.2
02/04/20244.x,主要×
25/11/20233.x×
17/09/20232.x×
03/09/20231.x×