[DeepClone] Drop scoped mode — mangled-only is now the only shape#576
Merged
Conversation
715336f to
446a645
Compare
446a645 to
352b139
Compare
nicolas-grekas
added a commit
to symfony/symfony
that referenced
this pull request
Apr 16, 2026
This PR was merged into the 8.1 branch. Discussion ---------- [VarExporter] Bump to ext-deepclone v0.5.0 | Q | A | ------------- | --- | Branch? | 8.1 | Bug fix? | no | New feature? | no | Deprecations? | no | Issues | - | License | MIT See symfony/polyfill#576 Commits ------- 6b47b4d [VarExporter] Bump to ext-deepclone v0.5.0
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.
Mirrors symfony/php-ext-deepclone#15 on the polyfill side.
Summary
The scoped shape
[$className => ['prop' => $val]]and the flat mangled shape['prop' => $val, "\0Class\0priv" => $val2]were functionally equivalent; keeping both required a fast path, an intermediatescoped_varsgrouping for theMANGLED_VARSbranch, a double-pass write, and a footgun guard.deepclone_hydrate()now interprets$varsexclusively as the flat(array) $obj-shape. Key resolution is done via a cached per-classpropertyScopesindex (adapted fromLazyObjectRegistry::getPropertyScopes()insymfony/var-exporter) that maps every mangled-key shape — bare"foo","\0*\0foo","\0Class\0foo"— to[$declaringClass, $realName]in a single hash lookup. Bare names resolve to the most-derived declaring class and correctly target parent-private slots when unambiguous. A scan-based fast path hands$varsstraight to the$classhydrator when every key resolves to$class(the common flat-DTO shape), bypassing the intermediate grouping array.Perf (14-prop DTO, PHP 8.4, warm caches)
The flat-class fast path is now on par with raw
ReflectionProperty::setValue— essentially equal; the inheritance case still pays the per-scope closure dispatch (3 closures × scope-bound), which is orthogonal to this change.BC breaks
[$class => ['prop' => $val]]is no longer recognized. Callers migrate by flattening: bare names for public / protected / most-derived-private,"\0ParentClass\0prop"for parent-declared private.DEEPCLONE_HYDRATE_MANGLED_VARSconstant removed (the mode is now implicit). Drop it from$flagsarguments.DEEPCLONE_HYDRATE_PRESERVE_REFSmoves from1 << 3to1 << 2(filling the slot vacated byMANGLED_VARS). Symbolic references via the constant name are unaffected.$classor a parent raisesValueError("invalid mangled key")instead of silently creating a dynamic property (matching the ext's "not a parent" rejection).Tests
*MatchesUnserializetests cover the equivalent behavior in the flat shape.testHydrateBareNameReachesParentPrivatecovering bare-name-to-parent-private resolution via thepropertyScopesindex (GP → P → C chain).Follow-up (out of scope for this PR)
symfony/var-exporter'sHydrator::hydrate()/Instantiator::instantiate()still need a separate PR to keep their$scopedVars/$mangledVarsBC parameters by translating to the flat shape internally.Test plan