Skip to content

Pass the hydrator to normalizers via the context - #215

Merged
DavidBadura merged 1 commit into
3.0.xfrom
hydrator-context
Oct 6, 2026
Merged

DavidBadura merged 1 commit into
3.0.xfrom
hydrator-context

Conversation

@DavidBadura

Copy link
Copy Markdown
Member

Normalizers which hydrate or extract nested objects no longer get the hydrator injected, they read it from the context under the new key Hydrator::HYDRATOR.

Until now the StackHydrator called setHydrator() on every HydratorAwareNormalizer of a class when its metadata was loaded. That mutated the normalizers inside the (possibly cached and shared) metadata, and a hydrator wrapping the StackHydrator, like a tracing decorator, never saw the nested objects because the normalizers kept pointing to the inner hydrator. In #150 that needed an extra root hydrator mechanism to rewire the normalizers after the decorators were applied.

Now every hydrator sets $context[Hydrator::HYDRATOR] ??= $this when it is called. The outermost hydrator wins automatically, so a wrapper only has to add that one line to see nested objects, and the metadata stays untouched.

This is a BC break for 3.0:

  • HydratorAwareNormalizer is removed, custom normalizers read the hydrator from the context instead.
  • ObjectNormalizer, ObjectMapNormalizer, ArrayNormalizer and ArrayShapeNormalizer lose setHydrator().
  • The context passed to middlewares, normalizers, upcasters and lifecycle hooks now contains the hydrator.
  • The serialized form of the normalizers changed, so the metadata cache has to be cleared.

As a small side effect ObjectNormalizer and ObjectMapNormalizer only throw MissingHydrator when they really need the hydrator, null values work without one.

The changes are documented in a new docs/UPGRADE-3.0.md.

The StackHydrator injected itself into every HydratorAwareNormalizer of the
loaded metadata. That mutated shared, cached metadata and made it hard for a
hydrator which wraps the StackHydrator to see nested objects. The hydrator is
now passed with every call in the context under Hydrator::HYDRATOR. Each
hydrator only sets it if it is missing, so the outermost one wins.

HydratorAwareNormalizer and the setHydrator() methods of the built-in
normalizers are removed.
@DavidBadura DavidBadura added this to the 3.0.0 milestone Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Hello 👋

here is the most recent benchmark result:

StackHydratorBench
==================

+-------------------------------+-------------------------------------+------------------------------+
|                               | time (kde mode)                     | memory                       |
+-------------------------------+------------------+------------------+-----------------+------------+
| subject                       | Tag: <current>   | Tag: base        | Tag: <current>  | Tag: base  |
+-------------------------------+------------------+------------------+-----------------+------------+
| benchHydrate1Object ()        | 4.000μs (±0.00%) | 3.600μs (±0.00%) | 2.772mb         | 3.296mb    |
| benchExtract1Object ()        | 4.000μs (±0.00%) | 4.000μs (±0.00%) | 2.772mb         | 3.296mb    |
| benchHydrate1000Objects ()    | 2.939ms (±0.00%) | 3.111ms (±0.00%) | 3.052mb         | 3.500mb    |
| benchExtract1000Objects ()    | 2.723ms (±0.00%) | 2.690ms (±0.00%) | 3.019mb         | 3.490mb    |
| benchHydrate1000000Objects () | 1.728s (±0.00%)  | 1.493s (±0.00%)  | 3.052mb         | 3.500mb    |
| benchExtract1000000Objects () | 1.727s (±0.00%)  | 1.524s (±0.00%)  | 3.019mb         | 3.009mb    |
+-------------------------------+------------------+------------------+-----------------+------------+

HydratorWithLazyBench
=====================

+------------------------------------------+-----------------------------------------+------------------------------+
|                                          | time (kde mode)                         | memory                       |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| subject                                  | Tag: <current>     | Tag: base          | Tag: <current>  | Tag: base  |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| benchHydrate1Object ()                   | 1.000μs (±0.00%)   | 0.800μs (±0.00%)   | 2.772mb         | 2.772mb    |
| benchHydrate1ObjectTriggerInit ()        | 5.000μs (±0.00%)   | 4.400μs (±0.00%)   | 2.772mb         | 2.772mb    |
| benchHydrate1000Objects ()               | 531.667μs (±0.00%) | 449.333μs (±0.00%) | 2.830mb         | 2.817mb    |
| benchHydrate1000ObjectsTriggerInit ()    | 3.731ms (±0.00%)   | 4.081ms (±0.00%)   | 2.865mb         | 2.931mb    |
| benchHydrate1000000Objects ()            | 359.651ms (±0.00%) | 291.802ms (±0.00%) | 2.830mb         | 2.817mb    |
| benchHydrate1000000ObjectsTriggerInit () | 2.412s (±0.00%)    | 2.186s (±0.00%)    | 2.865mb         | 2.931mb    |
+------------------------------------------+--------------------+--------------------+-----------------+------------+

