Laravel Framework 13.35.0: property-array fake assertions, smarter scheduling and a quieter HTTP client
Laravel Framework v13.35.0 blends a handful of genuinely useful developer-experience additions with a long tail of correctness fixes. Testing fakes can now be asserted against with plain property arrays, the scheduler gains a global "run on one server" switch plus a proper interrupt mechanism for long-running commands, and Eloquent models can declare default attributes through an opt-in trait. Alongside those features sit two reference-cycle memory leak fixes that matter for queue workers and Octane, a batch of Eloquent corrections around relations and pagination, and polish across routing, HTTP and storage.
Cleaner fake assertions with property arrays
Until now, asserting on a faked job or event meant writing a closure for every assertion, usually importing several variables with use. PR #61770 lets you pass an array of properties instead:
Queue::assertPushed(ProcessPodcast::class, [
'podcast' => $podcast,
'user' => $user,
'status' => 'pending',
]);
This works across the Queue, Bus, Event and Notification fakes, including assertNotPushed, assertPushedOn and the dispatched() helpers, and models are compared with is() rather than strict equality. It is a small change that removes a lot of noise from test suites that dispatch many similar jobs.
Scheduling: one server, graceful interrupts, better introspection
Two scheduler additions headline this release. Schedule::alwaysOnOneServer() (#61789) applies onOneServer() to every scheduled event from a single line in your service provider, instead of wrapping the entire routes/console.php file in a group. It is off by default, and unnamed scheduled closures are skipped since they cannot be locked.
Second, long-running scheduled commands can now respond to schedule:interrupt (#61764). The interrupt command previously stored an expiring boolean that detached, background commands could never see; it now records a timestamp, and a new $schedule->hasBeenInterruptedSince($startedAt) check lets a command exit at a safe point during deploys. An interrupt also no longer bleeds into a later run in the same minute.
Introspection improves too: schedule:list --json now includes an on_one_server key (#61798), and the timezone conversion no longer gives up and mislabels the expression as UTC when the next runs straddle a DST change (#61894).
Model defaults() through an opt-in trait
Eloquent gains a HasDefaultAttributes trait (#61813). Models using it can define a defaults() method whose values are merged into new model instances at construction, taking precedence over the $attributes property and overridable by constructor arguments. Defaults are not applied to models hydrated from the database, and they are re-evaluated for each instance, so runtime values work. Because it is strictly opt-in, there is no behavioural change for existing models.
Routing and HTTP: the QUERY verb and friends
Routing gains first-class support for the HTTP QUERY method (#61797), so search-style endpoints can carry a request body safely: Route::query('/products/search', ...). It participates in Route::any() and route:list, and remains CSRF-verified like other body-carrying methods.
Other HTTP and routing improvements:
response()->markdown()(#61800) returns atext/markdownresponse, pairing nicely with$request->wantsMarkdown()for content negotiation.- Cached routes now match the root URL when the app is served from a subdirectory (#61794), fixing a
MethodNotAllowedHttpExceptionthat only appeared withroute:cache. - The HTTP client no longer expands URL templates when
withUrlParameters()was not used (#61893), so JSON inside query strings like?where={"status":"open"}survives intact. failOnUnknownFields()now ignores_tokenand_method(#61891), so Blade forms stop tripping the unknown-field validation in production.- Download filenames with no ASCII equivalent no longer crash
Storage::download()andstreamDownload()(#61888).
Two quiet memory leaks and percentage memory limits
Long-running processes get real relief this release. The default callback in PendingRequest::throw() was a non-static closure capturing $this, keeping every response — body included — in memory until PHP's cycle collector happened to run (#61802). The same pattern in Mailable::priority() and custom headers() could leak hundreds of megabytes when sending many mails with attachments (#61863); both are now static closures.
For workers, queue:work --memory accepts percentages such as --memory=60% (#61753), scaling the kill threshold with the PHP memory_limit so container memory changes do not require recalculating worker flags.
Everything else
The remaining changes are mostly fixes worth knowing about:
- Eloquent:
chunkById()/lazyById()no longer loop forever on cast primary keys (#61816);has()/withCount()work onHasOneThroughone-of-many relations (#61818);cursorPaginate()keeps union select and join bindings (#61841);whereJsonOverlaps()with a JSON path is fixed on MySQL (#61853);incrementOrCreate()works on relations and honoursextrawhen creating (#61849, #61878);findOrNew()andchunkMap()behave correctly on through andBelongsToManyrelations (#61855);morphTo()->withTrashed()is respected byhas()(#61846); JSON path updates keep unsaved cast-object changes (#61847) and work on encrypted casts (#61834);withCasts()no longer leaks through cloned queries (#61883); andforceCreate()respectswithAttributes()(#61823). - Collections and helpers:
collapse(),collapseWithKeys()andflatten()handle lazy collections (#61811), andseconds(),minutes(),hours()anddays()no longer truncate floats (#61825). - Storage and cache:
FilesystemManager::set(),forgetDisk()andpurge()accept enums (#61868), local disks support alazy_root_creationoption (#61799), and Memcached locks no longer report free locks as locked (#61801). - Queueing:
Queue::fakeFor()releases unique job locks (#61824), inherited queue attributes survive a child'sQueueabletrait (#61826), and SQS bulk pushes normalize enum queue names (#61828). - Testing: parallel testing works with PHPUnit 13.4 (#61859) and tolerates concurrent compiled view directories (#61786).
Takeaways
- Upgrade for the property-array fake assertions, scheduler controls and the
defaults()trait. - If you run queue workers or Octane, the two memory leak fixes are the headline.
- Eloquent through-relation, chunking and cursor pagination edge cases are much healthier.
- The
QUERYverb, markdown responses and broader enum support round out the DX wins.
Sources
- Release v13.35.0
- Pull request #61753
- Pull request #61764
- Pull request #61770
- Pull request #61786
- Pull request #61787
- Pull request #61789
- Pull request #61790
- Pull request #61791
- Pull request #61794
- Pull request #61796
- Pull request #61797
- Pull request #61798
- Pull request #61799
- Pull request #61800
- Pull request #61801
- Pull request #61802
- Pull request #61803
- Pull request #61810
- Pull request #61811
- Pull request #61812
- Pull request #61813
- Pull request #61816
- Pull request #61817
- Pull request #61818
- Pull request #61819
- Pull request #61823
- Pull request #61824
- Pull request #61825
- Pull request #61826
- Pull request #61827
- Pull request #61828
- Pull request #61834
- Pull request #61837
- Pull request #61841
- Pull request #61844
- Pull request #61845
- Pull request #61846
- Pull request #61847
- Pull request #61849
- Pull request #61853
- Pull request #61854
- Pull request #61855
- Pull request #61859
- Pull request #61863
- Pull request #61867
- Pull request #61868
- Pull request #61869
- Pull request #61876
- Pull request #61877
- Pull request #61878
- Pull request #61880
- Pull request #61883
- Pull request #61884
- Pull request #61885
- Pull request #61887
- Pull request #61888
- Pull request #61889
- Pull request #61891
- Pull request #61893
- Pull request #61894