News

Route::query() and Model defaults() in Laravel 13.35

Published
Route::query() and Model defaults() in Laravel 13.35 image

The Laravel team released v13.35.0 with Route::query() for HTTP QUERY routes, an opt-in defaults() method for Eloquent models, property arrays in the queue, bus, event, and notification fake assertions, percentage memory limits for queue:work, two scheduler additions for multi-server setups and deploys, and more.

Route::query()

Joel Butcher brought Route::query() to Laravel 13. It registers a route for the HTTP QUERY method, a safe method that carries a request body, which suits search endpoints with filters too large for a query string:

Route::query('/products/search', SearchProductsController::class);

Laravel 13.19 added Http::query() and the query() and queryJson() testing helpers, but defining the route meant Route::match(['QUERY'], ...). QUERY is now in the router's list of verbs, so Route::any() and Route::redirect() also match it.

Unlike the version merged for Laravel 14, it leaves the Registrar contract and CSRF protection still verifies QUERY requests the same as POST requests. A QUERY route in the web middleware group needs a CSRF token, or an entry in the except list. Our tutorial on the HTTP QUERY method with Scout search covers the client side.

See #61797 for more details.

Model defaults() With HasDefaultAttributes

Wes Hooper brought the model defaults() method from Laravel 14 to Laravel 13 as an opt-in trait. defaults() is the method version of the $attributes property, so default values can come from config, feature flags, or the current date. In Laravel 13, a model has to use the HasDefaultAttributes trait for the method to run:

use Illuminate\Database\Eloquent\Concerns\HasDefaultAttributes;
 
class Subscription extends Model
{
use HasDefaultAttributes;
 
protected function defaults(): array
{
return [
'cancelled' => false,
'trial_days' => Feature::active('extended-trials') ? 30 : 14,
];
}
}

The trait keeps the change from affecting models that already have a method named defaults(). Values from defaults() win over $attributes, and they are raw, so they skip casts and mutators.

See #61813 for more details.

Property Arrays in Fake Assertions

Jack Bayliss updated the Queue, Bus, Event, and Notification fakes to accept an array of expected property values where they accept a callback:

// Before
Queue::assertPushed(ProcessPodcast::class, function (ProcessPodcast $job) use ($podcast, $user) {
return $job->podcast->is($podcast)
&& $job->user->is($user)
&& $job->status === 'pending';
});
 
// After
Queue::assertPushed(ProcessPodcast::class, [
'podcast' => $podcast,
'user' => $user,
'status' => 'pending',
]);

Each value is compared with strict equality, except Eloquent models, which are compared with is(). The properties need to be public, since the fake reads them from outside the object.

See #61770 for more details.

Percentage Memory Limits for queue:work

Jack Bayliss also updated the --memory option on queue:work to accept a percentage of PHP's memory_limit, so the worker limit follows the container size instead of a fixed number of megabytes:

php artisan queue:work --memory=60%

With a memory_limit of 512M, the worker stops after a job once it uses about 307 MB. If memory_limit is -1, which is common for the PHP CLI, the percentage resolves to a negative number and the worker never stops for memory. Set memory_limit for the worker process if you use a percentage.

See #61753 for more details.

Schedule::alwaysOnOneServer()

A scheduler running on several servers or replicas runs every task on each of them, unless the task calls onOneServer(). A third PR from Jack Bayliss adds Schedule::alwaysOnOneServer(), which applies onOneServer() to every scheduled task:

// AppServiceProvider::boot()
Schedule::alwaysOnOneServer();

Until now, the option was to wrap every task in routes/console.php in a Schedule::onOneServer()->group() call. Closure tasks without a name are skipped, because onOneServer() needs a name to build the lock. Like onOneServer(), it needs a cache store that every server shares.

See #61789 for more details.

Stop Long-Running Scheduled Commands on Deploy

php artisan schedule:interrupt stops the sub-minute loop in schedule:run, but a command started with runInBackground() never saw it. cyppe changed the interrupt to store a timestamp, the same way queue:restart works, and added Schedule::hasBeenInterruptedSince() so a long command can stop at a safe point:

