Skip to content

Bring the groups extension to 3.0 - #233

Open
DavidBadura wants to merge 2 commits into
3.0.xfrom
groups-property-filter
Open

DavidBadura wants to merge 2 commits into
3.0.xfrom
groups-property-filter

Conversation

@DavidBadura

@DavidBadura DavidBadura commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Closes #231. Builds on #232, which should be merged first, after that the diff only contains the groups.

In 2.1 the GroupsMiddleware filtered the metadata and passed it on in the stack. With the mapper chain of 3.0 a middleware can't change what the mapper reads anymore, so the groups are now applied by the mappers themselves. For that the core gets a small extension point, the PropertyFilter. It is registered with StackHydratorBuilder::addPropertyFilter() and returns the properties to map for a class and a context, or null for all of them. The GroupsExtension registers a GroupsPropertyFilter (the selection logic of the old middleware) next to the metadata enricher. The attribute and the context keys stay the same as in 2.1.

The ReflectionMapper only reads the properties the filter keeps and treats the fields of the others as missing. Their aliases from #237 are removed with them, so a kept property is still read from an alias while the alias of a filtered property is ignored. So groups break circular references again and uninitialized properties outside the groups are not read. The generated mappers get an extractFiltered() method which only reads the kept properties, also private ones of parent classes. The filter itself is applied by a FilteredMapper around the generated mapper, which the factory only adds for hydrators with a property filter. A first version checked the filter inline and made the generated extract 2.5 to 5% slower in interleaved benchmark runs, with the wrapper there is no measurable difference. The ReflectionMapper checks inline and is about 1 to 3% slower, I left that for now.

The groups tests of 2.1 run against both the reflection and the generated mappers. The docs page of 2.1 is back, and the extensions page explains the property filters. Custom mappers of a MapperFactory have to apply the filter as well, StackHydrator::propertyFilter() returns it.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Hello 👋

Here are the most recent benchmark results, compared against 3.0.x. Time is the mode over the iterations in the its column, with the relative standard deviation in brackets.

GeneratedHydrator

subject revs its memory base current diff
benchHydrate1Object 1000 5 1.855mb -21.23% 0.607μs (±1.29%) 0.611μs (±3.34%) +0.63%
benchExtract1Object 1000 5 1.855mb 0.00% 0.731μs (±1.26%) 0.751μs (±5.52%) +2.77%
benchHydrate1000Objects 3 5 1.855mb 0.00% 597.841μs (±2.27%) 597.456μs (±1.75%) -0.06%
benchExtract1000Objects 3 5 1.855mb 0.00% 359.729μs (±10.72%) 356.661μs (±2.66%) -0.85%

StackHydrator

subject revs its memory base current diff
benchHydrate1Object 1000 5 1.855mb 0.00% 1.117μs (±1.11%) 1.142μs (±1.52%) +2.20%
benchExtract1Object 1000 5 1.855mb 0.00% 1.497μs (±1.80%) 1.549μs (±1.25%) +3.43%
benchHydrate1000Objects 3 5 1.855mb 0.00% 1.107ms (±0.70%) 1.146ms (±0.55%) +3.56%
benchExtract1000Objects 3 5 1.855mb 0.00% 1.094ms (±0.66%) 1.134ms (±1.72%) +3.63%

StackHydratorWithLazy

subject revs its memory base current diff
benchHydrate1Object 1000 5 1.855mb 0.00% 0.258μs (±2.61%) 0.261μs (±2.27%) +0.83%
benchHydrate1ObjectTriggerInit 1000 5 1.855mb 0.00% 1.561μs (±0.49%) 1.571μs (±0.34%) +0.65%
benchHydrate1000Objects 3 5 1.855mb 0.00% 250.780μs (±1.24%) 248.523μs (±7.97%) -0.90%
benchHydrate1000ObjectsTriggerInit 3 5 1.855mb 0.00% 1.584ms (±0.93%) 1.564ms (±1.51%) -1.24%

StackHydratorWithCryptography

subject revs its memory base current diff
benchHydrate1Object 1000 5 1.855mb 0.00% 1.987μs (±2.14%) 2.072μs (±2.49%) +4.27%
benchExtract1Object 1000 5 1.855mb 0.00% 6.940μs (±2.29%) 6.824μs (±0.97%) -1.68%
benchHydrate1000Objects 3 5 1.855mb 0.00% 1.971ms (±0.57%) 2.010ms (±1.30%) +1.97%
benchExtract1000Objects 3 5 1.855mb 0.00% 6.416ms (±1.88%) 6.305ms (±1.24%) -1.73%

GeneratedHydratorWithCryptography

subject revs its memory base current diff
benchHydrate1Object 1000 5 1.855mb 0.00% 1.444μs (±2.51%) 1.496μs (±1.89%) +3.62%
benchExtract1Object 1000 5 1.855mb 0.00% 5.874μs (±1.38%) 5.804μs (±3.13%) -1.19%
benchHydrate1000Objects 3 5 1.855mb 0.00% 1.418ms (±0.98%) 1.420ms (±1.16%) +0.15%
benchExtract1000Objects 3 5 1.855mb 0.00% 5.192ms (±0.86%) 5.191ms (±1.73%) -0.01%

This comment gets updated every time a new commit comes in.

@DavidBadura
DavidBadura force-pushed the populate-without-defaults branch from b756ea4 to 625c869 Compare October 18, 2026 17:48
@DavidBadura
DavidBadura force-pushed the groups-property-filter branch from 66c312c to d39ced9 Compare October 18, 2026 17:48
@DavidBadura
DavidBadura changed the base branch from populate-without-defaults to 3.0.x October 18, 2026 17:48
Hydrating with OBJECT_TO_POPULATE set promoted properties to their default
value if their field was missing, so an update overwrote values which were not
part of the data. Defaults are now only used for new objects.

The generated mappers get an own populate path for classes with promoted
defaults, so the code for new objects stays the same.
The GroupsMiddleware of 2.1 passed filtered metadata down the stack, which the
mapper chain of 3.0 does not allow anymore. The groups are now applied by the
mappers themselves through a new PropertyFilter, which extensions register on
the builder. The GroupsExtension registers a GroupsPropertyFilter instead of
the middleware.

The ReflectionMapper only reads the properties the filter keeps and treats the
fields of the others as missing. The generated mappers get an extractFiltered()
method and a FilteredMapper which applies the filter around them. The factory
only adds it for hydrators with a property filter, so the generated code of all
other hydrators stays the same.
@DavidBadura
DavidBadura force-pushed the groups-property-filter branch from d39ced9 to bd7b2a0 Compare October 19, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant