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
Unter PHP 8.2 ist der Unterschied eklatant:
- HydraType: 267,9 ns
- Ocramius GeneratedHydrator: 307,9 ns
- Symfony PropertyNormalizer: 8.499,0 ns
- Valinor: 10.755,2 ns
Symfony PropertyNormalizer und Valinor sind leistungsstarke Werkzeuge, aber sie sind Generalisten. Der PropertyNormalizer ist Teil einer Serializer-Komponente, die Format-Erkennung, Normalizer und tiefe Objektgraphen verarbeitet. Valinor ist sehr strikt bei Typbäumen und Coercion. Diese Leistungsfähigkeit hat ihren Preis. In diesem Test waren sie etwa dreißig- bis vierzigmal langsamer als HydraType. Selbst der Ocramius GeneratedHydrator, der bereits Code-Generierung nutzt, liegt aufgrund architektonischer Unterschiede bei Pipelines und dem Eigenschaftszugriff leicht zurück.
Der Kontext spielt eine Rolle. Eine einzige Datenbankabfrage oder ein HTTP-Roundtrip lassen all diese Zahlen verblassen. Wenn Sie nach einer Abfrage nur drei Objekte hydratisieren, ist das Jagen von Nanosekunden reine Energieverschwendung. Die Lücke wird erst bei der Skalierung bedeutsam. Ein Batch-Prozess, der fünfzigtausend Datensätze hydratisiert, oder ein Queue-Consumer, der Objekte aus Cache-Arrays wieder aufbaut, wird den Unterschied zwischen 267 Nanosekunden und zehn Mikrosekunden spüren. Multipliziert über Felder und Zeilen hinweg kann der schnellere Mapper Sekunden von einem Job einsparen, ohne die Geschäftslogik zu ändern.
Das eigentliche Fazit
Die Lehre daraus ist nicht, dass jedes Projekt einen eigenen Hydrator benötigt. Sondern dass sich Architektur-Entscheidungen summieren. Indem HydraType nur den Code generiert, den man tatsächlich anfordert, bleiben einfache Eigenschaften schnell, während gleichzeitig ein Ausweg für komplexe Fälle geboten wird. Typkonvertierung und verschachtelte Objekte sind vorhanden, wenn man sie benötigt, aber sie müssen nicht bei jedem skalaren String des Objekts die Performance-Prüfung mit unterschreiben.
Bevor Sie das Werkzeug wechseln, führen Sie ein Profiling durch. Wenn die Hydratisierung in Ihren realen Traces nicht auftaucht, gibt es nichts zu optimieren. Aber wenn Sie große Datensätze durch generische Mapper schleusen und dabei die CPU glühen sehen, denken Sie daran, dass einfache PHP-Zuweisungen immer noch existieren. Manchmal ist der schnellste Code einfach der Code, den Sie selbst geschrieben hätten – einmal generiert und dann vergessen.