use Illuminate\Console\Scheduling\Schedule;
 
public function handle(Schedule $schedule)
{
$startedAt = now();
 
foreach ($this->batches() as $batch) {
if ($schedule->hasBeenInterruptedSince($startedAt)) {
return;
}
 
$this->process($batch);
}
}

schedule:run uses the same check, so an interrupt from earlier in the minute no longer stops a run that starts after it.

See #61764 for more details.

response()->markdown()

Ashot Gharakeshishyan added a markdown() method to the response factory, which pairs with the request's existing wantsMarkdown() method:

return $request->wantsMarkdown()
? response()->markdown($post->markdown)
: view('posts.show', ['post' => $post]);

It returns a regular response with a text/markdown content type, and accepts a status and headers like response()->make().

See #61800 for more details.

Lazy Root Creation for Local Disks

Jamie York added a lazy_root_creation option for local disks. By default, Flysystem's local adapter creates the root directory when the disk is built. With the option set to true, the directory is created when the first file is written:

'reports' => [
'driver' => 'local',
'root' => storage_path('app/reports'),
'lazy_root_creation' => true,
],

See #61799 for more details.

Release Notes

You can see the complete list of new features and updates below and the diff between 13.34.0 and 13.35.0 on GitHub. The following release notes are directly from the changelog:

