Гидратация объектов кажется решенной проблемой, пока вы не начнете ее измерять. Вы извлекаете строку из базы данных, мапите массив в объект и идете дальше. Большинство из нас относится к этому как к «сантехнике» — невидимому, неброскому процессу, который по умолчанию считается достаточно быстрым. Но однажды, профилируя пакетную задачу или воркер очереди, вы замечаете, что неожиданно большая часть процессорного времени уходит на маппер. Именно это осознание привело к созданию HydraType — гидратора, построенного вокруг одного упрямого правила: класс никогда не должен платить за функции, которые не используют его свойства.
Если поле — это обычная строка, не требующая никакой трансформации, сгенерированный код должен выглядеть так, будто его написал человек вручную. Прямое присваивание. Никаких циклов, конвейеров или рефлексии.
Сколько на самом деле стоит гидратация
На первый взгляд, превращение ['name' => 'Alice', 'age' => 30] в объект User кажется тривиальным. Проблемы начинаются, когда вам нужна гибкость. Большинство универсальных гидраторов полагаются на рефлексию для инспекции классов во время выполнения. Они строят карты метаданных, согласовывают приведение типов и разрешают вложенные графы объектов. Они также обожают конвейеры (pipelines). Каждое значение проходит через последовательность мутаторов, утверждений (assertions) и трансформеров, даже если эта последовательность пуста. Сама структура цикла создает накладные расходы.
Извлеките пару десятков записей, и вы ничего не заметите. Но современные PHP-приложения регулярно обрабатывают тысячи сообщений из очередей, импортируют массивные CSV-наборы или гидратируют глубокие наборы результатов из Elasticsearch. В таких сценариях маппер, тратящий даже несколько микросекунд на каждое поле, становится реальным «узким местом». Тысяча объектов, имеющих по восемь полей, превращается в налог, который вы ощущаете на себе.
Философия нулевых накладных расходов
Центральная идея HydraType — изоляция функций. Преобразование типов, гидратация вложенных объектов и проверки (assertions) поддерживаются, но ни одна из них не является обязательной. Если свойству требуется лишь прямое копирование из массива в объект, сгенерированный райтер (writer) содержит именно это. Здесь нет общего конвейера, который заставлял бы обычное скалярное поле стоять в очереди за валидаторами, которые ему не нужны.
Это сложнее, чем кажется. Многие библиотеки используют единый путь выполнения для каждого поля, чтобы поддерживать компактность и единообразие кодовой базы. HydraType идет от обратного. Он генерирует уникальный PHP-класс для каждого целевого DTO, создавая именно те операции, которые требуются конкретному классу. Сложность становится опциональной, а не навязанным налогом на каждое свойство.
За счет чего достигается скорость
Четыре конкретных решения отличают HydraType как от универсальных инструментов на базе рефлексии, так и от других подходов с генерацией кода.
1. Генерируйте код один раз, запускайте вечно
HydraType инспектирует ваш целевой класс всего один раз, а затем записывает специализированный PHP-класс, адаптированный под его точную структуру. Если ваш DTO содержит публичную строку $name, целое число $status и приватный DateTime $createdAt, сгенерированный райтер знает об этом заранее. Нет повторной рефлексии, парсинга строк или ленивого построения метаданных во время выполнения. Как только OPCache компилирует сгенерированный файл, гидратор становится неотличим от написанного вручную PHP-кода.
2. Устранение пустых конвейеров
Большинство гидраторов строят свою работу вокруг конвейера (pipeline) времени выполнения. Они поддерживают массив вызываемых объектов (callables) — мутаторов, валидаторов, трансформеров — и итерируют по каждому значению. Даже когда эти массивы пусты, foreach или array_reduce все равно выполняются. HydraType полностью устраняет это. Во время генерации кода он проверяет, что на самом деле требуется свойству. Если список операций пуст, он выдает простое присваивание, например $object->name = $data['name'];. Во время выполнения ничего не итерируется, потому что генератор уже все продумал.
3. Использование области видимости замыканий для устранения рефлексии при записи
Приватные свойства — классическая головная боль для внешних мапперов. Обычный выход из ситуации — ReflectionProperty::setValue(), но этот метод несет тяжелые накладные расходы по сравнению с прямым доступом к свойствам. HydraType обходит это, используя Closure::bind. Сгенерированный райтер содержит замыкания (closures), область видимости которых ограничена целевым классом, что позволяет им напрямую обращаться к приватным и защищенным свойствам без рефлексии. Привязка (binding) происходит один раз при конструировании; после этого замыкание выполняется почти с нативной скоростью.
4. Повторное использование райтера
Некоторые библиотеки создают новый экземпляр стратегии или граф рефлексии для каждого объекта, который они гидратируют. HydraType создает свой райтер один раз, а затем использует этот экземпляр для тысяч объектов. Затраты на один объект сводятся к абсолютному минимуму: установке значений свойств и возврату результата.
Цифры
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.
