Repository navigation
Generated transformers instead of a generated middleware - #214
Closed
DavidBadura wants to merge 2 commits into
Closed
DavidBadura wants to merge 2 commits into
DavidBadura wants to merge 2 commits into
Conversation
The TransformMiddleware now delegates to a ClassTransformer per class, which the hydrator gets from the transformer factories registered on the builder. Reflection stays the default, the generated extension only registers a factory which loads one generated transformer per class. When no other middleware runs for a class, the StackHydrator calls its transformer directly. This replaces the GeneratedHydrator decorator and the middleware slot, and also speeds up the reflection path. The name of a generated transformer contains the fingerprints of the class and its nested classes, so a changed class falls back to reflection instead of throwing. The code is generated on first use with autoGenerate, or ahead of time with the TransformerCompiler.
|
Hello 👋 Here are the most recent benchmark results, compared against GeneratedHydrator
StackHydrator
StackHydratorWithLazy
StackHydratorWithCryptography
GeneratedHydratorWithCryptography
This comment gets updated every time a new commit comes in. |
The TransformMiddleware only connected the stack to the transformer, so the hydrator now ends every stack with it on its own and gets a single transformer factory, which the builder composes from the registered factories. The CoreExtension no longer registers a middleware, a hydrator without middlewares is valid and a class which all middlewares skip is only transformed. A TransformMiddleware passed by the user still ends the stack like before. MissingMiddlewares, AllMiddlewaresSkipped and PRIORITY_TRANSFORM are deprecated since they can not happen or have no effect anymore.
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This reworks the generated code from #150 on top of a new extension point instead of a middleware plus decorator.
Transforming is no longer a middleware but the core of the
StackHydrator. Middlewares wrap around it, and at the end of the stack the hydrator maps the data with theClassTransformerof the class. The hydrator gets a singleClassTransformerFactory, the builder composes it from everything registered withStackHydratorBuilder::addTransformerFactory()in aChainTransformerFactory. The first transformer wins, theReflectionTransformeris the fallback. When no middleware runs for a class (none registered or all of them skip it), the hydrator calls the transformer directly without building the stack. That fast path is generic, so the reflection path profits from it as well.Because of that the
CoreExtensionno longer registers a middleware, a hydrator without middlewares is valid and a class which every middleware skips is simply transformed. ATransformMiddlewarepassed in by the user still ends the stack like before (middlewares after it were never called anyway).MissingMiddlewares,AllMiddlewaresSkippedandExtension::PRIORITY_TRANSFORMare deprecated since they can not happen or have no effect anymore.The generated extension is now just a transformer factory. It loads one generated class per hydrated class, nested classes are still inlined. The file name contains the fingerprints of the class and its nested classes, so a changed class has no matching code and simply falls back to reflection instead of throwing
OutdatedGeneratedMiddleware. Code is generated on first use withautoGenerate: true(dev), or ahead of time with theTransformerCompiler, which takes$builder->metadataFactory()so the code matches the metadata of all extensions:With that the class list is gone from the runtime config, and so are
GeneratedHydrator,GeneratedDecorator,GeneratedMiddlewareSlot,GeneratedMiddleware,HydratorNotSetandGeneratedMiddlewareNotLoaded.HydratorDecorator::decorate()only receives the hydrator to wrap, andbuild()works again with the generated extension. The root hydrator for nested objects from the base branch is kept as is, so tracing still sees nested objects and disables inlining.The extracted call stack is shared by all transformers of a hydrator, so circular references are still reported with the whole chain.
Benchmarks against the current state of #150 (PHP 8.5, opcache, 10 iterations, the runs were a bit noisy):
The generated path stays roughly where the decorator version was, extract is a few percent slower since the generated
extract()delegates to the method of the root class. Creating lazy proxies got a bit slower because of the extra lookup for the direct transformer at the start ofhydrate().