v13.35.0

  • [13.x] Allow fake assertions to accept an array of properties by @jackbayliss in #61770
  • [13.x] Support percentages for queue:work --memory by @jackbayliss in #61753
  • [13.x] Let long-running scheduled commands see schedule:interrupt by @cyppe in #61764
  • [13.x] Make the parallel compiled view callbacks tolerate a concurrent run by @joshdaugherty in #61786
  • [13.x] Add markdown() method to the response factory by @Ashot1995 in #61800
  • [13.x] Add onOneServer to the schedule:list --json output by @MizouziE in #61798
  • [13.x] Add lazy_root_creation configuration option for local disks by @ziadoz in #61799
  • [13.x] Test Improvements by @crynobone in #61790
  • Exception enhancements by @timacdonald in #61791
  • [13.x] Add Schedule::alwaysOnOneServer(); by @jackbayliss in #61789
  • [13.x] add test for notifyJobOfSignal SIGALRM by @jackbayliss in #61787
  • [13.x] Optimize Str::finish and Str::start by @HamidRezaLS in #61803
  • [13.x] Fix reference cycle from PendingRequest::throw() default callback by @andrewnabors in #61802
  • [13.x] Fix Memcached lock isLocked() on a free lock by @mrjavadseydi in #61801
  • [13.x] Add QUERY route method support by @joelbutcher in #61797
  • [13.x] Fix DatabaseRule where() and whereNot() losing false values by @sajjadhossainshohag in #61796
  • [13.x] Fix duration helpers truncating float values by @xurshudyan in #61825
  • [13.x] Release unique job locks in Queue::fakeFor() by @xurshudyan in #61824
  • [13.x] Fix forceCreate() ignoring withAttributes() by @xurshudyan in #61823
  • [13.x] Fix has() and withCount() on HasOneThrough one-of-many relations by @xurshudyan in #61818
  • [13.x] Accept null password in hashPasswordForCookie by @troioi-vn in #61817
  • [13.x] Fix chunkById() and lazyById() looping forever on models with a cast primary key by @xurshudyan in #61816
  • [13.x] Fix custom MorphPivot not touching owners on detach by @mrjavadseydi in #61812
  • [13.x] Fix collapse(), collapseWithKeys() and flatten() dropping lazy collections by @ahmadreza-fatemikia in #61811
  • [13.x] Fix DB::setDefaultConnection() storing enums in the configuration by @ahmadreza-fatemikia in #61810
  • [13.x] normalize queue in SQS bulk method by @jackbayliss in #61828
  • [13.x] Fix HasOneThrough::is() comparing the wrong keys by @xurshudyan in #61827
  • [13.x] Fix inherited queue attributes being ignored when the child uses Queueable by @xurshudyan in #61826
  • [13.x] Fix JSON path updates on AsEncryptedArrayObject and AsEncryptedCollection by @xurshudyan in #61834
  • Fix root route matching with route cache when served from a subdirectory by @Simon0Harms in #61794
  • Address query issues by @taylorotwell in #61837
  • [13.x] Document enum support for disk in copyToDisk() and moveToDisk() by @riesjart in #61867
  • [13.x] Skip building a query in loadMissing() when no relations are given by @MAboyadak in #61869
  • [13.x] Accept enums in FilesystemManager::set(), forgetDisk() and purge() by @riesjart in #61868
  • [13.x] Fix parallel testing with PHPUnit 13.4 by @lazerg in #61859
  • [13.x] Fix AsHtmlString attributes serializing to an empty object by @xurshudyan in #61854
  • [13.x] Fix whereJsonOverlaps() with a JSON path on MySQL by @xurshudyan in #61853
  • [13.x] Fix reference cycle from Mailable headers() and priority() callbacks by @xurshudyan in #61863
  • [13.x] Fix findOrNew() and chunkMap() on HasManyThrough and BelongsToMany by @xurshudyan in #61855
  • [13.x] Fix incrementOrCreate() not setting the foreign key on relations by @xurshudyan in #61849
  • [13.x] Fix loadMissing() with nested relations on mixed morphTo models by @xurshudyan in #61845
  • [13.x] Fix Eloquent Collection findOrFail() with a collection of ids by @xurshudyan in #61844
  • [13.x] Fix cursorPaginate() losing union select and join bindings by @xurshudyan in #61841
  • [13.x] Fix docblocks of logoutOtherDevices method by @imanghafoori1 in #61877
  • [13.x] Fix JSON path updates discarding unsaved changes on cast objects by @xurshudyan in #61847
  • [13.x ] never return type for failedBasicResponse method by @imanghafoori1 in #61887
  • [13.x] Fix downloads crashing on filenames with no ASCII equivalent by @jhm-ciberman in #61888
  • [13.x] Qualify eager loaded columns on through and one-of-many relations by @xurshudyan in #61885
  • [13.x] Fix recycled models leaking into later calls on the same factory by @xurshudyan in #61884
  • [13.x] Fake the pwnedpasswords API in ValidationPasswordRuleTest::testMessagesOrder by @xurshudyan in #61880
  • [13.x] Fix morphTo()->withTrashed() being ignored by has() and whereHasMorph() by @xurshudyan in #61846
  • [13.x] Fix incrementOrCreate() ignoring extra attributes when creating by @xurshudyan in #61878
  • [13.x] Fix undefined array key in GenericUser::getAuthPassword() for users without a password by @tommie1001 in #61876
  • [13.x] Database test improvements by @jnoordsij in #61819
  • [13.x] Port defaults() via opt-in trait by @weshooper in #61813
  • Bump source-map-js from 1.2.1 to 1.2.2 in /src/Illuminate/Foundation/resources/exceptions/renderer by @dependabot in #61889
  • [13.x] Fix schedule:list timezone conversion when the next runs straddle a DST change by @xiCO2k in #61894
  • [13.x] Don't expand URL templates without URL parameters by @xurshudyan in #61893
  • [13.x] Ignore _token and _method when failing on unknown fields by @xurshudyan in #61891
  • [13.x] Fix withCasts() on a cloned query changing the casts of the original query by @xurshudyan in #61883
Paul Redmond photo

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

Sponsored

masteringlaravel logo
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review

The latest

View all →
Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat image

Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat

Read article
VMPal: Give Your AI Agent a Whole Computer image

VMPal: Give Your AI Agent a Whole Computer

Read article
Synapse: A Dev Dashboard for Laravel AI SDK Agents image

Synapse: A Dev Dashboard for Laravel AI SDK Agents

Read article
Decide with Jev: Three Questions in One Request with the Laravel AI SDK image

Decide with Jev: Three Questions in One Request with the Laravel AI SDK

Read article
Grant: Enum-Based Roles and Permissions for Laravel image

Grant: Enum-Based Roles and Permissions for Laravel

Read article
Securing Filament plugins with Plumb image

Securing Filament plugins with Plumb

Read article