Skip to content

Preserve references on dynamic properties - #41

Merged
nicolas-grekas merged 1 commit into
mainfrom
dynamic-property-refs
Sep 23, 2026
Merged

nicolas-grekas merged 1 commit into
mainfrom
dynamic-property-refs

Conversation

@nicolas-grekas

@nicolas-grekas nicolas-grekas commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Builds on #40.

To preserve the reference topology PHP supports, like unserialize(serialize()) does, deepclone_to_array() now keeps references on dynamic properties too, and deepclone_from_array() binds them instead of rejecting such payloads since #34. The deprecation their creation raised on the origin isn't reported again; readonly classes still reject them:

$o = new Foo(); // #[AllowDynamicProperties]
$o->a = 1;
$o->b = &$o->a;
$c = deepclone_from_array(deepclone_to_array($o));
$c->a = 42; // $c->b is 42 too, now

This also fixes the other paths that passed references to zend_std_write_property(), which aborts debug builds: the lazy-ghost replay of deepclone_from_array(), and deepclone_hydrate() with DEEPCLONE_HYDRATE_PRESERVE_REFS on dynamic properties, hooked properties and uninitialized lazy objects. Hooked properties get the value, as PHP doesn't allow references there.

Companion polyfill PR: symfony/polyfill#674

deepclone_to_array() now keeps shared references on dynamic properties too,
on objects with the standard property handlers, and deepclone_from_array()
binds them to the properties table like unserialize() does, instead of
rejecting such payloads. The deprecation their creation raised on the origin
is not reported again. Readonly classes still reject them, as do virtual
properties and objects with custom property handlers.

zend_std_write_property() only accepts dereferenced values, which the
lazy-ghost replay of deepclone_from_array() and deepclone_hydrate() with
DEEPCLONE_HYDRATE_PRESERVE_REFS did not ensure: a reference targeting a
dynamic property, a hooked property or a property of an uninitialized lazy
object aborted debug builds. Dynamic properties and lazy objects, once
initialized, now get the reference, and hooked properties the value.
@nicolas-grekas
nicolas-grekas merged commit 84b426c into main Sep 23, 2026
21 checks passed
nicolas-grekas added a commit to symfony/polyfill that referenced this pull request Sep 23, 2026
…ze()) does (nicolas-grekas)

This PR was merged into the 1.x branch.

Discussion
----------

[DeepClone] Preserve PHP references like unserialize(serialize()) does

| Q             | A
| ------------- | ---
| Branch?       | 1.x
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Companion to symfony/php-ext-deepclone#40 and symfony/php-ext-deepclone#41, to preserve the reference topology PHP supports. References on properties, declared or dynamic, are now kept and bound back instead of being copied by value:

```php
$o->b = &$o->a;
$c = deepclone_from_array(deepclone_to_array($o));
$c->a = 42; // $c->b is 42 too, now
```

This also fixes references in arrays: one bound to a typed property made `deepclone_to_array()` throw a `TypeError`, unshared ones next to objects produced payloads that got rejected, that resolved object ids instead of objects, or that carried the original objects themselves, and shared ones holding `null` were rejected by `deepclone_from_array()`.

Exporting objects costs about the same as before, -2% to +1% instructions depending on the graph, and importing is unchanged. The native runs of the new property tests are skipped with ext-deepclone < 0.8.4.

Commits
-------

2ace422 [DeepClone] Preserve PHP references like unserialize(serialize()) does
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