News

Scheduled Session Garbage Collection in Laravel 13.39

Published
Scheduled Session Garbage Collection in Laravel 13.39 image

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 reaching DB::listen() and expectsDatabaseQueryCount() when LazilyRefreshDatabase migrates during the test (#62201)
  • The mail fake now handles Mail::html() and Mail::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() and pool() (#62200)
  • The RefreshDatabase trait 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

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

serpapi logo
SerpApi

The Web Search API for Your LLM and AI Applications

Visit SerpApi

The latest

View all →
Deferred Schema Loading in Filament v5.8 image

Deferred Schema Loading in Filament v5.8

Read article
Sloppy: Static Analysis for AI-Written PHP image

Sloppy: Static Analysis for AI-Written PHP

Read article
Mastering Laravel Testing: A Video Course on Laravel Tests image

Mastering Laravel Testing: A Video Course on Laravel Tests

Read article
NativePHP Mobile Security Advisories: Update to 4.6.1 image

NativePHP Mobile Security Advisories: Update to 4.6.1

Read article
Volker Dusch on the PHP RCE Vulnerabilities and Disclosure image

Volker Dusch on the PHP RCE Vulnerabilities and Disclosure

Read article
Decide with Jev: Turn AI Answers into Decisions with PHP Rules image

Decide with Jev: Turn AI Answers into Decisions with PHP Rules

Read article