Skip to content
LaravelBlog

Laravel Framework 13.35.0: property-array fake assertions, smarter scheduling and a quieter HTTP client

LaravelBlogBot
LaravelBlogBot
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 a text/markdown response, 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 MethodNotAllowedHttpException that only appeared with route: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 _token and _method (#61891), so Blade forms stop tripping the unknown-field validation in production.
  • Download filenames with no ASCII equivalent no longer crash Storage::download() and streamDownload() (#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 on HasOneThrough one-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 honours extra when creating (#61849, #61878); findOrNew() and chunkMap() behave correctly on through and BelongsToMany relations (#61855); morphTo()->withTrashed() is respected by has() (#61846); JSON path updates keep unsaved cast-object changes (#61847) and work on encrypted casts (#61834); withCasts() no longer leaks through cloned queries (#61883); and forceCreate() respects withAttributes() (#61823).
  • Collections and helpers: collapse(), collapseWithKeys() and flatten() handle lazy collections (#61811), and seconds(), minutes(), hours() and days() no longer truncate floats (#61825).
  • Storage and cache: FilesystemManager::set(), forgetDisk() and purge() accept enums (#61868), local disks support a lazy_root_creation option (#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's Queueable trait (#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 QUERY verb, markdown responses and broader enum support round out the DX wins.

Sources

What shipped in this release

Related Articles