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
W PHP 8.2 różnica jest uderzająca:
- HydraType: 267.9 ns
- Ocramius GeneratedHydrator: 307.9 ns
- Symfony PropertyNormalizer: 8 499,0 ns
- Valinor: 10 755,2 ns
Symfony PropertyNormalizer i Valinor to potężne narzędzia, ale są to rozwiązania ogólnego przeznaczenia. PropertyNormalizer jest częścią komponentu serializer, który obsługuje wykrywanie formatów, normalizatory i głębokie grafy obiektów. Valinor rygorystycznie podchodzi do drzew typów i wymuszania typów (coercion). Ta moc ma swoją cenę. W tym teście były one około trzydziestu do czterdziestu razy wolniejsze niż HydraType. Nawet Ocramius GeneratedHydrator, który już wykorzystuje generowanie kodu, pozostaje nieco w tyle ze względu na różnice architektoniczne w zakresie potoków (pipelines) i dostępu do właściwości.
Kontekst ma znaczenie. Pojedyncze zapytanie do bazy danych lub cykl HTTP (roundtrip) sprawiają, że wszystkie te liczby stają się nieistotne. Jeśli hydratujesz tylko trzy obiekty po zapytaniu, ściganie nanosekund jest stratą energii. Różnica staje się istotna przy skalowaniu. Proces wsadowy hydratujący pięćdziesiąt tysięcy rekordów lub konsument kolejki odbudowujący obiekty z tablic cache poczuje różnicę między 267 nanosekundami a dziesięcioma mikrosekundami. Pomnóż to przez liczbę pól i wierszy, a szybszy mapper może skrócić czas wykonywania zadania o kilka sekund, nie zmieniając przy tym żadnej logiki biznesowej.
Kluczowy wniosek
Lekcja płynąca z tego zestawienia nie polega na tym, że każdy projekt potrzebuje własnego hydratora. Chodzi o to, że wybory architektoniczne kumulują się. Dzięki generowaniu kodu, który zawiera tylko to, o co prosisz, HydraType utrzymuje wysoką wydajność zwykłych właściwości, oferując jednocześnie mechanizm obejścia (escape hatch) dla tych bardziej złożonych. Konwersja typów i zagnieżdżone obiekty są dostępne wtedy, gdy są potrzebne, ale nie obciążają wydajności przy każdym skalarnym ciągu znaków w obiekcie.
Zanim zmienisz narzędzie, wykonaj profilowanie. Jeśli hydratacja nie pojawia się w Twoich rzeczywistych śladach (traces), nie ma czego naprawiać. Jeśli jednak przepuszczasz duże zbiory danych przez generyczne mappery i obserwujesz wysokie zużycie CPU, pamiętaj, że zwykłe przypisanie w PHP wciąż istnieje. Czasami najszybszy kod to po prostu kod, który napisałbyś samodzielnie – wygenerowany raz i zapomniany.
