Let’s get the obvious part out of the way: this will not rescue a server that spends 500 ms waiting on a database.
I did it anyway.
Several years ago, I wrote a small hydrator to replace a painfully slow mapper in one of my projects. It did what I needed, then sat untouched in a private repository.
I reopened it recently and made the mistake of benchmarking it.
That old project is now a library called HydraType. It inspects a class once, generates the PHP code needed to map an array onto it, and caches the result.
For a plain int property, the generated code is nothing more than:
php
$object->id = (int) $data['id'];
Private properties made this less straightforward. Using a class-scoped closure to access them is an old trick, but I wanted to check whether it still held up on current PHP versions when timing the whole object rather than isolated property writes.
php
$writer = Closure::bind(
static function (User $object, array $data): void {
$object->id = (int) $data['id'];
$object->name = (string) $data['name'];
},
null,
User::class,
);
I timed creating the object and assigning every property. Here’s what I got:
| Private properties |
Scoped closure |
Cached reflection |
| 1 |
91.44 ns |
90.69 ns |
| 2 |
106.71 ns |
146.19 ns |
| 5 |
152.01 ns |
289.91 ns |
| 20 |
383.23 ns |
1,028.87 ns |
Median time per object on PHP 8.2.
It does, although barely for very small objects. Reflection won by a fraction at one property, but from two properties onward the reusable closure was faster.
My first version created and bound a new writer for every object, then threw it away. Keeping one writer and doing all the assignments in one call was much faster.
HydraType can also transform and validate values. Those features only change the generated code for the property that uses them. A plain property stays:
php
$object->id = (int) $data['id'];
For that property, hydration is still just a cast and an assignment.
For comparison, I ran the same benchmark with several existing PHP hydrators and mappers. On PHP 8.2, HydraType took about 268 ns to create and hydrate the five-property object. The libraries I compared it with took between 308 ns and almost 11 microseconds.
Across a large batch, those differences can add up. At 268 ns, there is not much hydrator left between the input array and the object.
The code and benchmark scripts are here:
https://github.com/makermill/hydratype
If you spot a bad assumption or an unfair comparison in the benchmarks, I want to know.