patchlevel/hydrator
Hydrate objects to arrays and back without calling constructors. Supports final and readonly classes, nested structures, enums, dates, lazy hydration, field renaming, and metadata caching with a developer-friendly, fully typed API.
Architecture fit
patchlevel/hydrator is a framework-agnostic PHP serialization/hydration library, not a Laravel package. There is no service provider, config file, facade, or Eloquent integration — it operates purely on plain PHP objects. For a Laravel codebase this means it occupies the same architectural slot as spatie/laravel-data, Symfony Serializer, or hand-rolled fromArray()/toArray() methods on DTOs. Its distinguishing capabilities are narrow but real:
final, readonly, and promoted-property DTOs viable without factory boilerplate, which fits well with the modern Laravel style of immutable value objects at the boundaries (Form Requests → DTO → Action → Resource).rename/exclude) gives you a stable external contract decoupled from internal property names — the main reason teams reach for a serializer instead of json_encode.exclude attributes piling up. This is directly relevant to our boundary-DTO architecture.Critically, it does not do validation, rule inference, Eloquent model hydration, query-builder integration, or resource transformation. spatie/laravel-data bundles all of those. Adopting hydrator means accepting a narrower tool and keeping validation in Form Requests — which is a legitimate architecture, just a deliberate one. Groups do not change this: groups select which fields appear, not whether values are valid.
Integration feasibility
Low effort, high reversibility. composer require patchlevel/hydrator, bind a configured Hydrator instance as a singleton in a small service provider, inject it into actions/controllers. No migration, no schema change, no framework coupling. A pilot can be stood up in under a day. The only hard prerequisite is PHP ≥ 8.1 for the core (8.4 if we want lazy proxies), which we need to confirm against our runtime matrix. 2.1.0 adds no new runtime prerequisite.
Technical risk The dominant risk remains adoption and bus factor, not code quality:
StackHydratorTest timeout counter). That is a quiet, disciplined release — it partially offsets the "expect churn before 3.x" concern without eliminating it. The 2.x line now looks more stable than the 2.0.x-era release cadence implied.__construct also skips invariant enforcement. Any object whose constructor validates (e.g., assert($amount >= 0)) becomes hydratable into an invalid state from untrusted array input. 2.1.0 gives us the tool to mitigate this centrally (assertion middleware) rather than per-DTO, but the design-level risk remains and must be handled by policy: hydrate only from trusted, typed sources, never directly from raw request payloads without validation upstream.Key questions
spatie/laravel-data is the lower-risk answer and this evaluation should end here.Serializer interface.)Verdict: Technically sound, strategically still unproven but improving. 2.1.0 is a low-risk, additive release that materially strengthens the case (groups, middleware) without altering the core risk profile (bus factor, constructor-bypass invariants, crypto-shredding). Approve for a scoped pilot behind an internal abstraction; do not standardize org-wide, and do not adopt with crypto-shredding enabled in v1. Widen the pin from 2.0.x to ^2.1.
Stack fit
Good fit for a Laravel app that has already committed to immutable DTOs at HTTP/queue boundaries. Poor fit for a team that leans on spatie/laravel-data for validation-plus-hydration, or on Eloquent $casts/toArray() for model serialization. The library is additive: it replaces hand-written fromArray()/toArray() pairs on ~10–50 DTOs, not the whole persistence layer. Groups make it a better fit than before for teams that currently maintain multiple near-duplicate DTO classes purely to vary payload shape by context.
Recommended containment pattern:
App\Support\Serialization\Serializer (interface: toArray(object, ?array $groups), fromArray(class, array))
└── HydratorSerializer (wraps patchlevel/hydrator)
This one interface is the whole risk-mitigation strategy. It makes the dependency swappable, gives us one place to add logging on hydration failures, and prevents Patchlevel\Hydrator\* types from leaking into domain code. Update: keep the Serializer interface even though groups and middleware now cover much of what it did — it is the exit hatch, not a duplication. Do not let the interface grow into a re-implementation of the library's attribute system.
Middleware registration should live entirely inside HydratorSerializer's construction (or a dedicated SerializationServiceProvider), so the middleware list is reviewable in one diffable place rather than scattered across DTO attributes.
Migration path Incremental and opt-in, by design:
readonly final with renamed fields. Then add one group across two contexts and one assertion middleware, and confirm the skip attribute behaves as expected. Confirm behavior on our PHP version.validated(). Groups are not a validation mechanism and must not be treated as one.Migration off an existing solution (e.g., laravel-data) is a rewrite of attribute annotations and any magic-property access, and a feature regression (no validation, no rule inference, no model integration). Do not migrate existing working DTOs to hydrator for its own sake; apply it only to new code and to DTOs that are currently hand-rolled. Groups reduce the future case for laravel-data's partial/contextual serialization; they do not retroactively justify a migration.
Compatibility
composer update with no code change required; groups and middleware are opt-in. This is exactly the kind of release we wanted to see after pinning.^2.1 with lockfile, treat minor bumps as reviewable-not-automatic, majors as planned work. The 2.0 → 2.1 delta supports loosening from the original patch-only pin.spatie/laravel-data, Symfony Serializer, or Laravel's container; they can coexist. Ensure we do not end up with two serialization strategies on the same DTO, which produces confusing "which attribute wins" bugs.{"_v": 1, ...}) for anything durable — queues, caches, event stores.Sequencing
?array $groups (week 1)Maintenance
Ongoing maintenance is minimal — small API surface, no configuration, no service provider to keep in sync with Laravel majors. 2.1.0 did not change this. The cost that does accrue is dependency governance: a low-adoption, small-team package needs an owner on our side, a pinning policy (lockfile-pinned ^2.1, minors reviewable), and an upgrade checklist per release. Budget roughly a day per major version bump; minors on the evidence of 2.1.0 should be well under an hour.
Two hidden maintenance items now:
admin, admin_v2, internal, webhook) and middleware stacks accumulate per-DTO exceptions until nobody can predict a payload shape from reading the class. Assign ownership: a single SerializationGroups enum-like constant set and a central middleware registry, reviewed like a public API.Support
Scaling
instanceof, ===, serialization of proxies). Defer to a second phase. Note that groups are the cheaper lever for reducing payload size before we reach for proxies.Failure modes Ranked by likelihood × impact:
| Failure | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Invalid object constructed via bypass (invariants skipped) | Medium | High | Hydrate only post-validation, from trusted producers; enforce invariants via non-skippable assertion middleware (now viable in 2.1.0) rather than per-DTO code |
| Payload schema drift after property rename or group reassignment → failed/partial hydration | High over time | High | Version all persisted payloads; treat array shape and group membership as a contract; contract tests per DTO per context; decode-failure alerting |
| DTO silently skips a security-relevant middleware | Low–Medium | High | Deny-list skippable middleware; add a CI/architecture test asserting critical middleware applies to all serialized DTOs |
| Group misconfiguration → field leaked to a public payload or missing from a consumer | Medium | High | Explicit per-context contract tests (assert exact key set, not just "hydrates without error"); review group changes as API changes |
| Uninitialized/missing property silently defaulted or throwing mid-deserialization | Medium | Medium | Explicit contract tests per DTO; fail-fast on unknown keys in pilot |
Breaking change in a 2.x → 3.x bump |
Medium | Medium | Lockfile pin to ^2.1; read changelog; containment interface limits blast radius |
| Upstream abandonment | Low (near term) | High | Containment interface; ability to fork or swap to Symfony Serializer in ~1 sprint |
| Crypto-shredding misuse — key loss = irreversible data loss; wrong key scope = failed erasure claim | Low (if unused) | Critical | Do not enable in v1; security + legal review; KMS-backed key custody; documented destruction audit trail |
| Two serialization strategies coexisting → inconsistent API output | Medium | Medium | One DTO = one strategy; lint/review rule |
Ramp-up
Fast for a competent Laravel engineer — the API is small (hydrate/extract, a handful of attributes, Normalizer registration), docs are current and published, and behavior is intuitive to anyone who has used a serializer. Expect half a day to productivity, one to two days to fluency with attributes, nested normalizers, and guesser registration. No framework-specific knowledge required. 2.1.0 adds groups and middleware, both familiar concepts to anyone who has used Symfony Serializer or a Laravel middleware stack — budget one additional half-day for the team to agree on group semantics and the middleware registry.
Compensating cost: because the package is obscure, onboarding material must be internal. Write a one-page "how we use hydrator here" doc with 3 examples, the containment-interface rule, the group vocabulary, and the non-skippable middleware policy — otherwise new hires and AI tooling will produce subtly wrong code (most likely 2.0-era code without groups), and review becomes the enforcement mechanism.
TPM recommendation: Fund a one-sprint, one-engineer pilot in a queue/cache path behind a Serializer interface, with payload versioning, the 2.1.0 features explicitly in scope (one grouped DTO across two contexts, one non-skippable assertion middleware), and crypto-shredding disabled. Gate broader adoption on the pilot showing a measurable reduction in DTO boilerplate with zero hydration failures and a group vocabulary a reviewer can predict from the class definition. Revisit the make-vs-buy decision against spatie/laravel-data at that checkpoint — for most Laravel teams the incumbent still wins on adoption and validation bundling, but 2.1.0 narrows the gap materially: constructor bypass on readonly final DTOs, pluggable normalizers, and now group-based contextual serialization plus a middleware interception point is a genuinely better-shaped tool for a team with a disciplined boundary-DTO architecture.
How can I help you explore Laravel packages today?