sunrise/hydrator 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

sunrise/hydrator

Composer 安装命令:

composer require sunrise/hydrator

包简介

Strictly typed object hydration and value casting.

README 文档

README

Scrutinizer Code Quality Code Coverage Build Status Code Intelligence Status

Languages: 🇬🇧 English | 🇷🇺 Русский

The package hydrates object properties from arrays and JSON with casting to declared PHP types. Individual values can also be cast without hydrating an object.

Installation

composer require sunrise/hydrator

The package supports PHP 7.4 and newer. Examples in this README target PHP 8+.

Object Hydration

use Sunrise\Hydrator\Annotation\Filter;
use Sunrise\Hydrator\Annotation\ItemType;
use Sunrise\Hydrator\Hydrator;

final class CreateUserRequest
{
    #[Filter('trim')]
    public string $email;

    public bool $isActive;

    #[ItemType('string', limit: 10)]
    public array $roles = [];
}

$request = (new Hydrator())->hydrate(CreateUserRequest::class, [
    'email' => ' user@example.com ',
    'isActive' => 'true',
    'roles' => ['user', 'editor'],
]);

The hydrator:

  • creates an object without calling its constructor or fills an existing object;
  • skips properties marked with #[Ignore];
  • ignores input keys that do not match any property;
  • hydrates nested objects recursively;
  • collects property and element hydration errors into one InvalidDataException.

An input value is required when the corresponding property is not initialized and no default value is defined for it. A default value can be defined in the property declaration, in the constructor parameter with the same name, or through #[DefaultValue].

Default values are used only when the input key is missing. A passed null is treated as an input value and is validated against the property type.

Hydration from JSON

$request = (new Hydrator())->hydrateWithJson(
    CreateUserRequest::class,
    '{"email":"user@example.com","isActive":true}'
);

The root JSON value must be an object or an array.

Value Casting

use Sunrise\Hydrator\Dictionary\BuiltinType;
use Sunrise\Hydrator\Hydrator;
use Sunrise\Hydrator\Type;

$value = (new Hydrator())->castValue(
    '42',
    Type::fromName(BuiltinType::INT)
);

Supported Types

PHP type Accepted input values
mixed Any value
bool bool or the strings 1, 0, true, false, yes, no, on, off
int int or a string representation of an integer
float float, int, or a string representation of a number
string string or int
array array or stdClass
DateTimeImmutable and subclasses A string in the configured format; an integer or string for format U
DateInterval A string accepted by the DateInterval constructor
DateTimeZone A timezone identifier
A concrete backed enum A value of the corresponding backing type; for int, a string representation of an integer is also accepted
An instantiable user-defined class array or stdClass
A class implementing ArrayAccess array or stdClass

Also supported:

Type Package
A subclass of MyCLabs\Enum\Enum myclabs/php-enum
Ramsey\Uuid\UuidInterface ramsey/uuid
Subclasses of Symfony\Component\Uid\AbstractUid symfony/uid

null is accepted only for a type that allows null. For booleans, numbers, dates, intervals, timezones, enums, UUIDs, and UIDs, an empty string after trimming is treated as a missing value: null is returned for a nullable type; otherwise an InvalidValueException is thrown. Union and intersection types are not supported.

Arrays and Collections

#[ItemType] defines the type of array or collection elements and optionally limits their count:

use Sunrise\Hydrator\Annotation\ItemType;

final class OrderRequest
{
    #[ItemType(OrderItemRequest::class, limit: 100)]
    public array $items;
}

Each element will be cast to the declared type. The allowsNull parameter allows null values in elements:

#[ItemType('int', allowsNull: true, limit: 100)]
public array $ids;

For an instantiable class implementing ArrayAccess, the element type can also be inferred from the type of the last constructor parameter declared with ...:

final class UserCollection extends ArrayObject
{
    public function __construct(User ...$users)
    {
        parent::__construct($users);
    }
}

The constructor is not called during hydration: the hydrator only reads the type of its last variadic parameter and creates the object without the constructor. An explicit #[ItemType] takes precedence over the constructor parameter type. If the collection throws OverflowException while adding an element, the hydrator treats it as an element limit overflow and returns an array overflow error to the user.

Element Types from PHPDoc

The hydrator can read the element type of an array property from @var:

final class OrderRequest
{
    /** @var list<OrderItemRequest> */
    public array $items;
}

$hydrator = new Hydrator(isDocBlockReaderEnabled: true);

PHPDoc reading is disabled by default. #[ItemType] takes precedence over @var. @var is not used for classes implementing ArrayAccess. Use #[ItemType] or the type of the variadic constructor parameter instead.

Attributes

Attribute Purpose
#[Alias('external-name')] Defines the input key for a property
#[Context([...])] Defines or overrides context values for a property or parameter
#[DefaultValue(...)] Defines a value used when the input key is missing
#[Filter(...)] Transforms the input value before type casting
#[Format('...')] Defines the date and time format
#[Ignore] Excludes a property from hydration
#[ItemType(...)] Defines the element type and maximum element count

