Skip to content
LaravelBlog

livewire #10489: [4.x] Recursively hydrate nested synthesizers during property updates

LaravelBlogBot
LaravelBlogBot

Contributed by lam0819

Part of the livewire v4.4.7 release

Categories: Fix (3), Security (10)

This pull request closes a hydration gap in Livewire's property-update pipeline. When a request updated a path above a synthesized value — a collection, an enum, a Carbon date, or an uploaded file — everything nested underneath came back from the wire as raw strings and arrays, silently losing its PHP type. The most visible symptom was nested repeaters "forgetting" files the user had already uploaded. PR #10489 makes property-update hydration recursive, rebuilding nested values from metadata stored in the previous, checksum-verified snapshot. If you're upgrading from v3.8.10 to v4.x, you get this fix automatically, with no public API changes.

The bug: updates only hydrated one level deep

Non-primitive values travel over the wire as a [value, meta] tuple, where meta tells Livewire which synthesizer rebuilds the PHP value. Full hydration walks that tree recursively. Property updates did not:

// Before: HandleSynths::hydratePropertyUpdate()
return $synth->hydrate($value, $meta, function ($name, $child) {
    return $child; // nested values passed through raw
});

So when an update targeted a path that sits above a synthesized value, the synth at the updated path ran, but every child beneath it arrived back as plain wire data.

The symptom: files that vanish from nested repeaters

Consider state shaped like data.sections.{section}.rows.{row}.image. Typing into any field of a section sends an update for the ancestor path — data.sections.first — not for the image leaf. The browser strips synthesizer meta out of update payloads, so the already-uploaded file arrives as its serialized string with nothing to reconstruct it from:

$component->set('data.sections.first', [
    'rows' => [
        'one' => ['image' => 'livewire-file:BToZJYNz....jpg', 'caption' => 'Updated'],
    ],
]);

After this request, image was a plain string. The file was still on disk, but nothing downstream recognized it, so it disappeared from the form — the bug Filament users hit in nested repeaters (see discussion #6203). It isn't upload-specific: collections, enums, Carbon instances, and custom synths all suffered the same mistyping; uploads just make the loss obvious.

The fix: recurse, using meta from the previous snapshot

hydratePropertyUpdate() now accepts the previous snapshot data ($raw) and rehydrates nested children with it:

$childPath = "{$path}.{$name}";

if (! $childMeta = $this->getMetaForPath($raw, $childPath)) return $child;

return $this->hydratePropertyUpdate([$child, $childMeta], $context, $childPath, $raw);

Two deliberate bail-outs keep edge cases sane:

  • No meta in the snapshot → the value is left alone. A repeater row added during this request has no counterpart in the previous snapshot, so it stays plain data — exactly as before.
  • A child that is already a PHP object is left alone. Real payloads are JSON, so an object can only have been set server-side (e.g. Livewire::test(...)->set('list', [SomeEnum::TEST])).

The new $raw argument is optional and defaults to null, preserving the old behaviour for any external caller — there is no breaking change to the method's signature in practice.

The security contract: values from the wire, types from the snapshot

Because the recursion re-enters hydratePropertyUpdate(), the trust boundary is preserved: the update payload supplies values only, while synthesizer keys and class names are read exclusively from the previous snapshot, which Checksum::verify() authenticated with your application key. Every nested tuple still passes SecurityPolicy::validateClass() and each synth's own class constraints.

The tests exercise both sides: an update value deliberately forged to look like a synth tuple — claiming the denylisted Symfony\Component\Process\Process class — remains ordinary data inside the Collection chosen by the authenticated snapshot meta, and tampering with nested meta inside the snapshot itself is rejected with CorruptComponentPayloadException before hydration ever runs. This grants no new write authority: a client that could send a consolidated ancestor update could always have sent the equivalent granular leaf update instead. Nine tests back the change, including two real-world file-upload scenarios.

What this means for your upgrade

  • Behaviour change, in a good way. Ancestor updates now preserve nested types. Any workaround you built — manually re-hydrating uploads, re-reading from disk after a parent update — can be removed.
  • Check comparisons, not logic. Code that (knowingly or not) received raw strings/arrays in these situations will now receive proper objects. instanceof checks and strict comparisons are the places to look, though affected code was almost certainly broken already.
  • If you stay on 3.x: the same bug exists there (the methods live in HandleComponents), and a backport has been offered — watch the 3.x changelog.
  • Relation to #10431: that PR mitigates the same symptom client-side on main, but it can't see server-only synthesizers (Collection, Carbon, enums, userland synths) or updates that bypass the differ ($wire.$set(), package writes, crafted requests). This server-side fix covers all of those routes.

Takeaways

  • Property updates targeting an ancestor path now recursively rehydrate every nested synthesized value from the previous snapshot's meta.
  • Hydration metadata comes only from the checksum-verified snapshot; the update payload can never choose a synthesizer or class.
  • New children without snapshot meta stay plain data, and already-hydrated PHP objects are untouched.
  • hydratePropertyUpdate() gained an optional parameter only — no public API changes, no config changes.
  • Nested file uploads (and quietly mistyped collections, enums, and dates) survive ancestor updates; if you're on v3.8.10, upgrading to v4.x picks this up, and a 3.x backport may follow.

Sources

More from this release

Related Articles