Skip to content

What's New in Laravel 13.33: Tagged Memoized Cache, Model Refreshes, and More

Laravel 13.33 ships tagged support for the memoized cache store, a #[Refreshes] model attribute for generated columns, nullable AsCollection casts, inplace() index migrations, and several quality-of-life improvements.

Published · Updated · 3 min read

What's New in Laravel 13.33: Tagged Memoized Cache, Model Refreshes, and More

Laravel 13.33 Released: Key Features and Improvements

The Laravel team shipped v13.33.0 on September 23, 2026, with a solid set of developer-focused additions. Here is a breakdown of the most impactful changes.


Tagged Memoized Cache

Contributed by Joost de Bruijn, Cache::memo() now supports tagged caches via a tags() method. Within a single request, repeated reads for the same key hit the cache backend only once — subsequent reads are served from memory.

// Two calls, one round trip to Redis
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");

// Writes go to the tagged cache and drop the memoized copy
Cache::memo()->tags(['permissions'])->put("permissions:{$user->id}", $permissions);

// Flushing the tag also clears memoized entries
Cache::memo()->tags(['permissions'])->flush();

Before this change, combining tagged caches with memoization required a manual Cache::store('array') wrapper. See #61593.


Refresh Model Attributes After Writes

Caleb White contributed a #[Refreshes] PHP attribute (and a $refreshes property alternative) that reloads specific database columns after a model is created or updated. This is particularly useful for virtualAs and storedAs generated columns that the database computes at write time.

use Illuminate\Database\Eloquent\Attributes\Refreshes;

#[Refreshes('name')]
class User extends Model
{
    //
}

$user = User::create(['first_name' => 'Taylor', 'last_name' => 'Otwell']);
$user->name; // "Taylor Otwell" — no manual refresh() call needed

The refresh issues one targeted query for the listed columns only, on the write connection. See #61523.


Store a Real NULL With AsCollection and AsArrayObject

Zein Ahmad added an opt-in nullable() mode to both casts. Without it, assigning null stores the JSON string "null", which breaks whereNull() queries. With nullable(), the column stores a proper database NULL.

protected function casts(): array
{
    return [
        'items'   => AsCollection::class,       // stores "null"
        'meta'    => AsCollection::nullable(),   // stores NULL
        'options' => AsArrayObject::nullable(),
    ];
}

See #61596.


inplace() and lock() for Index Migrations

Sander Muller added inplace() and lock() modifiers for index and foreign key operations, letting MySQL migrations request ALGORITHM=INPLACE and LOCK=NONE to avoid blocking table rebuilds.

Schema::table('video_sessions', function (Blueprint $table) {
    $table->index('foo', 'foo_index')->inplace()->lock('none');
});

Supported methods: index(), unique(), primary(), fullText(), spatialIndex(), and foreign(). See #61602.


Other Notable Changes

  • Form Request parent attributes — #[StopOnFirstFailure] and similar attributes on a base FormRequest class are now inherited by child requests (#61586).
  • Worker::$killOnTimeout — Set to false to throw TimeoutExceededException instead of killing the worker process on job timeout (#61591).
  • mockArtisan() / realArtisan() — New test helpers that cleanly separate expectation-based Artisan mocking from real command execution (#61588).
  • Valkey protocol support — valkey:// and valkeys:// connection strings are now recognized (#61635).

Key Takeaways

  • Cache::memo()->tags([...]) eliminates redundant cache backend round trips for tagged keys within a request.
  • #[Refreshes] keeps generated/computed columns accurate on the in-memory model after every write.
  • AsCollection::nullable() ensures whereNull() works correctly on JSON cast columns.
  • inplace() on index migrations reduces downtime risk on large MySQL tables.
  • mockArtisan() and realArtisan() make Artisan testing intent explicit without toggling $mockConsoleOutput.

Source: Laravel News — Laravel 13.33.0

Frequently asked questions

What does Cache::memo()->tags() do in Laravel 13.33?

It combines the memoized cache store with tagged cache support. Within a single request, the first read fetches data from the cache backend (e.g., Redis) and stores it in memory. All subsequent reads for the same tagged key are served from memory, reducing round trips. Writes and flushes propagate to the tagged cache and also invalidate the in-memory copy.

When should I use the #[Refreshes] attribute on an Eloquent model?

Use it when your model has virtualAs or storedAs generated columns, or any column whose value is set by the database at write time. Without #[Refreshes], those columns are missing or stale on the in-memory model until you call refresh() manually. The attribute reloads only the specified columns in a single query after each create or update.

What is the difference between AsCollection::class and AsCollection::nullable() in casts?

The default AsCollection::class cast serializes a null PHP value as the JSON string "null", which means whereNull() queries will not match that row. AsCollection::nullable() stores a real database NULL instead, making the column behave as expected with null-checking queries.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project