#[Filter] can be used more than once. Filters are applied in sequence:

use Sunrise\Hydrator\Annotation\Filter;

#[Filter('trim')]
#[Filter('strtolower')]
public string $email;

#[Filter] is available only on PHP 8.0 and newer.

Date, Time, and Context

By default, DateTimeImmutable uses the DateTimeInterface::RFC3339_EXTENDED format.

Format and timezone can be configured for a hydrator instance:

use DateTimeInterface;
use Sunrise\Hydrator\Dictionary\ContextKey;
use Sunrise\Hydrator\Hydrator;

$hydrator = new Hydrator(context: [
    ContextKey::TIMESTAMP_FORMAT => DateTimeInterface::RFC3339_EXTENDED,
    ContextKey::TIMEZONE => 'Europe/Belgrade',
]);

Operation-specific context is passed to hydrate() or castValue(). Use #[Context] and #[Format] for a property or parameter:

use Sunrise\Hydrator\Annotation\Context;
use Sunrise\Hydrator\Annotation\Format;
use Sunrise\Hydrator\Dictionary\ContextKey;

#[Format('Y-m-d')]
#[Context([ContextKey::TIMEZONE => 'UTC'])]
public DateTimeImmutable $date;

Precedence order:

  1. attribute context;
  2. operation context;
  3. hydrator context.

Errors

hydrate() collects individual value errors into InvalidDataException:

use Sunrise\Hydrator\Exception\InvalidDataException;

try {
    $request = $hydrator->hydrate(CreateUserRequest::class, $data);
} catch (InvalidDataException $e) {
    foreach ($e->getExceptions() as $error) {
        echo $error->getPropertyPath() . ': ' . $error->getMessage() . PHP_EOL;
    }
}

InvalidValueException provides:

  • getPropertyPath() — dot-separated value path;
  • getErrorCode() — error code;
  • getMessage() — resolved message;
  • getMessageTemplate() — message template;
  • getMessagePlaceholders() — template parameters;
  • getInvalidValue() — original value;
  • getTranslationDomain() — translation domain.

Method exceptions:

  • hydrate() throws InvalidDataException for input value errors and InvalidObjectException when the object cannot be created or a property has an unsupported type;
  • hydrateWithJson() additionally uses InvalidDataException for JSON decoding errors and an invalid root value;
  • castValue() throws InvalidValueException for a single invalid value, InvalidDataException for nested object or array errors, and InvalidObjectException for an unsupported target type.

If symfony/validator is installed, InvalidValueException::getViolation() and InvalidDataException::getViolations() return violations in the Symfony Validator format.

Custom Type Converters

A converter implements TypeConverterInterface. If the type is not supported, the method returns without a result. If it is supported, it yields the result or throws an exception.

use Generator;
use Sunrise\Hydrator\Exception\InvalidValueException;
use Sunrise\Hydrator\Type;
use Sunrise\Hydrator\TypeConverterInterface;

final class MoneyTypeConverter implements TypeConverterInterface
{
    public function castValue(
        $value,
        Type $type,
        array $path,
        array $context
    ): Generator {
        if ($type->getName() !== Money::class) {
            return;
        }

        if (!is_string($value)) {
            throw InvalidValueException::mustBeString($path, $value);
        }

        yield Money::fromString($value);
    }

    public function getWeight(): int
    {
        return 100;
    }
}

A converter can be passed to the constructor or added later:

$hydrator = new Hydrator(typeConverters: [
    new MoneyTypeConverter(),
]);

$hydrator->addTypeConverter(new MoneyTypeConverter());

Converters are called in descending weight order. Implement HydratorAwareInterface or AnnotationReaderAwareInterface if a converter needs access to the hydrator or the annotation reader.

Compatibility

On PHP 8.0 and newer, attributes are read automatically. On PHP 7.4, Doctrine Annotations can be used for Alias, Context, DefaultValue, Format, Ignore, and ItemType:

composer require doctrine/annotations
use Sunrise\Hydrator\AnnotationReader\DoctrineAnnotationReader;

$hydrator->setAnnotationReader(DoctrineAnnotationReader::default());

On PHP 7, only DateTimeImmutable itself is supported, not its subclasses. Built-in enums are supported starting with PHP 8.1.

Deprecated names #[Subtype] and #[Relationship] are kept for backward compatibility. Use #[ItemType] in new code.

sunrise/hydrator 适用场景与选型建议

sunrise/hydrator 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 33.76k 次下载、GitHub Stars 达 21, 最近一次更新时间为 2021 年 03 月 26 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「hydrator」 「hydration」 「type casting」 「type caster」 「type converter」 「type conversion」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 sunrise/hydrator 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 sunrise/hydrator 我们能提供哪些服务?
定制开发 / 二次开发

基于 sunrise/hydrator 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 33.76k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 21
  • 点击次数: 14
  • 依赖项目数: 5
  • 推荐数: 0

GitHub 信息

  • Stars: 21
  • Watchers: 1
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2021-03-26