livewire/livewire: [4.x] Fix cached computed properties returning broken objects on Laravel 13
Contributed by lazerg
Part of the livewire v4.4.7 release
If you are upgrading to Livewire v4 from v3.8.10 and you use computed properties with persist: true or cache: true, this release fixes a serious bug on Laravel 13: values that were cached as objects could come back on later requests as broken __PHP_Incomplete_Class instances instead of your model, collection, or DTO. Livewire now detects those unusable cache hits — including objects nested inside arrays — and re-evaluates the computed property so your component always receives a correct value. In debug mode it also logs an actionable warning telling you exactly which property and class are affected and how to restore real caching.
The problem: broken objects from cache on Laravel 13
New Laravel 13 applications ship with cache.serializable_classes set to false. That means Laravel's serialized cache stores (file, database, Redis, and so on) will refuse to unserialize any PHP object class that is not explicitly allowlisted, as a hardening measure against insecure deserialization.
This collides with Livewire's persisted computed properties. Consider a typical pattern:
#[Computed(persist: true)]
public function user()
{
return User::find($this->userId); // an Eloquent model
}
The first request runs the closure and serializes the model into the cache, so everything looks fine. But on the next request Laravel cannot restore the class, PHP hands back an __PHP_Incomplete_Class, and prior to this fix Livewire returned that broken object straight into your view. Property access and method calls then fail with incomplete-object errors. On v3.8.10 with older Laravel versions you never saw this because unserialization from cache was unrestricted.
What Livewire v4 does instead
Internally, both the persisted and cached code paths (tagged caches included) now route every cache hit through a validation step. If the value is an __PHP_Incomplete_Class — at the top level, or nested anywhere inside a returned array — Livewire treats it as unusable, re-evaluates the computed property, and returns a fresh, correct value for that request.
When app.debug is enabled, you also get a log warning naming the component, the property, and the rejected class:
Livewire re-evaluated cached computed property [ShowUser::user] because Laravel could not unserialize [App\Models\User]. The value is correct, but it was not served from cache. Return a scalar or array, or add the class to [cache.serializable_classes].
The fix resolves issue #10524.
What you should change in your code
There are two supported remedies, and they are the same ones the updated documentation now recommends.
Option 1 (preferred): cache scalars or arrays instead of objects. This is what the docs examples now do by default:
#[Computed(persist: true)]
public function userName()
{
return User::whereKey($this->userId)->value('name'); // plain string
}
#[Computed(cache: true, key: 'homepage-post-titles')]
public function postTitles()
{
return Post::query()->pluck('title', 'id')->all(); // plain array
}
Option 2: intentionally allowlist the class in Laravel. If you genuinely need the object back from cache, add every class in its object graph to config/cache.php:
'serializable_classes' => [
App\Models\User::class,
],
"Object graph" matters: a loaded Eloquent model with relations pulls in the related classes, and a cached Collection of models involves both the collection and each model class. Once the classes are allowlisted, cached objects are served from cache normally again. There is deliberately no Livewire option that bypasses Laravel's cache security policy.
The performance trade-off to watch
Correctness is restored automatically, but caching is not. Because the cached payload cannot be restored, the re-evaluation path runs your query (or whatever the computed property does) on every request until the cache entry expires — 3600 seconds by default, configurable via seconds. Outside of debug mode the only symptom is a silent slowdown, so treat any of these warnings in your logs as a signal to either restructure the cached value or allowlist the class.
Validation and documentation updates
The computed-property documentation now uses cache-safe string and array examples, explains Laravel 13's object-unserialization policy in a callout, and shows how to allowlist a class on purpose. The fix is covered by four new tests proving that restricted objects are re-evaluated for both cache: true and persist: true, that arrays containing restricted objects are handled, that the debug warning is emitted, and that explicitly allowlisted classes remain served from cache. The computed suite passes with 31 tests and 64 assertions; the full unit suite passes with 1,353 tests and 3,863 assertions (13 skipped, 3 pre-existing incomplete tests).
Takeaways
- On Laravel 13,
cache.serializable_classesdefaults tofalse, so cached objects come back as__PHP_Incomplete_Classunless their classes are allowlisted. - Livewire v4 now detects these broken cache hits (top-level or nested in arrays) and re-evaluates the computed property, so your views stay correct.
- In debug mode, a log warning identifies the component, property, and class involved.
- Re-evaluation bypasses the cache until the entry expires — expect the query to run per request until you fix the property.
- Preferred fix: cache scalars or arrays. Alternative: allowlist the class in
config/cache.php. No Livewire flag can override Laravel's policy.