Object hydration looks like a solved problem until you measure it. You fetch a row from the database, you map the array to an object, and you move on. Most of us treat it as plumbing—unseen, unsexy, and assumed to be fast enough. Then one day you profile a batch job or a queue worker and notice that a surprising chunk of CPU time vanishes into the mapper. That realization is exactly what led to HydraType, a hydrator built around one stubborn rule: a class should never pay for features its properties do not use.
If a field is a plain string that needs zero transformation, the generated code should look like a human wrote it by hand. Direct assignment. No loops, no pipelines, no reflection.
What Hydration Actually Costs
At first glance, turning ['name' => 'Alice', 'age' => 30] into a User object seems trivial. The trouble starts when you want flexibility. Most general-purpose hydrators lean on reflection to inspect classes at runtime. They build metadata maps, negotiate type coercion, and resolve nested object graphs. They also love pipelines. Every value crawls through a sequence of mutators, assertions, and transformers, even if that sequence is empty. The loop structure itself adds overhead.
Fetch a few dozen records and you will never notice. But modern PHP applications routinely process thousands of queue messages, import massive CSV sets, or hydrate deep result sets from Elasticsearch. In those scenarios, a mapper that burns even a few microseconds per field becomes a real bottleneck. A thousand objects with eight fields each multiplies into a tax you feel.
The Zero-Overhead Philosophy
The central idea behind HydraType is feature isolation. Type conversion, nested object hydration, and assertions are all supported, yet none of them are mandatory. If a property needs nothing but a straight copy from array to object, the generated writer contains exactly that. There is no shared pipeline forcing a plain scalar field to wait in line behind validators it does not need.
This is harder to achieve than it sounds. Many libraries share a single execution path for every field because it keeps the codebase small and uniform. HydraType takes the opposite approach. It generates a unique PHP class for each target DTO, emitting precisely the operations that specific class requires. Complexity becomes opt-in, not a blanket tax on every property.
How the Speed Happens
Four concrete choices separate HydraType from both generic reflection-based tools and even other generated approaches.
1. Generate Code Once, Run It Forever
HydraType inspects your target class a single time, then writes a dedicated PHP class tailored to its exact shape. If your DTO contains a public string $name, an int $status, and a private DateTime $createdAt, the generated writer knows all of this upfront. There is no repeated reflection, no string parsing, and no lazy metadata building during runtime. Once OPCache compiles the generated file, the Hydrator is indistinguishable from hand-written PHP.
2. Eliminate Empty Pipelines
Most hydrators structure their work around a runtime pipeline. They maintain an array of callables—mutators, validators, transformers—and iterate over every value. Even when those arrays are empty, the foreach or array_reduce still executes. HydraType removes this entirely. During code generation it checks what a property actually requires. If the operation list is empty, it emits a plain assignment like $object->name = $data['name'];. The runtime never iterates anything, because the generator already did the thinking.
3. Use Closure Scoping to Kill Reflection Writes
Private properties are a classic headache for external mappers. The usual escape hatch is ReflectionProperty::setValue(), but that method carries a heavy penalty compared to direct property access. HydraType sidesteps it by using Closure::bind. The generated writer contains closures scoped to the target class, which lets them touch private and protected properties directly without reflection. The binding happens once during construction; after that, the closure runs at near-native speed.
4. Reuse the Writer
Some libraries instantiate a new strategy or reflection graph for every single object they hydrate. HydraType creates its writer once, then reuses that instance across thousands of objects. The per-object cost drops to the bare minimum of setting property values and returning the result.
The Numbers
On PHP 8.2, the difference is stark:
- HydraType: 267.9 ns
- Ocramius GeneratedHydrator: 307.9 ns
- Symfony PropertyNormalizer: 8,499.0 ns
- Valinor: 10,755.2 ns
Symfony PropertyNormalizer and Valinor are powerful tools, but they are generalists. PropertyNormalizer is part of a serializer component that handles format detection, normalizers, and deep object graphs. Valinor is rigorous about type trees and coercion. That power comes with cost. In this test, they were roughly thirty to forty times slower than HydraType. Even Ocramius GeneratedHydrator, which already uses code generation, sits slightly behind because of architectural differences around pipelines and property access.
Context matters. A single database query or an HTTP roundtrip dwarfs all of these numbers. If you are hydrating three objects after a query, chasing nanoseconds is a waste of energy. The gap becomes meaningful when you scale. A batch process hydrating fifty thousand records, or a queue consumer rebuilding objects from cache arrays, will feel the difference between 267 nanoseconds and ten microseconds. Multiply across fields and rows, and the faster mapper can shave seconds off a job without changing any business logic.
The Real Takeaway
The lesson here is not that every project needs a custom hydrator. It is that architecture choices compound. By generating code that includes only what you ask for, HydraType keeps plain properties fast while still offering an escape hatch for complex ones. Type conversion and nested objects exist when you need them, but they do not co-sign the performance check for every scalar string on the object.
Before you switch tools, profile. If hydration does not show up in your real-world traces, there is nothing to fix. But if you are pushing large datasets through generic mappers and watching CPU burn, remember that plain PHP assignment still exists. Sometimes the fastest code is simply the code you would have written yourself, generated once and then forgotten.
