The Laravel team released v13.33.0 with tag support for the memoized cache, a Refreshes model attribute that reloads generated columns after a write, and more:
Tagged Memoized Cache
Joost de Bruijn contributed a tags() method to the memoized cache store, so Cache::memo() now works with tagged caches. Reads inside the same request hit the cache backend once and come from memory after that:
// Two calls, one round trip to RedisCache::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 copyCache::memo()->tags(['permissions'])->put("permissions:{$user->id}", $permissions); // Flushing the tag also clears the memoized entriesCache::memo()->tags(['permissions'])->flush();
Before this release, the same pattern meant wrapping a tagged cache call in a second Cache::store('array') lookup by hand. Laravel News covered the untagged version when it shipped in Laravel 12.9.
Read more in Memoize Tagged Cache Reads in Laravel. See #61593 for more details.
Refresh Model Attributes After Writes
Caleb White contributed a #[Refreshes] attribute (and a matching $refreshes property) that reloads specific columns from the database after a model is created or updated. It is meant for virtualAs and storedAs columns and other values the database sets, which are otherwise missing or stale on the in-memory model until you call refresh():
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" without calling refresh()
The refresh runs one query for the listed columns only, on the write connection. The attribute accepts an array or variadic column names, and the $refreshes property works the same way if you do not use attributes. Read more in Refresh Generated Columns After Saving an Eloquent Model.
See #61523 for more details.
Store a Real NULL With AsCollection and AsArrayObject
Zein Ahmad contributed an opt-in nullable() mode for the AsCollection and AsArrayObject casts. Assigning null to one of these casts stores the JSON string "null" in the column, so whereNull() never matches the row. With nullable(), the cast stores a database NULL instead:
protected function casts(): array{ return [ 'items' => AsCollection::class, // unchanged: stores "null" 'meta' => AsCollection::nullable(), // stores NULL 'options' => AsArrayObject::nullable(), ];}
Read more in Store NULL Instead of "null" With AsCollection::nullable(). See #61596 for more details.
inplace() and lock() for Index Migrations
Sander Muller contributed an inplace() modifier for index and foreign key operations, along with a lock() modifier, so a MySQL migration can request ALGORITHM=INPLACE and LOCK=NONE instead of falling back to a blocking table rebuild:
Schema::table('video_sessions', function (Blueprint $table) { $table->index('foo', 'foo_index')->inplace()->lock('none');});
The modifier works on the following methods:
index()unique()primary()fullText()spatialIndex()foreign()
It mirrors the instant() modifier that column definitions already have.
See #61602 for more details.
Form Request Attributes From Parent Classes
Md Sukkur Ali fixed form request attributes so a child request picks them up from its parent class. PHP does not inherit attributes, so attributes such as #[StopOnFirstFailure] on a shared base request were ignored by every subclass:
#[StopOnFirstFailure]#[FailOnUnknownFields]abstract class ApiRequest extends FormRequest{ //} // Both attributes now applyclass StorePostRequest extends ApiRequest{ //}
See #61586 for more details.
Opt Out of Killing the Worker on Timeout
Jack Bayliss contributed a Worker::$killOnTimeout flag. By default, a job that exceeds its timeout kills the whole worker process, and the supervisor boots a fresh one. Set the flag to false and the worker throws a TimeoutExceededException instead, so the job can clean up and the worker keeps running:
use Illuminate\Queue\Worker; Worker::$killOnTimeout = false;
Warning: a job that wraps its handle() body in a broad try/catch can swallow the exception.
A related PR by Kieran Brown adds Worker::killUsing(), a hook that runs before the SIGKILL so a custom exit code can reach the supervisor (#61622).
See #61591 for more details.
mockArtisan() and realArtisan() Test Helpers
Jenthe Noordsij contributed two test helpers that split the two jobs of $this->artisan(). The mockArtisan() method always returns a PendingCommand for expectations, and realArtisan() always runs the command and returns the exit code as an int, so you no longer toggle $mockConsoleOutput to switch between the two:
$this->mockArtisan('inspire') ->expectsOutputToContain('Simplicity') ->assertExitCode(0); $code = $this->realArtisan('migrate', ['--force' => true]);
See #61588 for more details.
Release Notes
You can see the complete list of new features and updates below and the diff between 13.32.0 and 13.33.0 on GitHub. The following release notes are directly from the changelog:
v13.33.0
- [13.x] Drop restart_syscalls from pcntl_signal in Worker by @jackbayliss in #61590
- Adjust when password hash is stored for authenticated sessions by @taylorotwell in #61594
- [13.x] Ability to opt out of killing the worker on job timeout by @jackbayliss in #61591
- [13.x] Fix enum keys in tagged cache many() and get() by @Meacue in #61603
- [13.x] Add opt-in support for persisting real NULL on AsCollection/AsArrayObject by @zeinrahmad76 in #61596
- [13.x] Fix Eloquent Collection duplicates() with a key or callback by @lazerg in #61597
- Improve Cloud job retries by @timacdonald in #61584
- [13.x] Support brick/math 0.20 & 1.0 by @jackbayliss in #61382
- [13.x] add health-start-period to workflows by @jackbayliss in #61608
- [13.x] Resolve FormRequest attributes from parent classes by @mdsukkur in #61586
- [13.x] Add order() to DevCommands by @jackbayliss in #61575
- [13.x] Add EncodedParameter to RouteUrlGenerator by @bytestream in #61610
- [13.x] Add inplace() DDL algorithm modifier for index and foreign key operations by @SanderMuller in #61602
- [13.x] Improve the value types of the Collection put method by @dfinchenko in #61611
- Allow relative URLs for Mercure hub by @barryvdh in #61629
- [13.x] Add server options to PostgreSQL connections by @DGarbs51 in #61627
- [13.x] Make the worker timed out exit code reachable by @kieranbrown in #61622
- Ci mariadb 11 by @Rhaima96 in #61615
- [13.x] Tagged memoized cache by @joostdebruijn in #61593
- Add "valkey://" and "valkeys://" protocol support by @cweiske in #61635
- [13.x] Fix SQLite schema dumps containing shadow tables by @jhm-ciberman in #61637
- [13.x] Make spelling consistent across docblocks by @shaedrich in #61631
- [13.x] Use
composer require --with-all-dependenciesto install additional requirements by @crynobone in #61617 - [13.x] Rebuild the phpredis client when a pipeline or transaction loses the connection by @maduonline in #61632
- Apply fixes from StyleCI by @taylorotwell in #61638
- [13.x] Add
--pretendoption toinstall:broadcastingcommand by @crynobone in #61633 - [13.x] Improved artisan mocking by @jnoordsij in #61588
- [13.x] feat: refresh configured model attributes after writes by @calebdw in #61523
- [13.x] Pass fresh batch to
BatchFinishedandBatchCanceledevents by @xurshudyan in #61644 - [13.x] Output empty JSON array from route:list --json when no routes match by @xurshudyan in #61642
- [13.x] Test against MariaDB 12.3 by @Rhaima96 in #61641
- [13.x] Use default view when make:notification --markdown has no value by @xurshudyan in #61643
- [13.x] Test Improvements by @crynobone in #61660
- [13.x] Fix
LazyCollection::take()with a negative limit larger than the collection by @crynobone in #61659 - [13.x] Skip batches that cannot be retried in queue:retry-batch by @xurshudyan in #61653
- [13.x] Consolidate overlapping tests by @jasonmccreary in #61648
- [13.x] Fix order-dependent tests by @jasonmccreary in #61647
- [13.x] Respect FILESYSTEM_DISK override when configuring Cloud disks by @DGarbs51 in #61664
- [13.x] Configure Laravel Cloud database read replicas by @WendellAdriel in #61665
- [13.x] Remove non-string headers from NotPwndVerifier by @kylemilloy in #61666
- [13.x] Fix ERR invalid cursor returned when calling
$predisClient->scan(null, ...)while using Redis 7.4 by @crynobone in #61673 - [13.x] Restore query logging when a pretend callback throws by @xurshudyan in #61671
- [13.x] add sole to higher order proxies by @jackbayliss in #61667
- [13.x] Add
createQuietlyandcreateManyQuietlytoBelongsToManyby @xurshudyan in #61669 - [13.x] Fix
whereValueBetween()binding expression values by @xurshudyan in #61670 - [13.x] Add opt-in native Postgres pooling for Laravel Cloud by @DGarbs51 in #61668