uxf / hydrator
Requires
- php: ^8.4
- nette/php-generator: ^4.0
- phpdocumentor/type-resolver: ^1.7 || ^2.0
- thecodingmachine/safe: ^3.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- 3.x-dev
- 3.85.1
- 3.85.0
- 3.84.0
- 3.80.0
- 3.79.2
- 3.79.0
- 3.78.7
- 3.78.2
- 3.78.0
- 3.77.9
- 3.77.4
- 3.77.3
- 3.76.3
- 3.76.0
- 3.75.0
- 3.73.5
- 3.73.4
- 3.72.2
- 3.72.1
- 3.72.0
- 3.70.5
- 3.70.3
- 3.70.2
- 3.70.0
- 3.67.3
- 3.65.3
- 3.65.0
- 3.64.3
- 3.61.5
- 3.60.12
- 3.60.10
- 3.60.0
- 3.59.0
- 3.58.0
- 3.57.12
- 3.57.0
- 3.56.0
- 3.55.0
- 3.54.0
- 3.53.3
- 3.44.5
- 3.44.4
- 3.44.3
- 3.44.2
- 3.44.0
- 3.41.0
- 3.38.0
- 3.36.3
- 3.36.2
- 3.34.0
- 3.29.0
- 3.26.0
- 3.23.1
- 3.21.4
- 3.21.0
- 3.20.0
- 3.19.2
- 3.17.0
- 3.13.2
- 3.13.0
- 3.10.0
- 3.8.0
- 3.7.3
- 3.6.0
- 3.5.0
- 3.4.0
- 3.3.0
- 3.2.4
- 3.2.3
- 3.2.2
- 3.2.1
- 3.2.0
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- dev-main
This package is auto-updated.
Last update: 2026-10-09 15:08:35 UTC
README
The fastest PHP object hydrator.....
Install
$ composer req uxf/hydrator
Config
// config/packages/uxf.php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use UXF\Core\Http\Request\NotSet;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('uxf_hydrator', [
'ignored_types' => [NotSet::class], // optional
'collection_types' => ['array'], // optional
'map_sources' => [__DIR__], // optional
'overwrite' => true, // optional (default %kernel.debug%)
'warmup' => true, // optional (default null = enabled when overwrite is false)
'default_options' => [
'allow_lax_string' => true, // default false
'allow_trim_string' => true, // default false
'allow_fallback' => true, // default false
'nullable_optional' => true, // default false
'xml_mode' => true, // default false
],
]);
};
Warmup
bin/console cache:warmup pre-generates type casters, so production (e.g. a Docker image) never generates them at runtime:
uxf/core- classes used by#[FromBody],#[FromQuery],#[FromForm]and#[FromHeader]controller argumentsuxf/graphql- GraphQL inputs
Nested classes are generated too. A class which cannot be generated fails the warmup.
warmup: null (default) runs only when overwrite is disabled (prod), true / false forces it on / off.
Tests (paratest)
With overwrite: true (default in debug) every process regenerates each type caster it uses and waits for the file lock,
so parallel test workers repeat the same work. For faster tests disable overwrite in the test environment
(warmup is then enabled automatically) and warm the cache before running tests:
// config/packages/uxf.php
if ($containerConfigurator->env() === 'test') {
$containerConfigurator->extension('uxf_hydrator', [
'overwrite' => false,
]);
}
bin/console cache:warmup -e test
vendor/bin/paratest
With overwrite: false the runtime never checks whether a class changed. Run cache:warmup (or cache:clear)
after changing a hydrated class, otherwise tests run with an outdated type caster (e.g. a single test started from IDE).
Classes not covered by the warmup are still generated at runtime once; files are written atomically, so parallel
workers never include a partially written file.
Basic usage
ObjectHydrator create object from
class Person
{
public function __construct(
public readonly string $name,
public readonly int $age,
public readonly Sex $sex, // enum
public readonly DateTimeImmutable $createdAt,
public readonly ?string $note = null, // optional
) {
}
}
$hydrator = $container->get(UXF\Hydrator\ObjectHydrator::class);
$data = [
'name' => 'Joe Doe',
'age' => 55,
'sex' => 'MALE',
'createdAt' => '2000-01-02T13:03:01+00:00',
];
// single object
/** @var Person $person */
$person = $this->hydrator->hydrateArray($data, Person::class);
var_dump($person);
/*
Person {
name: "Joe Doe" (string)
age: 55 (int)
sex: Sex::MALE, (enum)
createdAt: "2000-01-02T13:03:01+00:00" (DateTimeImmutable)
}
*/
// array of objects
/** @var Person[] $people */
$people = $this->hydrator->hydrateArrays([$data], Person::class);
Exceptions
try {
$badPerson = $this->hydrator->hydrateArray($badData, Person::class);
} catch (HydratorException $e) {
/** @var array<string, array<string>> $errors */
$errors = $e->errors;
}
Options
- You can set
default_optionsin bundle configuration or as ObjectHydrator constructor parameter. - Or pass
Optionsparameter with unique name tohydrateArray/hydrateArraysmethod. - Use unique option name!!! Same option name with different values can lead to unexpected results...
allow_lax_string: true
Convert all scalars or stringable objects to string.
class Test
{
public function __construct(public readonly string $helloWorld) {}
}
$a = ['helloWorld' => new Uuid()]; // Uuid is Stringable
$b = ['helloWorld' => 1]; // 1 => '1'
allow_trim_string: true
Trim all strings.
class Test
{
public function __construct(public readonly string $helloWorld) {}
}
$a = ['helloWorld' => ' A ']; // helloWorld => 'A'
allow_fallback: true
Use FallbackParameterGenerator as fallback hydrator variant.
class Test
{
public function __construct(public readonly mixed $helloWorld) {}
}
$a = ['helloWorld' => ['every value is possible', 1, new DateTime(), ['hello' => 'kitty']];
nullable_optional: true
final readonly class Test
{
public function __construct(
public ?string $helloWorld,
) {
}
}
$a = []; // helloWorld => null
xml_mode: true
Convert scalars from string + enable parse empty/one-item/multi-item collections
HydratorProperty
use UXF\Hydrator\Attribute\HydratorProperty;
class Cart
{
public function __construct(
#[HydratorProperty('@id')]
public readonly int $id,
) {
}
}
{
"@id": 1
}
HydratorXml
With xml_mode: true you can use HydratorXml PHP attribute for XML objects with optional XML attributes. Compatible with Symfony XmlEncoder.
use UXF\Hydrator\Attribute\HydratorProperty;
use UXF\Hydrator\Attribute\HydratorXml;
#[HydratorXml('text')] // "text is PHP property name with #"
final readonly class CatDto
{
public function __construct(
#[HydratorProperty('#')]
public string $text,
#[HydratorProperty('@ID')]
public ?int $id = null,
) {
}
}
Accept
<cat ID="2">CAT</cat>
OR
<cat>CAT</cat>
HydratorMap
Interface
use UXF\Hydrator\Attribute\HydratorMap;
// interface
#[HydratorMap(property: 'type', matrix: [
'o' => Orienteering::class,
'p' => Paragliding::class,
])]
interface Activity
{
}
// children
class Orienteering implements Activity
{
public function __construct(
public readonly int $card,
) {
}
}
class Paragliding implements Activity
{
public function __construct(
public readonly string $glider,
) {
}
}
// usage
class Club
{
/**
* @param Activity[] $activities
*/
public function __construct(
public readonly array $activities,
public readonly Activity $activity,
) {
}
}
Abstract class
use UXF\Hydrator\Attribute\HydratorMap;
// abstract class
#[HydratorMap(property: 'type', matrix: [
'c' => CrossCountrySkiing::class,
'o' => Orienteering::class,
'p' => Paragliding::class,
])]
abstract class Sport
{
}
// children
class CrossCountrySkiing extends Sport
{
public function __construct(
public readonly string $ski,
) {
}
}
class Orienteering extends Sport
{
public function __construct(
public readonly int $card,
) {
}
}
class Paragliding extends Sport
{
public function __construct(
public readonly string $glider,
) {
}
}
// usage
class Club
{
/**
* @param Sport[] $sports
*/
public function __construct(
public readonly array $sports,
public readonly Sport $sport,
) {
}
}
HydratorMapKey
use UXF\Hydrator\Attribute\HydratorMapKey;
#[HydratorMapKey('r')]
final class Running extends Sport
{
public function __construct(
public readonly string $type,
public readonly string $shoes,
) {
}
}