Add MapperProvider::pregenerate() — native mapper pre-generation - #127
Open
janedbal wants to merge 1 commit into
Open
Add MapperProvider::pregenerate() — native mapper pre-generation#127janedbal wants to merge 1 commit into
janedbal wants to merge 1 commit into
Conversation
…compilation
Consumers pre-generating mappers (e.g. a console command compiling
mappers for every API input class at deploy time) so far had to
discover delegated classes themselves by reflecting over provider
internals, which is fragile and opaque to static analysis.
The ground truth of what needs pre-generation is decided during
compilation: AbstractDelegateMapperCompiler either emits a
$provider->get*Mapper(X::class) call (a runtime dependency) or inlines
the inner mapper. PhpCodeBuilder now records the class names at those
two emission points, and MapperProvider::pregenerate() runs a
compile-as-you-discover fixpoint over them: compile the seeds, enqueue
recorded delegations, repeat until nothing new appears.
Dependencies are tracked per direction, so only mappers actually
reachable at runtime are generated. Classes covered by runtime
factories registered via register{Input,Output}Factory() are skipped.
Existing mapper files are not overwritten, but are recompiled in
memory so discovery also works on a warm cache.
Co-Authored-By: Claude Code
janedbal
marked this pull request as ready for review
August 5, 2026 15:01
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.
Alternative to #126, based on the review feedback there — instead of exposing the provider/compiler graph for consumers to traverse, the library records delegated classes where the truth is decided and offers pre-generation natively.
Motivation
Consumers pre-generating mappers at deploy time (e.g. a console command compiling mappers for every API input class) so far had to discover transitively referenced classes themselves — by reflecting over provider internals (
get_mangled_object_vars()over plainobject), which is fragile and reported byshipmonk/dead-code-detectoras an unknown read over unknown type.Design: record at the emission point
A pre-generation dependency is "a class the generated code will call
$provider->get{Input,Output}Mapper()for at runtime". That is decided during compilation, at a single funnel:AbstractDelegateMapperCompilereither emits a provider call (runtime dependency) or inlines the inner mapper as aCallbackMappermethod (no standalone dependency — and its own delegates are emitted, hence recorded, within the same compilation).PhpCodeBuildernow records class names at those two emission points — ground truth by construction, nothing to keep in sync, no interface churn.MapperProvider::pregenerate(iterable $classNames): list<class-string>then runs a compile-as-you-discover fixpoint: compile the seeds, enqueue recorded delegations, repeat until nothing new appears. Each iteration is work that had to happen anyway, discovery is a free byproduct.Properties:
register{Input,Output}Factory()are skipped in that directionConsumer story
The application command reduces to seed discovery + one call:
If this direction is preferred, #126 (
CompositeMapperCompilerProvider) becomes unnecessary for the pre-generation use case and can be closed — a graph-traversal API would only be worth keeping for hypothetical static analyses that must not compile.Co-Authored-By: Claude Code
Drafted by Claude