Skip to content

[DeepClone] Drop scoped mode — mangled-only is now the only shape#576

Merged
nicolas-grekas merged 1 commit into
1.xfrom
drop-scoped-mode
Apr 16, 2026
Merged

[DeepClone] Drop scoped mode — mangled-only is now the only shape#576
nicolas-grekas merged 1 commit into
1.xfrom
drop-scoped-mode

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Member

@nicolas-grekas nicolas-grekas commented Apr 15, 2026

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 intermediate scoped_vars grouping for the MANGLED_VARS branch, a double-pass write, and a footgun guard.

deepclone_hydrate() now interprets $vars exclusively as the flat (array) $obj-shape. Key resolution is done via a cached per-class propertyScopes index (adapted from LazyObjectRegistry::getPropertyScopes() in symfony/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 $vars straight to the $class hydrator 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)

shape before after vs Reflection
flat class 3,500 ns 1,930 ns 1.10×
3-level inheritance 4,300 ns 3,760 ns 2.27×

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

  • Scoped-shape input [$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_VARS constant removed (the mode is now implicit). Drop it from $flags arguments.
  • DEEPCLONE_HYDRATE_PRESERVE_REFS moves from 1 << 3 to 1 << 2 (filling the slot vacated by MANGLED_VARS). Symbolic references via the constant name are unaffected.
  • The "NUL-prefixed scope key → footgun ValueError" guard is gone — NUL-prefix keys are now valid mangled keys.
  • A NUL-prefixed key that does not resolve to a declared property on $class or a parent raises ValueError("invalid mangled key") instead of silently creating a dynamic property (matching the ext's "not a parent" rejection).

Tests

  • Six scoped-mode-only test cases removed (integer-key / non-array / interface / unrelated / non-existing scope) — the remaining *MatchesUnserialize tests cover the equivalent behavior in the flat shape.
  • Added testHydrateBareNameReachesParentPrivate covering bare-name-to-parent-private resolution via the propertyScopes index (GP → P → C chain).
  • All 362 DeepClone tests pass locally.

Follow-up (out of scope for this PR)

symfony/var-exporter's Hydrator::hydrate() / Instantiator::instantiate() still need a separate PR to keep their $scopedVars / $mangledVars BC parameters by translating to the flat shape internally.

Test plan

  • 362/362 DeepClone tests green locally
  • CI green across the PHP matrix

@nicolas-grekas nicolas-grekas force-pushed the drop-scoped-mode branch 5 times, most recently from 715336f to 446a645 Compare April 16, 2026 09:09
@nicolas-grekas nicolas-grekas merged commit 6a987a6 into 1.x Apr 16, 2026
2 of 20 checks passed
@nicolas-grekas nicolas-grekas deleted the drop-scoped-mode branch April 16, 2026 12:51
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
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