The Laravel team released v13.40.0 with callbacks for queue jobs and scheduled tasks that run too long, commit IDs for checking whether a read replica has a write, insertBefore() and insertAfter() for the middleware stack, a strict flag on request input helpers, and a security fix for the Concurrency process driver.
Queue::whenJobRunsLongerThan()andSchedule::whenTaskRunsLongerThan()DB::getLastCommitId()andDB::hasCommitId()for read replicas- Insert middleware before or after a built-in middleware
- A
strictflag forinteger(),float(),boolean(), andstring() - Signed result output for
Concurrency::run()with the process driver - Transaction fixes for lost connections and lock wait timeouts
Alerts for Long-Running Jobs and Scheduled Tasks
Jack Bayliss added Queue::whenJobRunsLongerThan(), which runs a callback when a job attempt takes longer than a threshold. Laravel already has DB::whenQueryingForLongerThan() and similar methods for requests and commands, but not for queued jobs:
use Carbon\CarbonInterval; use Illuminate\Support\Facades\Queue; Queue::whenJobRunsLongerThan(CarbonInterval::seconds(90), function ($job, $duration) { Log::warning("{$job->resolveName()} took {$duration}ms"); });
The callback listens to the JobAttempted event, so it also runs for attempts that fail or are released. A plain number is read as milliseconds, and the duration passed to the callback is in milliseconds.
A second PR adds Schedule::whenTaskRunsLongerThan() for scheduled tasks. It runs after a task finishes or fails, and here a plain number is read as seconds:
use Carbon\CarbonInterval; use Illuminate\Console\Scheduling\Event; use Illuminate\Support\Facades\Schedule; Schedule::whenTaskRunsLongerThan(CarbonInterval::minutes(45), function (Event $task, float $runtime) { Log::warning("Scheduled task [{$task->getSummaryForDisplay()}] took {$runtime}s"); });
See #62141 and #62233 for more details.
Commit IDs for Read Replicas
Tobias Petry added methods that tell you whether a read replica has received a specific write. Laravel 13.38 added the PreventStaleDatabaseReads middleware, which sends reads to the write server for a fixed time after a write. With commit IDs, you can check the replica instead of guessing how long the lag is:
use Illuminate\Support\Facades\DB; // After a write, on the primary $order = $user->orders()->create($attributes); $commitId = DB::getLastCommitId(); // Later, on a replica DB::hasCommitId($commitId); // Check now DB::hasCommitId($commitId, wait: 2); // Wait up to two seconds
A mergeCommitIds() method combines several commit IDs into one, so you can wait for all of them at once. The methods work on MySQL with GTIDs enabled, MariaDB with the binary log enabled, and PostgreSQL. Waiting on PostgreSQL requires PostgreSQL 19. Other drivers throw a RuntimeException.
See #62198 for more details.
Insert Middleware Before or After a Built-In Middleware
Jamie York added insertBefore() and insertAfter() to the middleware configuration in bootstrap/app.php. Before this, running your middleware right after one of Laravel's defaults meant declaring the stack yourself:
->withMiddleware(function (Middleware $middleware) { $middleware->insertAfter(TrustProxies::class, StripCrossOriginReferer::class); $middleware->insertBefore(TrimStrings::class, SanitizeStrings::class); })
The insertBeforeInGroup() and insertAfterInGroup() methods do the same for a middleware group such as web. An earlier PR in this release also lets replace() and replaceInGroup() accept an array, which is what the new methods use.
See #62230 and #62261 for more details.
Strict Input Casting
Chris Morrell added a strict argument to the string(), str(), boolean(), integer(), and float() methods on requests and other classes that use the InteractsWithData trait. Without it, ?count=abc123 casts to 0, and an array such as ?name[]=chris throws an "Array to string conversion" error. With strict set, invalid values return the default:
// ?count=abc123 $request->integer('count', 5); // 0 $request->integer('count', 5, strict: true); // 5
In strict mode, boolean() only accepts 1, true, on, yes, 0, false, off, and no.
See #62254 for more details.
Concurrency Process Driver Security Fix
This release fixes a remote code execution issue in Concurrency::run() with the default process driver, reported by @Arr0way. The parent process read a task's result from the child process's output and unserialized it. A task that printed user-controlled text could write a fake result, which the parent then unserialized.
The child process now writes its result in a frame signed with a random key that the parent creates for each task. The parent ignores any frame that the key does not verify. The fix also stops the "Trying to access array offset on null" error that a task printing any output could cause. If your application uses Concurrency::run() with the process driver, update to 13.40.0.
See #62297 for more details.
Database Transaction Fixes
Volodya Kurshudyan fixed two transaction problems:
- When the connection drops inside a transaction, Laravel now discards the dead PDO and reconnects on the next query. Before, the old PDO still had an open transaction, and every later transaction failed with "There is already an active transaction" until a queue worker or Octane process restarted (#62211)
- A lock wait timeout or deadlock in a nested transaction now rolls back to that transaction's savepoint. Before, the nested writes were committed if the outer transaction caught the exception (#62248)
Scheduler and Queue Worker Output
Jack Bayliss also sent two changes to what the scheduler and queue worker report:
- The scheduler now dispatches
ScheduledTaskSkippedwhen anonOneServer()task already ran on another server (#62256) - The
queue:workcommand showsRELEASEDinstead ofDONEfor jobs released back to the queue, such as by theWithoutOverlappingorRateLimitedmiddleware (#62180)
Release Notes
You can see the complete list of new features and updates below and the diff between 13.39.0 and 13.40.0 on GitHub. The following release notes are directly from the changelog:
v13.40.0
- [13.x] Fix Paginator::links() return type by @dhrupo in #62226
- [13.x] Fix updating JSON paths with arrays and collections on MySQL and MariaDB by @xurshudyan in #62225
- [13.x] Use the write connection in BelongsToMany createOrFirst lookup by @xurshudyan in #62223
- [13.x] Drop check constraints when dropping columns on SQL Server by @xurshudyan in #62222
- [13.x] Centralize + improve tests for
InteractsWithDataby @inxilpro in #62217 - [13.x] Fix Redis tagged cache flush blocking Redis when flushing many tags by @MohannadElemary in #62215
- [13.x] Qualify dropped and renamed indexes with the table schema on PostgreSQL by @xurshudyan in #62213
- [13.x] Reconnect after losing the connection during a transaction by @xurshudyan in #62211
- [13.x] Add missing PostgreSQL full text languages by @xurshudyan in #62210
- [13.x] Add Queue::whenJobRunsLongerThan by @jackbayliss in #62141
- [13.x] Fix named arguments in Cookie::queue() by @xurshudyan in #62246
- [13.x] Fix ImageFile::default() returning a plain File rule by @Marzaan in #62244
- [13.x] Fix pagination contracts render() return type by @dhrupo in #62242
- [13.x] Fix SQL Server ignoring orderBy on update and delete with limit by @xurshudyan in #62239
- [13.x] Preserve error_get_last() for suppressed errors by @xurshudyan in #62228
- Prepare reusable development environments for Amp orbs by @DGarbs51 in #62231
- Allow
$middleware->replace()to accept an array of replacements by @ziadoz in #62230 - [13.x] Show released jobs as released in queue:work output by @jackbayliss in #62180
- Add
$middleware->insertBefore()and$middleware->insertAfter()helper methods by @ziadoz in #62261 - [13.x] Reconnect the Cloud log socket after a failed write by @jamesdangercarpenter in #62258
- [13.x] Dispatch
ScheduledTaskSkippedwhen a task already ran on another server by @jackbayliss in #62256 - [13.x] Fix forceIndex() generating invalid SQL on SQLite for PRIMARY and multiple indexes by @tharlei in #62255
- [13.x] Add Schedule::whenTaskRunsLongerThan by @jackbayliss in #62233
- [13.x] Roll back to savepoint on concurrency errors in nested transactions by @xurshudyan in #62248
- [13.x] Fix
toResourceCollection()on filtered collections by @xurshudyan in #62266 - Fix lazily loaded images not being freed until garbage collection by @tamc in #62265
- Bump stefanzweifel/git-auto-commit-action from 7.2.0 to 7.2.1 in the github-actions group by @dependabot[bot] in #62288
- KeyGenerateCommand: improve help message about '--show' by @valerio-bozzolan in #62286
- [13.x] Record failed batch jobs when the model is missing by @xurshudyan in #62280
- [13.x] Fix broadcasting to multiple encrypted Pusher channels by @xurshudyan in #62279
- [13.x] Fix Str::remove() rejecting a Traversable subject by @Amirhf1 in #62278
- [13.x] Fix Str::substrCount() treating offset and length as bytes by @Amirhf1 in #62277
- [13.x] Fix
withExists()andloadExists()on SQL Server by @xurshudyan in #62273 - [13.x] Fix
cursorPaginate()columns on union queries with multiple orders by @xurshudyan in #62272 - [13.x] Fix @param types wider than the native parameter type by @dhrupo in #62271
- [13.x] Detect QueryList.TerminateAll() as lost connection by @jackbayliss in #62270
- [13.x] Add a
strictflag toInteractsWithDataby @inxilpro in #62254 - [13.x] database fundamentals to know if a read connection has catched up to a write connection by @tpetry in #62198
- [13.x] Fix CommandSignatureTest by @jackbayliss in #62295
- [13.x] Fix JSON:API resource collections wrapping plain objects by @lazerg in #62299
- [13.x] Fix: Authenticate the Concurrency Process Result Envelope to Prevent Task Output Injection by @Arr0way in #62297