StackHydratorWithCryptographyBench
==================================

+-------------------------------+---------------------------------------+------------------------------+
|                               | time (kde mode)                       | memory                       |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base         | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| benchHydrate1Object ()        | 7.600μs (±0.00%)  | 7.200μs (±0.00%)  | 2.820mb         | 2.820mb    |
| benchExtract1Object ()        | 16.600μs (±0.00%) | 79.200μs (±0.00%) | 2.820mb         | 2.836mb    |
| benchHydrate1000Objects ()    | 4.671ms (±0.00%)  | 4.177ms (±0.00%)  | 3.128mb         | 3.115mb    |
| benchExtract1000Objects ()    | 9.641ms (±0.00%)  | 8.969ms (±0.00%)  | 3.201mb         | 3.203mb    |
| benchHydrate1000000Objects () | 2.558s (±0.00%)   | 2.283s (±0.00%)   | 3.128mb         | 3.115mb    |
| benchExtract1000000Objects () | 6.879s (±0.00%)   | 6.694s (±0.00%)   | 3.201mb         | 3.201mb    |
+-------------------------------+-------------------+-------------------+-----------------+------------+

HydratorBench
=============

+-------------------------------+-------------------------------------+------------------------------+
|                               | time (kde mode)                     | memory                       |
+-------------------------------+------------------+------------------+-----------------+------------+
| subject                       | Tag: <current>   | Tag: base        | Tag: <current>  | Tag: base  |
+-------------------------------+------------------+------------------+-----------------+------------+
| benchHydrate1Object ()        | 4.000μs (±0.00%) | 3.400μs (±0.00%) | 2.772mb         | 2.772mb    |
| benchExtract1Object ()        | 4.000μs (±0.00%) | 4.000μs (±0.00%) | 2.772mb         | 2.772mb    |
| benchHydrate1000Objects ()    | 2.916ms (±0.00%) | 2.603ms (±0.00%) | 3.052mb         | 3.019mb    |
| benchExtract1000Objects ()    | 2.738ms (±0.00%) | 2.489ms (±0.00%) | 3.019mb         | 3.008mb    |
| benchHydrate1000000Objects () | 1.689s (±0.00%)  | 1.447s (±0.00%)  | 3.052mb         | 3.019mb    |
| benchExtract1000000Objects () | 1.717s (±0.00%)  | 1.500s (±0.00%)  | 3.019mb         | 3.009mb    |
+-------------------------------+------------------+------------------+-----------------+------------+

HydratorWithCryptographyBench
=============================

+-------------------------------+---------------------------------------+------------------------------+
|                               | time (kde mode)                       | memory                       |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base         | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| benchHydrate1Object ()        | 7.800μs (±0.00%)  | 7.600μs (±0.00%)  | 2.820mb         | 2.820mb    |
| benchExtract1Object ()        | 17.400μs (±0.00%) | 83.800μs (±0.00%) | 2.820mb         | 2.836mb    |
| benchHydrate1000Objects ()    | 4.650ms (±0.00%)  | 4.207ms (±0.00%)  | 3.128mb         | 3.114mb    |
| benchExtract1000Objects ()    | 9.724ms (±0.00%)  | 8.933ms (±0.00%)  | 3.201mb         | 3.203mb    |
| benchHydrate1000000Objects () | 2.628s (±0.00%)   | 2.282s (±0.00%)   | 3.128mb         | 3.115mb    |
| benchExtract1000000Objects () | 6.905s (±0.00%)   | 6.698s (±0.00%)   | 3.201mb         | 3.201mb    |
+-------------------------------+-------------------+-------------------+-----------------+------------+

This comment gets update everytime a new commit comes in!

@DavidBadura
DavidBadura merged commit 5c41124 into 3.0.x Oct 6, 2026
16 of 17 checks passed
@DavidBadura
DavidBadura deleted the hydrator-context branch October 6, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants