netmex/hydrator-bundle
Netmex Hydrator Bundle brings configurable object hydration to Symfony applications, converting arrays, request data, or API payloads into typed DTOs and entities. It offers reusable hydrators, attribute-based mapping, and extension points for custom transformations with minimal boilerplate.
Install / upgrade
composer require netmex/hydrator-bundle ^2.0
This is a major release — treat it as a new integration, not a patch. If you're coming from 1.x, branch first and read the README usage section and the examples before touching existing mapping definitions; the notes explicitly warn about breaking changes to the mapping API and internal architecture.
Where to look first
README.md → Usage section (the release notes point here for the 2.0 API shape).First use case — flat DTO from a request payload
// 1. Define a target object (plain PHP, typed properties)
final class UserDto
{
public string $name;
public string $email;
}
// 2. Define the mapping (see README for the exact 2.0 API surface)
// 3. Hydrate
$user = $hydrator->hydrate(UserDto::class, $request->validated());
Then graduate to the two headline features in this release: nested objects (one DTO containing another) and collections (a list of arrays → a list of hydrated objects). Those cover ~90% of real hydration work.
Request → DTO at the boundary
The highest-value pattern: validate with a FormRequest, then hydrate $request->validated() into a typed DTO/command object instead of passing arrays around. Keeps controllers thin and makes downstream code statically analysable.
Nested hydration for composite payloads
Order containing Customer containing Address, or a User with a Profile. Map the outer object once and let the nested mapping handle the sub-object — 2.0 has dedicated processing for nested mappings and their input data, so you no longer have to hand-roll the recursion.
Recursive mapping for tree structures Categories, comment threads, org charts, menu trees. Define the mapping for one node and let the recursive support walk the children. Pair this with a depth limit or explicit termination in your input contract — see Gotchas.
Collection mapping for list endpoints and imports
$hydrator->hydrateCollection(ItemDto::class, $rows) style usage: array of arrays in, array of objects out. Ideal for:
index endpoints returning mapped resourcesConfigurable mapping options as a policy layer 2.0 introduces options that customize mapping/hydration behaviour. Treat these as your policy knobs — strict-vs-lenient handling of unexpected keys, null/absent field behaviour, coercion rules. Set them once in config per mapping context (e.g. a lenient "ingest" mapping and a strict "internal" mapping) rather than sprinkling conditionals in application code.
Use the orchestration/processing/hydration split as your extension point Because responsibilities are now separated, customization should be a matter of swapping or decorating one stage — a custom processor for a special input shape, a custom hydrator for value objects — instead of forking the whole mapper. If you have custom logic from 1.x, re-home it into the correct stage rather than re-implementing the pipeline.
Testing pattern Hydration is pure input→object, so test mappings directly: build the array fixture, hydrate, assert on the object graph. For collections, assert count and the first/last item — off-by-one and dropped-item bugs hide in the middle.
Assume 1.x examples are stale. The notes flag breaking changes to both the internal architecture and the mapping API. Any blog post, Stack Overflow answer, or snippet in your codebase written against 1.x is suspect. Port mappings deliberately and diff behaviour on a real fixture set.
Clear the container cache after upgrading. Service definitions moved into dedicated config files in 2.0. If you have decorated, overridden, or referenced services by ID, verify those IDs still exist — stale container caches will mask the breakage with confusing "service not found" errors.
Recursive mapping needs a cycle guard. Recursive support will happily recurse on self-referencing input (a category whose parent is itself, or an accidentally circular structure). Validate or cap depth at the input boundary; don't rely on the hydrator to detect cycles.
Nested failures are hard to trace — isolate them. Deep mappings fail with errors that point at the outer object. When debugging, hydrate the innermost nested structure alone first, confirm the mapping, then work outward. Hydrating a collection one element at a time via a loop is the fastest way to find the offending row.
Collection mapping expects a list, not a lazy sequence. If you're feeding it a Collection, generator, or paginator result, materialise to an array first. Empty-collection and null-vs-[] handling is worth an explicit test — the two often take different paths.
Strict typing means no more loose coercion. The release tightens type hints. Inputs that "worked" under loose coercion in 1.x (numeric strings into int, "1" into bool) may now fail. Normalise input types before hydration, or handle it in your mapping options.
Options are the answer before you write custom code. Before adding a bespoke hydrator for a special case, check whether a 2.0 mapping option covers it. Conversely, don't pile options on to compensate for a mapping that's structurally wrong — fix the mapping.
Watch per-item cost on large collections. Hydration is reflection-heavy; mapping metadata is typically re-resolved per hydration. For imports and bulk jobs, benchmark at realistic volume before moving it into a synchronous request path — and chunk large inputs.
Framework wiring caveat. HydratorBundle is a Symfony bundle, so its service configuration assumes a Symfony container. In a Laravel app you won't get autoconfiguration from the bundle — plan on wiring the hydrator services manually in a service provider (or using a bridge) and pin the version, since the container/config layout just changed in a major release.
Pin it. "netmex/hydrator-bundle": "^2.0" in composer.json, and read the diff of the service configuration files before any 2.x minor bump if you have custom service definitions.
How can I help you explore Laravel packages today?