Skip to content

Drop SPL "\0" special key from deepclone_hydrate()#17

Merged
nicolas-grekas merged 1 commit intomainfrom
cleanup-serializable
Apr 26, 2026
Merged

Drop SPL "\0" special key from deepclone_hydrate()#17
nicolas-grekas merged 1 commit intomainfrom
cleanup-serializable

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Member

ArrayObject, ArrayIterator and SplObjectStorage all ship __serialize / __unserialize since PHP 7.4. The "\0" special key in deepclone_hydrate() was a port from Symfony's legacy Hydrator API and added ~80 lines of bespoke handling — offsetSet loops, constructor invocation, packed-array shape validation, error paths — that duplicates what the classes natively expose.

Callers now populate these objects the standard way:

$ao = deepclone_hydrate('ArrayObject');
$ao->__unserialize([ArrayObject::ARRAY_AS_PROPS, ['x' => 1, 'y' => 2], []]);

$s = deepclone_hydrate('SplObjectStorage');
$s->__unserialize([[$o1, 'info1', $o2, 'info2'], []]);

Or just round-trip via deepclone_from_array(), which routes through __unserialize natively.

The mangled-key resolution path ("propName", "\0*\0prop", "\0Class\0prop") is unchanged; only the SPL fast-path is removed. Tests updated to use __unserialize().

Symfony's Hydrator::hydrate() / Instantiator::instantiate() keep BC by translating the legacy "\0" shape to __unserialize() themselves.

Net diff: -128 lines.

ArrayObject, ArrayIterator and SplObjectStorage all ship __serialize /
__unserialize since PHP 7.4. The "\0" key was a port from Symfony's old
Hydrator API and added ~80 lines of bespoke handling (offsetSet loops,
constructor invocation, error-path validation) that duplicated what the
classes natively expose.

Callers populate these objects by instantiating with deepclone_hydrate()
and calling __unserialize() with the documented array shape — or they
just round-trip via deepclone_from_array(), which routes through
__unserialize natively.

The mangled-key resolution path is unchanged; only the SPL fast-path is
removed.
@nicolas-grekas nicolas-grekas merged commit ec59deb into main Apr 26, 2026
20 checks passed
@nicolas-grekas nicolas-grekas deleted the cleanup-serializable branch April 26, 2026 12:54
nicolas-grekas added a commit to symfony/polyfill that referenced this pull request Apr 26, 2026
Mirrors the C extension change (symfony/php-ext-deepclone#17). ArrayObject,
ArrayIterator and SplObjectStorage all ship __serialize / __unserialize
since PHP 7.4 — callers populate them the standard way:

    $ao = deepclone_hydrate('ArrayObject');
    $ao->__unserialize([ArrayObject::ARRAY_AS_PROPS, ['x' => 1], []]);

The mangled-key resolution path is unchanged; only the SPL fast-path is
removed. Symfony's Hydrator/Instantiator retain BC by translating the
legacy "\0" shape to __unserialize() themselves.
nicolas-grekas added a commit to symfony/polyfill that referenced this pull request Apr 26, 2026
…() (nicolas-grekas)

This PR was merged into the 1.x branch.

Discussion
----------

[DeepClone] Drop SPL "\0" special key from deepclone_hydrate()

Mirrors symfony/php-ext-deepclone#17.

ArrayObject, ArrayIterator and SplObjectStorage all ship `__serialize` / `__unserialize` since PHP 7.4. The `"\0"` special key in `deepclone_hydrate()` was a port from Symfony's legacy Hydrator API and added bespoke handling that duplicated what these classes natively expose.

Callers now populate these objects the standard way:

```php
$ao = deepclone_hydrate('ArrayObject');
$ao->__unserialize([ArrayObject::ARRAY_AS_PROPS, ['x' => 1, 'y' => 2], []]);

$s = deepclone_hydrate('SplObjectStorage');
$s->__unserialize([[$o1, 'info1', $o2, 'info2'], []]);
```

Or just round-trip via `deepclone_from_array()`, which routes through `__unserialize` natively.

The mangled-key resolution path (`"propName"`, `"\0*\0prop"`, `"\0Class\0prop"`) is unchanged; only the SPL fast-path is removed. Tests updated to use `__unserialize()`.

Symfony's `Hydrator::hydrate()` / `Instantiator::instantiate()` keep BC by translating the legacy `"\0"` shape to `__unserialize()` themselves.

Commits
-------

a805169 [DeepClone] Drop SPL "\0" special key from deepclone_hydrate()
symfony-splitter pushed a commit to symfony/polyfill-deepclone that referenced this pull request Apr 26, 2026
Mirrors the C extension change (symfony/php-ext-deepclone#17). ArrayObject,
ArrayIterator and SplObjectStorage all ship __serialize / __unserialize
since PHP 7.4 — callers populate them the standard way:

    $ao = deepclone_hydrate('ArrayObject');
    $ao->__unserialize([ArrayObject::ARRAY_AS_PROPS, ['x' => 1], []]);

The mangled-key resolution path is unchanged; only the SPL fast-path is
removed. Symfony's Hydrator/Instantiator retain BC by translating the
legacy "\0" shape to __unserialize() themselves.
nicolas-grekas added a commit to symfony/symfony that referenced this pull request Apr 26, 2026
…lize() in Hydrator/Instantiator (nicolas-grekas)

This PR was merged into the 8.1 branch.

Discussion
----------

[VarExporter] Translate legacy SPL "\0" key to __unserialize() in Hydrator/Instantiator

| Q             | A
| ------------- | ---
| Branch?       | 8.1
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

`deepclone_hydrate()` no longer treats the special `"\0"` key as SPL internal state (see symfony/php-ext-deepclone#17 / symfony/polyfill#584). `ArrayObject`, `ArrayIterator` and `SplObjectStorage` all ship `__serialize` / `__unserialize` since PHP 7.4 — the C extension and polyfill now expect callers to use the standard path.

This PR keeps BC for `Hydrator::hydrate()` and `Instantiator::instantiate()`: when `$mangledVars` contains a `"\0"` key with the legacy shape, it is intercepted and translated to a `__unserialize()` call.

Mapping:
* `SplObjectStorage`: `"\0" => [$obj1, $info1, $obj2, $info2, ...]` → `__unserialize([[$obj1, $info1, ...], []])`
* `ArrayObject`: `"\0" => [$array, $flags?, $iteratorClass?]` → `__unserialize([$flags ?? 0, $array, [], $iteratorClass ?? ArrayIterator::class])`
* `ArrayIterator`: `"\0" => [$array, $flags?]` → `__unserialize([$flags ?? 0, $array, []])`

In `Instantiator::instantiate()`, the `__unserialize()` call happens after instantiation so the constructor is still skipped.

Depends on symfony/polyfill#584 (which depends on symfony/php-ext-deepclone#17).

Commits
-------

54d013b [VarExporter] Translate legacy SPL "\0" key to __unserialize() in Hydrator/Instantiator
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