The Laravel team released v13.39.0 with a session garbage collection Artisan command, a way to turn off a model's primary key, job durations on the JobAttempted event, and more:
Session Garbage Collection Command
Andrew Brown added a session:garbage-collect command that runs the session driver's garbage collector. By default, Laravel cleans up expired sessions on a random request, using the lottery setting in config/session.php. With the command, you can run the cleanup on a schedule instead:
use Illuminate\Support\Facades\Schedule; Schedule::command('session:garbage-collect')->hourly();
The command uses your session.lifetime value to decide which sessions have expired. To stop requests from also running the cleanup, set the lottery to [0, 100].
See #62104 and #62150 for more details.
Disable the Primary on an Eloquent Model
Caleb White made it possible to pass disable a model's primary key using the #[Table] attribute. Before this update, a null key fell back to the $primaryKey property, which defaults to id, so you still needed the property:
#[Table(name: 'activity_log', key: false)]class Activity extends Model {} Activity::create(['event' => 'login']);
A follow-up from Volodya Kurshudyan also marks these models as non-incrementing, so create() no longer throws after the insert.
See #62113 and #62205 for more details.
Job Durations on JobAttempted
Jack Bayliss added a duration property to the JobAttempted event. Before this release, only JobProcessed had a duration, so you had no timing for a job that failed or was released for a retry. The value is in milliseconds:
use Illuminate\Queue\Events\JobAttempted; Event::listen(function (JobAttempted $event) { if (! $event->successful()) { Log::warning('Job failed', [ 'job' => $event->job->resolveName(), 'duration_ms' => $event->duration, ]); }});
A second PR adds the same timing to the sync queue, so jobs run with dispatchSync() now have a duration on both JobProcessed and JobAttempted.
See #62118 and #62155 for more details.
Size Methods on the Array Rule Builder
Richard van Baarsen added min(), max(), between(), and exactly() to Rule::array(), matching the methods on Rule::string(). You no longer need a string rule for the array size:
// Before'photos' => [Rule::required(), Rule::array(), 'min:1'], // After'photos' => [Rule::required(), Rule::array()->min(1)->max(10)],
They also work with allowed keys. For example, Rule::array(['a', 'b'])->min(1) produces array:a,b|min:1. See Fluent Rule Builders for All Laravel Validation Rules for the other builders.
See #62179 for more details.
Testing Updates
Volodya Kurshudyan sent several fixes to the testing helpers:
- A new
#[WithoutMigrationEvents]attribute stops migration queries from reachingDB::listen()andexpectsDatabaseQueryCount()whenLazilyRefreshDatabasemigrates during the test (#62201) - The mail fake now handles
Mail::html()andMail::plain(). Before this, those calls sent real email during tests (#62162) - The bus fake supports
withoutDispatchingAfterResponses(), so after-response jobs can run while other jobs are faked (#62130) - The process fake exit code and exception shorthands now work with
start()andpool()(#62200) - The
RefreshDatabasetrait rolls back every open transaction level after each test, so one test that leaves a transaction open no longer fails the tests after it (#62194)
Here is the new attribute on a test that counts queries:
use Illuminate\Foundation\Testing\Attributes\WithoutMigrationEvents; #[WithoutMigrationEvents]public function test_it_runs_one_query(){ $this->expectsDatabaseQueryCount(1); User::first();}
Mior Muhammad Zaki also fixed assertSeeText('0'), which passed without checking the response (#62126).
Relationship Create and Restore Methods
The BelongsToMany relation now has forceCreate() and forceCreateQuietly(). Before this, the call went to the query builder, so the model was created but never attached:
$team->users()->forceCreate(['name' => 'Bob', 'is_admin' => true]);
The restoreOrCreate() and createOrRestore() methods also work on relationships now. They were builder macros that ignored the relationship, so a has-many created models without the foreign key, and a belongs-to-many could restore the wrong model. Both methods now go through the relationship, including HasManyThrough and HasOneThrough.
See #62136, #62146, and #62204 for more details.
Atomic Config, Route, and Event Cache Files
The config:cache, route:cache, and event:cache commands now write to a temporary file and rename it. Before this, a request that booted while the command ran could read an empty cache file. With opcache.validate_timestamps=0, OPcache kept the empty file, and every request failed until PHP-FPM restarted.
See #62117 for more details.
Release Notes
You can see the complete list of new features and updates below and the diff between 13.38.0 and 13.39.0 on GitHub. The following release notes are directly from the changelog:
v13.39.0
- [13.x] Rebuild the phpredis client when a broken pipe surfaces as a warning by @aleedhillon in #62111
- [13.x] Resolve view namespace hints like view locations by @fgilio in #62005
- [13.x] feat(database): allow disabling primary key via #[Table(key: false)] by @calebdw in #62113
- [13.x] add duration to JobAttempted event by @jackbayliss in #62118
- [13.x] Write config, route and event cache files atomically by @xurshudyan in #62117
- [13.x] Fix database cache lock aborting the transaction on PostgreSQL by @xurshudyan in #62114
- Fix
assertSeeText('0')silently passes for zero as string by @crynobone in #62126 - [13.x] Fix incorrect docblocks by @xurshudyan in #62125
- [13.x] Don't let a job with a deleted model stop queue:retry by @xurshudyan in #62124
- [13.x] Fix the previous route leaking into 404 requests under Octane by @xurshudyan in #62123
- [13.x] Fix cache:prune-stale-tags with the phpredis SCAN_PREFIX option by @xurshudyan in #62122
- [13.x] Fix Http::batch() treating retry() and throw() failures as successes by @mkantautas in #62120
- [13.x] Create
SessionGarbageCollectCommandby @browner12 in #62104 - Bump vite from 8.0.16 to 8.3.5 in /src/Illuminate/Foundation/resources/exceptions/renderer by @dependabot[bot] in #62132
- [13.x] Add withoutDispatchingAfterResponses to BusFake by @xurshudyan in #62130
- [13.x] Fix pagination counts for grouped union queries by @mkantautas in #62131
- [13.x] Fix simple paginator ignoring page name by @xurshudyan in #62128
- [13.x] Qualify pivot columns in wherePivot closures by @xurshudyan in #62137
- [13.x] Add
forceCreateandforceCreateQuietlytoBelongsToManyby @xurshudyan in #62136 - Apply fixes from StyleCI by @taylorotwell in #62140
- [13.x] remember token is nullable by @browner12 in #62105
- [13.x] Fix dimensions of JXL images by @xurshudyan in #62151
- [13.x] Fix latest() and oldest() with a join from a subquery by @xurshudyan in #62149
- [13.x] Register the session:garbage-collect command by @xurshudyan in #62150
- [13.x] Ensure
_error_baginput is a string inHandler::invalid()by @manavu in #62147 - [13.x] Cloud exceptions follow-ups by @timacdonald in #62142
- [13.x] Don't fail database queue jobs on concurrency errors while reserving by @xurshudyan in #62145
- [13.x] Fix N+1 queries on JSON:API resource collection includes by @xurshudyan in #62152
- [13.x] Fix once() returning a cached value for a different object by @xurshudyan in #62121
- [13.x] Fix Rule::anyOf() passing associative arrays against plain rule lists by @xurshudyan in #62171
- [13.x] Fix withoutMiddleware() not excluding middleware with parameters by @xurshudyan in #62166
- [13.x] Block phar:// stream wrapper in Filesystem::exists() and isFile() by @AsahiSoftWareEngineer in #62164
- [13.x] Embed images once in markdown notifications by @xurshudyan in #62163
- [13.x] Fake
html()andplain()onMailFakeby @xurshudyan in #62162 - [12.x] Test
SessionGuard::user()with recaller cookie that has an empty remember token by @crynobone with @Copilot in #62156 - [13.x] Fix restoreOrCreate() and createOrRestore() on relations by @xurshudyan in #62146
- [13.x] ensure sync queue passes duration too by @jackbayliss in #62155
- Bump RedCrafter07/release-notes-action from 1.0.1 to 1.0.2 in the github-actions group by @dependabot[bot] in #62196
- [13.x] Roll back all transaction levels after each test in
RefreshDatabaseby @xurshudyan in #62194 - [13.x] Fix
Request::route()return type when no route is resolved by @yoannblot in #62191 - [13.x] Convert the down --retry datetime to GMT by @xurshudyan in #62190
- [13.x] Cloud exceptions improvements by @timacdonald in #62189
- [13.x] Fix
illuminate/busfatal error withoutilluminate/queueby @xurshudyan in #62182 - [13.x] Add size methods to the array rule builder by @riesjart in #62179
- [13.x] Fix
distinct:ignore_casethrowing on values that are not valid UTF-8 by @sumaiazaman in #62178 - [13.x] Use the CI MySQL port in
ApiAuthenticationWithEloquentTestby @xurshudyan in #62176 - [13.x] Throw on unencodable JSON path values by @xurshudyan in #62175
- [13.x] Allow reading files from the running phar by @xurshudyan in #62174
- [13.x] Fail schedule:interrupt when interruption polling is disabled by @jackbayliss in #62183
- Fix queue:retry with non-scalar failed job ids by @GromNaN in #62208
- [13.x] Roll back transactions manager when commit fails by @pawell67 in #62206
- [13.x] Fix creating models with #[Table(key: false)] by @xurshudyan in #62205
- [13.x] Throw on unencodable JSON in object casts by @xurshudyan in #62203
- [13.x] Add restoreOrCreate() and createOrRestore() to through relations by @xurshudyan in #62204
- [13.x] Add
WithoutMigrationEventsattribute by @xurshudyan in #62201 - [13.x] Support exit code and exception fake shorthands when starting processes by @xurshudyan in #62200
- [13.x] Fix install:broadcasting skipping app.jsx and app.js by @xurshudyan in #62202