arnapou / dto
库 - 从数组创建对象以及反向操作的简单库。
Requires
- php: ~8.3.0
- arnapou/ensure: ^2.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.52
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^1.10
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- phpstan/phpstan-strict-rules: ^1.5
- phpunit/php-code-coverage: ^11.0
- phpunit/phpunit: ^11.0
- symfony/serializer: ^6.4
README
这个库是一个简单的工具,可以从数组创建对象,反之亦然。
安装
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;
}
关于 fromData
和 toData
返回值的说明
- 如果值不受支持,转换器必须返回一个 Unsupported 对象。
fromData
可以返回一个 Success 对象,该对象携带结果。- 如果有错误,将引发一个 ConverterException。
- 如果您不想管理它,可以使用 ConverterWrapper 便利对象。
⚠️ 重要
这个库不允许您做您无法直接从用户空间做的事情:这是它的优势。
如果您需要在名称映射、私有属性的访问权限、getter/setter 调用、管理没有本地默认值的属性的默认值等方面进行大量自定义,那么我建议您查看类似
属性
- 仅使用公共属性
- 管理只读或提升的属性
- 没有私有/受保护的魔法注入
方法
- 创建对象时仅使用构造函数参数
- 不使用其他方法(getter/setter 等)
名称映射
- 默认情况下不进行映射
- 如果您想映射某些内容,您需要实现一个专门的 DataConverter
值映射
- 整数时间戳 <=>
DateTimeInterface
通过 DateTimeConverter - 字符串日期时间 <=>
DateTimeInterface
通过 DateTimeConverter - int + string <=>
BackedEnum
通过 EnumConverter - array <=>
stdClass
通过 StdClassConverter - array <=>
object
通过 SchemaConverter - array <=>
list<object|array|bool|float|int|string|null>
通过 CollectionConverter - 可以通过实现一个专门的 DataConverter 来自定义
关于数组和列表的说明
- 数组在 PHP 中是多态的,因为它们可以是哈希表或列表
- 无法确定何时将数组转换为对象列表
- 因此,您必须
- 使用对象来携带项目列表
- 实现一个专门的转换器
- 实现一个Collection,或扩展一个AbstractCollection
嵌套对象
- 是的,当然,这是这个库的一个优势
- 为了帮助管理嵌套对象,您可以使用一个BaseConverter,它遍历一个注入的DataConverter列表
性能
通过SchemaConverter完成的反射完全解耦到一个专门的接口Schema,该接口负责检索每个类的模式。
这可以由您来实现,或者您可以让ReflectionSchema执行其工作。所有元数据对象都是不可变的值对象,如果您想将其作为数组缓存,也可以使用此库进行转换。
如果您有很多(成百个?)为最终对象指定的转换器,并希望有一个更快的BaseConverter用于您的转换器,我建议您使用ClassMappingConverter。
如何开始
我建议从DtoConverter开始,它是一个BaseConverter,具有自动设置嵌套转换器的功能
在进一步实现自己的转换器之前,先玩玩它:这应该满足您80%的需求。
示例
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 是一个接口,允许在所有内部转换器的 fromData
或 toData
之前或之后变异数据。然后它在递归的每一层执行,无需更改整个对象设计。
此接口可以帮助您管理项目生命周期的对象变化,需要时可以随时插入或拔出。
示例 设置 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.3 | 8.2 |
---|---|---|---|
02/04/2024 | 4.x,主要 | × | |
25/11/2023 | 3.x | × | |
17/09/2023 | 2.x | × | |
03/09/2023 | 1.x | × |