From 1751d2c33250211883fb8097c5c4598baaba3ab4 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:41:08 +0000 Subject: [PATCH 001/153] Update CHANGELOG --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 236cd456a6f9..37a52dc09819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.36.1...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.37.0...12.x) + +## [v12.37.0](https://github.com/laravel/framework/compare/v12.36.1...v12.37.0) - 2025-11-04 + +* [12.x] allow passing custom "depth" to `files()` and `directories()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/57573 +* [12.x] EnumerateValues::value() support objects and return negative values by [@rafaelqueiroz](https://github.com/rafaelqueiroz) in https://github.com/laravel/framework/pull/57570 +* [12.x] Move duplicated logic to separate method to be reusable by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/57564 +* [12.x] Refactor unreleased data_has helper by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/57580 +* feat: added detailed about for cache failover driver by [@chinmaypurav](https://github.com/chinmaypurav) in https://github.com/laravel/framework/pull/57579 +* [12.x] fix data_has empty check by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/57586 +* [12.x] Fix: use trim before check empty string by [@alipowerful7](https://github.com/alipowerful7) in https://github.com/laravel/framework/pull/57583 +* feat: added detailed about for queue failover driver by [@chinmaypurav](https://github.com/chinmaypurav) in https://github.com/laravel/framework/pull/57582 +* Feat: add mailers detail for failover or roundrobin by [@chinmaypurav](https://github.com/chinmaypurav) in https://github.com/laravel/framework/pull/57590 +* [12.x] Refactor: remove un use var by [@alipowerful7](https://github.com/alipowerful7) in https://github.com/laravel/framework/pull/57617 +* [12.x] `Factory@insert()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57600 +* Fix ScheduleRunCommandTest failure on Windows by using OS-specific success command by [@Tina-1300](https://github.com/Tina-1300) in https://github.com/laravel/framework/pull/57621 +* [12.x] Add ucwords to Str and Stringable by [@braxey](https://github.com/braxey) in https://github.com/laravel/framework/pull/57581 +* [12.x] improve `Connection@listen()` docblock by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57633 +* [12.x] Fix: Correctly fallback to notification's connection/queue when using viaConnections/viaQueues by [@aydinfatih](https://github.com/aydinfatih) in https://github.com/laravel/framework/pull/57625 +* [12.x] Remove unused closure parameters in DatabaseServiceProvider by [@sumaiazaman](https://github.com/sumaiazaman) in https://github.com/laravel/framework/pull/57644 +* [12.x] Queue tests for Redis Cluster missing QUEUE_CONNECTION by [@vadimonus](https://github.com/vadimonus) in https://github.com/laravel/framework/pull/57641 +* refactor: remove unused parameter in ArtisanServiceProvider by [@omarchouman](https://github.com/omarchouman) in https://github.com/laravel/framework/pull/57658 +* [12.x] Ensure custom validation messages work for the File rule by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/57656 +* Feature/json schema improvements by [@Anticom](https://github.com/Anticom) in https://github.com/laravel/framework/pull/57609 +* Process queue jobs in background (Concurrently::defer()) by [@barryvdh](https://github.com/barryvdh) in https://github.com/laravel/framework/pull/57648 +* [12.x] ChainedBatch keeps queue and connection of wrapped batch by [@vadimonus](https://github.com/vadimonus) in https://github.com/laravel/framework/pull/57630 ## [v12.36.1](https://github.com/laravel/framework/compare/v12.36.0...v12.36.1) - 2025-10-29 From 810d6f47f547212f4ef233de3898ce3d0dfc920c Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:34:42 -0500 Subject: [PATCH 002/153] Update Application.php (#57665) --- src/Illuminate/Foundation/Application.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 3b53cfc4b88e..b331453bff9c 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1302,7 +1302,11 @@ public function getCachedPackagesPath() */ public function configurationIsCached() { - return is_file($this->getCachedConfigPath()); + if ($this->bound('config_loaded_from_cache')) { + return (bool) $this->make('config_loaded_from_cache'); + } + + return $this->instance('config_loaded_from_cache', is_file($this->getCachedConfigPath())); } /** From 7f98d1296ab6e90e822fa81450bf1f7787d00c1c Mon Sep 17 00:00:00 2001 From: Razin Shaikh <46933565+mrazinshaikh@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:03:52 +0530 Subject: [PATCH 003/153] [12.x] model:show command prompt for missing input with model suggestion (#57671) * Refactor GeneratorCommand and ShowModelCommand to use InteractsWithModels trait for model interaction. Moved possibleModels method to the new trait for better code organization. * formatting and type improvements * formatting * formatting --------- Co-authored-by: Taylor Otwell --- .../Console/Concerns/FindsAvailableModels.php | 25 +++++++++++++++++++ src/Illuminate/Console/GeneratorCommand.php | 19 +++----------- .../Database/Console/ShowModelCommand.php | 20 ++++++++++++++- .../Console/ObserverMakeCommand.php | 4 +-- .../Foundation/Console/PolicyMakeCommand.php | 2 +- .../Routing/Console/ControllerMakeCommand.php | 4 +-- 6 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 src/Illuminate/Console/Concerns/FindsAvailableModels.php diff --git a/src/Illuminate/Console/Concerns/FindsAvailableModels.php b/src/Illuminate/Console/Concerns/FindsAvailableModels.php new file mode 100644 index 000000000000..e2a81e62b4f0 --- /dev/null +++ b/src/Illuminate/Console/Concerns/FindsAvailableModels.php @@ -0,0 +1,25 @@ + + */ + protected function findAvailableModels() + { + $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path(); + + return (new Collection(Finder::create()->files()->depth(0)->in($modelPath))) + ->map(fn ($file) => $file->getBasename('.php')) + ->sort() + ->values() + ->all(); + } +} diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index 5b6af51a576b..a66a5e3b9747 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -3,6 +3,7 @@ namespace Illuminate\Console; use Illuminate\Console\Concerns\CreatesMatchingTest; +use Illuminate\Console\Concerns\FindsAvailableModels; use Illuminate\Contracts\Console\PromptsForMissingInput; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Collection; @@ -12,6 +13,8 @@ abstract class GeneratorCommand extends Command implements PromptsForMissingInput { + use FindsAvailableModels; + /** * The filesystem instance. * @@ -239,22 +242,6 @@ protected function qualifyModel(string $model) : $rootNamespace.$model; } - /** - * Get a list of possible model names. - * - * @return array - */ - protected function possibleModels() - { - $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path(); - - return (new Collection(Finder::create()->files()->depth(0)->in($modelPath))) - ->map(fn ($file) => $file->getBasename('.php')) - ->sort() - ->values() - ->all(); - } - /** * Get a list of possible event names. * diff --git a/src/Illuminate/Database/Console/ShowModelCommand.php b/src/Illuminate/Database/Console/ShowModelCommand.php index 3e99153756bf..35eaf69f8250 100644 --- a/src/Illuminate/Database/Console/ShowModelCommand.php +++ b/src/Illuminate/Database/Console/ShowModelCommand.php @@ -2,15 +2,21 @@ namespace Illuminate\Database\Console; +use Illuminate\Console\Concerns\FindsAvailableModels; +use Illuminate\Contracts\Console\PromptsForMissingInput; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\ModelInspector; use Illuminate\Support\Collection; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Output\OutputInterface; +use function Laravel\Prompts\suggest; + #[AsCommand(name: 'model:show')] -class ShowModelCommand extends DatabaseInspectionCommand +class ShowModelCommand extends DatabaseInspectionCommand implements PromptsForMissingInput { + use FindsAvailableModels; + /** * The console command name. * @@ -211,4 +217,16 @@ protected function displayCli($class, $database, $table, $policy, $attributes, $ $this->newLine(); } + + /** + * Prompt for missing input arguments using the returned questions. + * + * @return array + */ + protected function promptForMissingArgumentsUsing(): array + { + return [ + 'model' => fn (): string => suggest('Which model would you like to show?', $this->findAvailableModels()), + ]; + } } diff --git a/src/Illuminate/Foundation/Console/ObserverMakeCommand.php b/src/Illuminate/Foundation/Console/ObserverMakeCommand.php index 2193d8583fd1..65bb38ee4a00 100644 --- a/src/Illuminate/Foundation/Console/ObserverMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ObserverMakeCommand.php @@ -158,8 +158,8 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp } $model = suggest( - 'What model should this observer apply to? (Optional)', - $this->possibleModels(), + 'What model should be observed? (Optional)', + $this->findAvailableModels(), ); if ($model) { diff --git a/src/Illuminate/Foundation/Console/PolicyMakeCommand.php b/src/Illuminate/Foundation/Console/PolicyMakeCommand.php index 521c135b062b..96caa392dcd6 100644 --- a/src/Illuminate/Foundation/Console/PolicyMakeCommand.php +++ b/src/Illuminate/Foundation/Console/PolicyMakeCommand.php @@ -218,7 +218,7 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp $model = suggest( 'What model should this policy apply to? (Optional)', - $this->possibleModels(), + $this->findAvailableModels(), ); if ($model) { diff --git a/src/Illuminate/Routing/Console/ControllerMakeCommand.php b/src/Illuminate/Routing/Console/ControllerMakeCommand.php index dcf855c3f806..3d6886fbc1f9 100755 --- a/src/Illuminate/Routing/Console/ControllerMakeCommand.php +++ b/src/Illuminate/Routing/Console/ControllerMakeCommand.php @@ -328,8 +328,8 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp if (in_array($type, ['api', 'resource', 'singleton'])) { $model = suggest( - "What model should this $type controller be for? (Optional)", - $this->possibleModels() + "What model is this $type controller for? (Optional)", + $this->findAvailableModels() ); if ($model) { From 08f12a00330a9212f925e97bb1b32885418981c7 Mon Sep 17 00:00:00 2001 From: Richard van Baarsen Date: Wed, 5 Nov 2025 16:36:52 +0100 Subject: [PATCH 004/153] Don't call Model::toArray() to get attributes for factory insert (#57670) --- src/Illuminate/Database/Eloquent/Factories/Factory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index b0ccdaaa1529..eddce390d7b6 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -471,7 +471,7 @@ public function insert(array $attributes = [], ?Model $parent = null): void $query = $model->newQueryWithoutScopes(); $query->fillAndInsert( - $madeCollection->withoutAppends()->toArray() + $madeCollection->map(fn (Model $model) => $model->getAttributes())->all() ); } From 08c1f932810bfbf7f2f362021f9190b60588fe02 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:54:29 -0500 Subject: [PATCH 005/153] [12.x] Introduce `WithCachedRoutes` testing trait (#57623) * with cached routes * clean up * clean up * try to guess how Taylor would format it * compile to array * Update WithCachedRoutes.php * I think this is better * comments * lighter check first * static * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Foundation/Application.php | 3 +- .../Foundation/Testing/CachedState.php | 8 +++ .../Foundation/Testing/TestCase.php | 5 ++ .../Foundation/Testing/WithCachedRoutes.php | 53 +++++++++++++++++++ .../Foundation/FoundationApplicationTest.php | 25 +++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/Illuminate/Foundation/Testing/CachedState.php create mode 100644 src/Illuminate/Foundation/Testing/WithCachedRoutes.php diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index b331453bff9c..146d811f886a 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1326,7 +1326,8 @@ public function getCachedConfigPath() */ public function routesAreCached() { - return $this['files']->exists($this->getCachedRoutesPath()); + return ($this->bound('routes.cached') && $this->make('routes.cached') === true) || + $this['files']->exists($this->getCachedRoutesPath()); } /** diff --git a/src/Illuminate/Foundation/Testing/CachedState.php b/src/Illuminate/Foundation/Testing/CachedState.php new file mode 100644 index 000000000000..79cd3e0e906b --- /dev/null +++ b/src/Illuminate/Foundation/Testing/CachedState.php @@ -0,0 +1,8 @@ +booting(fn () => $this->markRoutesCached($app)); + } + $app->make(Kernel::class)->bootstrap(); return $app; diff --git a/src/Illuminate/Foundation/Testing/WithCachedRoutes.php b/src/Illuminate/Foundation/Testing/WithCachedRoutes.php new file mode 100644 index 000000000000..05b57052ef4d --- /dev/null +++ b/src/Illuminate/Foundation/Testing/WithCachedRoutes.php @@ -0,0 +1,53 @@ +app['router']->getRoutes(); + + $routes->refreshNameLookups(); + $routes->refreshActionLookups(); + + CachedState::$cachedRoutes = $routes->compile(); + } + + $this->markRoutesCached($this->app); + } + + /** + * Reset the route service provider so it's not defaulting to loading cached routes. + * + * This is helpful if some of the tests in the suite apply this trait while others do not. + * + * @return void + */ + protected function tearDownWithCachedRoutes(): void + { + RouteServiceProvider::loadCachedRoutesUsing(null); + } + + /** + * Inform the container to treat routes as cached. + */ + protected function markRoutesCached(Application $app): void + { + $app->instance('routes.cached', true); + + RouteServiceProvider::loadCachedRoutesUsing( + static fn () => app('router')->setCompiledRoutes(CachedState::$cachedRoutes) + ); + } + +} diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index d403e4a0702a..86c34b542f93 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -609,6 +609,31 @@ public function testAbortAcceptsHeaders() $this->assertSame(['X-FOO' => 'BAR'], $exception->getHeaders()); } } + + public function test_routes_are_cached() + { + $app = new Application(); + $app->instance('routes.cached', true); + $this->assertTrue($app->routesAreCached()); + } + + public function test_routes_are_not_cached_by_instance_falls_back_to_file() + { + $app = new Application(); + $files = new class + { + public string $pathRequested; + public function exists(string $path): bool + { + $this->pathRequested = $path; + return false; + } + }; + $app->instance('files', $files); + + $this->assertFalse($app->routesAreCached()); + $this->assertStringContainsString('routes-v7.php', $files->pathRequested); + } } class ApplicationBasicServiceProviderStub extends ServiceProvider From a7e7c8c22cdd16819fc697059df6b0e840490dfe Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 5 Nov 2025 22:54:57 +0000 Subject: [PATCH 006/153] Apply fixes from StyleCI --- src/Illuminate/Foundation/Testing/WithCachedRoutes.php | 1 - tests/Foundation/FoundationApplicationTest.php | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Testing/WithCachedRoutes.php b/src/Illuminate/Foundation/Testing/WithCachedRoutes.php index 05b57052ef4d..1a02a370b7a2 100644 --- a/src/Illuminate/Foundation/Testing/WithCachedRoutes.php +++ b/src/Illuminate/Foundation/Testing/WithCachedRoutes.php @@ -49,5 +49,4 @@ protected function markRoutesCached(Application $app): void static fn () => app('router')->setCompiledRoutes(CachedState::$cachedRoutes) ); } - } diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index 86c34b542f93..e41fc9320a35 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -623,9 +623,11 @@ public function test_routes_are_not_cached_by_instance_falls_back_to_file() $files = new class { public string $pathRequested; + public function exists(string $path): bool { $this->pathRequested = $path; + return false; } }; From 8cc0f326ff3bcbeb44be3e98e870ba49f72c742e Mon Sep 17 00:00:00 2001 From: Christophe Francey Date: Thu, 6 Nov 2025 15:27:18 +0100 Subject: [PATCH 007/153] [12.x] Add missing separators to `Stringable::ucwords` (#57688) * add missing separators to Stringable `ucwords` * Update Stringable.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Stringable.php | 5 +++-- tests/Support/SupportStringableTest.php | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 21fbe3fff860..67c4b9b5ddd5 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1110,11 +1110,12 @@ public function ucfirst() /** * Capitalize the first character of each word in a string. * + * @param string $separators * @return static */ - public function ucwords() + public function ucwords($separators = " \t\r\n\f\v") { - return new static(Str::ucwords($this->value)); + return new static(Str::ucwords($this->value, $separators)); } /** diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 8302193c596f..56dd427dd1da 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -180,6 +180,16 @@ public function testCanBeLimitedByWords() $this->assertSame('Taylor Otwell', (string) $this->stringable('Taylor Otwell')->words(3)); } + public function testUcwords() + { + $this->assertSame('Laravel', (string) $this->stringable('laravel')->ucwords()); + $this->assertSame('Laravel Framework', (string) $this->stringable('laravel framework')->ucwords()); + $this->assertSame('Laravel-Framework', (string) $this->stringable('laravel-framework')->ucwords('-')); + $this->assertSame('Мама', (string) $this->stringable('мама')->ucwords()); + $this->assertSame('Мама Мыла Раму', (string) $this->stringable('мама мыла раму')->ucwords()); + $this->assertSame('JJ Watt', (string) $this->stringable('JJ watt')->ucwords()); + } + public function testUnless() { $this->assertSame('unless false', (string) $this->stringable('unless')->unless(false, function ($stringable, $value) { From 89b94b9ce7da4d7126cc12ede39d900b3cc9f995 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:28:12 -0500 Subject: [PATCH 008/153] store result of routesAreCached (#57687) --- src/Illuminate/Foundation/Application.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 146d811f886a..ab90e663f6a1 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1326,8 +1326,11 @@ public function getCachedConfigPath() */ public function routesAreCached() { - return ($this->bound('routes.cached') && $this->make('routes.cached') === true) || - $this['files']->exists($this->getCachedRoutesPath()); + if ($this->bound('routes.cached')) { + return (bool) $this->make('routes.cached'); + } + + return $this->instance('routes.cached', $this['files']->exists($this->getCachedRoutesPath())); } /** From 48fbe2181a64373f2cb3b7b666f8df7a8aebab16 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Thu, 6 Nov 2025 08:23:09 -0700 Subject: [PATCH 009/153] fix phpdoc return type of HasAttributes::getArrayAttributeWithValue (#57691) Change incorrect phpdoc from `@return $this` to `@return array` --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 7591252b5148..55e9b60baf58 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1314,7 +1314,7 @@ protected function getStorableEnumValue($expectedEnum, $value) * @param string $path * @param string $key * @param mixed $value - * @return $this + * @return array */ protected function getArrayAttributeWithValue($path, $key, $value) { From 32650e9ce906e480eb93bc14139a6eadd843bb04 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Fri, 7 Nov 2025 23:14:07 +0700 Subject: [PATCH 010/153] Remove unnecessary imports from BackgroundQueue and DeferredQueue. (#57699) --- src/Illuminate/Queue/BackgroundQueue.php | 1 - src/Illuminate/Queue/DeferredQueue.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/Illuminate/Queue/BackgroundQueue.php b/src/Illuminate/Queue/BackgroundQueue.php index 863fd74bebaa..52353301f66f 100644 --- a/src/Illuminate/Queue/BackgroundQueue.php +++ b/src/Illuminate/Queue/BackgroundQueue.php @@ -2,7 +2,6 @@ namespace Illuminate\Queue; -use Illuminate\Contracts\Queue\Job; use Illuminate\Support\Facades\Concurrency; class BackgroundQueue extends SyncQueue diff --git a/src/Illuminate/Queue/DeferredQueue.php b/src/Illuminate/Queue/DeferredQueue.php index 14c92cee1cdd..b873a618c11e 100644 --- a/src/Illuminate/Queue/DeferredQueue.php +++ b/src/Illuminate/Queue/DeferredQueue.php @@ -2,8 +2,6 @@ namespace Illuminate\Queue; -use Illuminate\Contracts\Queue\Job; - class DeferredQueue extends SyncQueue { /** From 9d47f3e006466c6b9c368c954b5ed87a011dba02 Mon Sep 17 00:00:00 2001 From: Neil Carlo Sucuangco Date: Sat, 8 Nov 2025 04:14:25 +0800 Subject: [PATCH 011/153] [12.x] add SQLite support for whereNotMorphedTo method (#57698) * add SQLite support for whereNotMorphedTo method * Converts <=> operator to SQLite's IS operator in SQLiteGrammar --- .../Database/Query/Grammars/SQLiteGrammar.php | 19 +++++++++++++ .../Database/DatabaseEloquentBuilderTest.php | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 49a27724b465..d23e7e1e228d 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -43,6 +43,25 @@ protected function wrapUnion($sql) return 'select * from ('.$sql.')'; } + /** + * Compile a basic where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBasic(Builder $query, $where) + { + if ($where['operator'] === '<=>') { + $column = $this->wrap($where['column']); + $value = $this->parameter($where['value']); + + return "{$column} IS {$value}"; + } + + return parent::whereBasic($query, $where); + } + /** * Compile a "where like" clause. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index c031a6c80099..ec81afaac40c 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -2155,6 +2155,33 @@ public function testOrWhereNotMorphedToClass() $this->assertEquals(['baz', EloquentBuilderTestModelCloseRelatedStub::class], $builder->getBindings()); } + public function testWhereNotMorphedToWithSQLite() + { + $model = new EloquentBuilderTestModelParentStub; + $this->mockConnectionForModel($model, 'SQLite'); + + $relatedModel = new EloquentBuilderTestModelCloseRelatedStub; + $relatedModel->id = 1; + + $builder = $model->whereNotMorphedTo('morph', $relatedModel); + + $this->assertStringNotContainsString('<=>', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" IS ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql()); + $this->assertEquals([$relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings()); + } + + public function testWhereNotMorphedToClassWithSQLite() + { + $model = new EloquentBuilderTestModelParentStub; + $this->mockConnectionForModel($model, 'SQLite'); + + $builder = $model->whereNotMorphedTo('morph', EloquentBuilderTestModelCloseRelatedStub::class); + + $this->assertStringNotContainsString('<=>', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not "eloquent_builder_test_model_parent_stubs"."morph_type" IS ?', $builder->toSql()); + $this->assertEquals([EloquentBuilderTestModelCloseRelatedStub::class], $builder->getBindings()); + } + public function testWhereMorphedToAlias() { $model = new EloquentBuilderTestModelParentStub; From f1649db2e168355cb553a162cd545a1f8fafed27 Mon Sep 17 00:00:00 2001 From: Tom Westrick Date: Fri, 7 Nov 2025 15:17:43 -0500 Subject: [PATCH 012/153] Add READONLY to phpredis connection so it reconnects on failover (#57685) --- src/Illuminate/Redis/Connections/PhpRedisConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Redis/Connections/PhpRedisConnection.php b/src/Illuminate/Redis/Connections/PhpRedisConnection.php index 2f64317d209d..0ae07c9ca153 100644 --- a/src/Illuminate/Redis/Connections/PhpRedisConnection.php +++ b/src/Illuminate/Redis/Connections/PhpRedisConnection.php @@ -530,7 +530,7 @@ public function command($method, array $parameters = []) try { return parent::command($method, $parameters); } catch (RedisException $e) { - if (Str::contains($e->getMessage(), ['went away', 'socket', 'Error while reading', 'read error on connection', 'Connection lost'])) { + if (Str::contains($e->getMessage(), ['went away', 'socket', 'Error while reading', 'read error on connection', 'READONLY', 'Connection lost'])) { $this->client = $this->connector ? call_user_func($this->connector) : $this->client; } From cafe1ac75eb04c1292521444ef0aad0d202d38d8 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:57:07 -0500 Subject: [PATCH 013/153] [12.x] Introduce `WithCachedConfig` testing trait (#57663) * with cached routes * clean up * clean up * try to guess how Taylor would format it * compile to array * Update WithCachedRoutes.php * I think this is better * comments * lighter check first * static * formatting * wip * WithCachedConfig * thanks static analysis * Update TestCase.php * Update WithCachedConfig.php * Update TestCase.php * formatting * Update WithCachedConfig.php --------- Co-authored-by: Taylor Otwell --- .../Bootstrap/LoadConfiguration.php | 33 +++++++++++++-- .../Foundation/Testing/CachedState.php | 1 + .../InteractsWithTestCaseLifecycle.php | 2 +- .../Foundation/Testing/TestCase.php | 16 +++++++- .../Foundation/Testing/WithCachedConfig.php | 41 +++++++++++++++++++ 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 src/Illuminate/Foundation/Testing/WithCachedConfig.php diff --git a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php index 4c5f00e9a2c0..37229ac41a6a 100644 --- a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php +++ b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Bootstrap; +use Closure; use Illuminate\Config\Repository; use Illuminate\Contracts\Config\Repository as RepositoryContract; use Illuminate\Contracts\Foundation\Application; @@ -11,6 +12,13 @@ class LoadConfiguration { + /** + * The closure that resolves the permanent, static configuration if applicable. + * + * @var (Closure(Application): array)|null + */ + protected static ?Closure $alwaysUseConfig = null; + /** * Bootstrap the given application. * @@ -24,18 +32,26 @@ public function bootstrap(Application $app) // First we will see if we have a cache configuration file. If we do, we'll load // the configuration items from that file so that it is very quick. Otherwise // we will need to spin through every configuration file and load them all. - if (file_exists($cached = $app->getCachedConfigPath())) { + $loadedFromCache = false; + + if (self::$alwaysUseConfig !== null) { + $items = $app->call(self::$alwaysUseConfig); + + $loadedFromCache = true; + } elseif (file_exists($cached = $app->getCachedConfigPath())) { $items = require $cached; - $app->instance('config_loaded_from_cache', $loadedFromCache = true); + $loadedFromCache = true; } + $app->instance('config_loaded_from_cache', $loadedFromCache); + // Next we will spin through all of the configuration files in the configuration // directory and load each one into the repository. This will make all of the // options available to the developer for use in various parts of this app. $app->instance('config', $config = new Repository($items)); - if (! isset($loadedFromCache)) { + if (! $loadedFromCache) { $this->loadConfigurationFiles($app, $config); } @@ -195,4 +211,15 @@ protected function getBaseConfiguration() return $config; } + + /** + * Set a callback to return the permanent, static configuration values. + * + * @param (Closure(Application): array)|null $alwaysUseConfig + * @return void + */ + public static function alwaysUse(?Closure $alwaysUseConfig): void + { + static::$alwaysUseConfig = $alwaysUseConfig; + } } diff --git a/src/Illuminate/Foundation/Testing/CachedState.php b/src/Illuminate/Foundation/Testing/CachedState.php index 79cd3e0e906b..4ecb14cbb9b0 100644 --- a/src/Illuminate/Foundation/Testing/CachedState.php +++ b/src/Illuminate/Foundation/Testing/CachedState.php @@ -5,4 +5,5 @@ class CachedState { public static array $cachedRoutes; + public static array $cachedConfig; } diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php index 0daabf1ce139..860c7a4b47f5 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -203,7 +203,7 @@ protected function tearDownTheTestEnvironment(): void */ protected function setUpTraits() { - $uses = array_flip(class_uses_recursive(static::class)); + $uses = $this->traitsUsedByTest ?? array_flip(class_uses_recursive(static::class)); if (isset($uses[RefreshDatabase::class])) { $this->refreshDatabase(); diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index dbf8bbd1a4ac..0b5acfd37662 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -20,6 +20,13 @@ abstract class TestCase extends BaseTestCase Concerns\InteractsWithTestCaseLifecycle, Concerns\InteractsWithViews; + /** + * The list of trait that this test uses, fetched recursively. + * + * @var array + */ + protected array $traitsUsedByTest; + /** * Creates the application. * @@ -29,8 +36,15 @@ public function createApplication() { $app = require Application::inferBasePath().'/bootstrap/app.php'; + $this->traitsUsedByTest = array_flip(class_uses_recursive(static::class)); + + if (isset(CachedState::$cachedConfig) && + isset($this->traitsUsedByTest[WithCachedConfig::class])) { + $this->markConfigCached($app); + } + if (isset(CachedState::$cachedRoutes) && - in_array(WithCachedRoutes::class, class_uses_recursive(static::class))) { + isset($this->traitsUsedByTest[WithCachedRoutes::class])) { $app->booting(fn () => $this->markRoutesCached($app)); } diff --git a/src/Illuminate/Foundation/Testing/WithCachedConfig.php b/src/Illuminate/Foundation/Testing/WithCachedConfig.php new file mode 100644 index 000000000000..41d0cdcae3ca --- /dev/null +++ b/src/Illuminate/Foundation/Testing/WithCachedConfig.php @@ -0,0 +1,41 @@ +app->make('config')->all(); + } + + $this->markConfigCached($this->app); + } + + /** + * Reset the cached configuration. + * + * This is helpful if some of the tests in the suite apply this trait while others do not. + */ + protected function tearDownWithCachedConfig(): void + { + LoadConfiguration::setAlwaysUseConfig(null); + } + + /** + * Inform the container that the configuration is cached. + */ + protected function markConfigCached(Application $app): void + { + $app->instance('config_loaded_from_cache', true); // I'm not sure this is actually needed + + LoadConfiguration::alwaysUse(static fn () => CachedState::$cachedConfig); + } +} From 801b780c1f235a38571e8caf665b3f09c1ac4659 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sat, 8 Nov 2025 09:35:04 -0500 Subject: [PATCH 014/153] Update WithCachedConfig.php (#57708) --- src/Illuminate/Foundation/Testing/WithCachedConfig.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/WithCachedConfig.php b/src/Illuminate/Foundation/Testing/WithCachedConfig.php index 41d0cdcae3ca..e1ed65a8da7d 100644 --- a/src/Illuminate/Foundation/Testing/WithCachedConfig.php +++ b/src/Illuminate/Foundation/Testing/WithCachedConfig.php @@ -26,7 +26,7 @@ protected function setUpWithCachedConfig(): void */ protected function tearDownWithCachedConfig(): void { - LoadConfiguration::setAlwaysUseConfig(null); + LoadConfiguration::alwaysUse(null); } /** @@ -34,7 +34,7 @@ protected function tearDownWithCachedConfig(): void */ protected function markConfigCached(Application $app): void { - $app->instance('config_loaded_from_cache', true); // I'm not sure this is actually needed + $app->instance('config_loaded_from_cache', true); LoadConfiguration::alwaysUse(static fn () => CachedState::$cachedConfig); } From a6e9bb5287d3ec7b1c9a7d7e172460e6a0c1ec0b Mon Sep 17 00:00:00 2001 From: Kevin Bui Date: Sun, 9 Nov 2025 01:36:17 +1100 Subject: [PATCH 015/153] [12.x] Reorder some core aliases in alphabetical order. (#57706) * Reorder core aliases in alphabetical order. * Add a test case to assert that core container aliases are registered by default. --- src/Illuminate/Foundation/Application.php | 6 +++--- tests/Foundation/FoundationApplicationTest.php | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index ab90e663f6a1..e19d75520b9c 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1631,6 +1631,8 @@ public function registerCoreContainerAliases() 'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], 'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class], 'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class], + 'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class], + 'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class], 'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class], 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class], @@ -1648,12 +1650,9 @@ public function registerCoreContainerAliases() 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'hash' => [\Illuminate\Hashing\HashManager::class], 'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class], - 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], 'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class], 'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], - 'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class], - 'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class], 'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class], 'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class], 'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class], @@ -1664,6 +1663,7 @@ public function registerCoreContainerAliases() 'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class], 'session' => [\Illuminate\Session\SessionManager::class], 'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class], + 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], 'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class], 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class], 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index e41fc9320a35..c6cc5d54a7a2 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -636,6 +636,18 @@ public function exists(string $path): bool $this->assertFalse($app->routesAreCached()); $this->assertStringContainsString('routes-v7.php', $files->pathRequested); } + + public function testCoreContainerAliasesAreRegisteredByDefault(): void + { + $app = new Application(); + + $this->assertTrue($app->isAlias(\Illuminate\Contracts\Translation\Translator::class)); + $this->assertSame('translator', $app->getAlias(\Illuminate\Contracts\Translation\Translator::class)); + $this->assertTrue($app->isAlias(\Illuminate\Contracts\Auth\PasswordBrokerFactory::class)); + $this->assertSame('auth.password', $app->getAlias(\Illuminate\Contracts\Auth\PasswordBrokerFactory::class)); + $this->assertTrue($app->isAlias(\Illuminate\Contracts\Auth\PasswordBroker::class)); + $this->assertSame('auth.password.broker', $app->getAlias(\Illuminate\Contracts\Auth\PasswordBroker::class)); + } } class ApplicationBasicServiceProviderStub extends ServiceProvider From f86b94805b61d694e93a664593c5d1f644d091bf Mon Sep 17 00:00:00 2001 From: ziming Date: Sun, 9 Nov 2025 22:33:09 +0800 Subject: [PATCH 016/153] Allow Resend ^1.0 (#57713) * Allow Resend ^1.0 * update Mail/composer.json --- composer.json | 4 ++-- src/Illuminate/Mail/composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 3cd0043c01e2..1f9c0b186f15 100644 --- a/composer.json +++ b/composer.json @@ -121,7 +121,7 @@ "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", "predis/predis": "^2.3|^3.0", - "resend/resend-php": "^0.10.0", + "resend/resend-php": "^0.10.0|^1.0", "symfony/cache": "^7.2.0", "symfony/http-client": "^7.2.0", "symfony/psr-http-message-bridge": "^7.2.0", @@ -196,7 +196,7 @@ "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0|^1.0).", "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 8df873951555..dd6eabe4caa0 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -38,7 +38,7 @@ "suggest": { "aws/aws-sdk-php": "Required to use the SES mail driver (^3.322.9).", "illuminate/http": "Required to create an attachment from an UploadedFile instance (^12.0).", - "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0|^1.0).", "symfony/http-client": "Required to use the Symfony API mail transports (^7.2).", "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2)." From f0841232d3bd708b5282eefd468887549c7ff27e Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sun, 9 Nov 2025 09:36:41 -0500 Subject: [PATCH 017/153] [12.x] memoize result of `Application@eventsAreCached()` (#57709) * memoize result of eventsAreCached * Update FoundationApplicationTest.php * Update FoundationApplicationTest.php * Update Application.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Foundation/Application.php | 8 +++- .../Foundation/FoundationApplicationTest.php | 47 ++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index e19d75520b9c..1a4de5ac328b 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1350,7 +1350,13 @@ public function getCachedRoutesPath() */ public function eventsAreCached() { - return $this['files']->exists($this->getCachedEventsPath()); + if ($this->bound('events.cached')) { + return (bool) $this->make('events.cached'); + } + + return $this->instance( + 'events.cached', $this['files']->exists($this->getCachedEventsPath()) + ); } /** diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index c6cc5d54a7a2..c9f8230e3098 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -620,23 +620,36 @@ public function test_routes_are_cached() public function test_routes_are_not_cached_by_instance_falls_back_to_file() { $app = new Application(); - $files = new class - { - public string $pathRequested; - - public function exists(string $path): bool - { - $this->pathRequested = $path; - - return false; - } - }; + $files = new FileExistsFake; $app->instance('files', $files); $this->assertFalse($app->routesAreCached()); $this->assertStringContainsString('routes-v7.php', $files->pathRequested); } + public function test_events_are_cached_uses_container_instance() + { + $app = new Application(); + $app->instance('events.cached', true); + $files = new FileExistsFake; + $app->instance('files', $files); + + $this->assertTrue($app->eventsAreCached()); + $this->assertFalse(isset($files->pathRequested)); + } + + public function test_events_are_cached_checks_filesystem_if_not_set() + { + $app = new Application(); + $files = new FileExistsFake; + $app->instance('files', $files); + + $this->assertFalse($app->eventsAreCached()); + $this->assertStringContainsString('events.php', $files->pathRequested); + $this->assertTrue($app->bound('events.cached')); + $this->assertFalse($app->make('events.cached')); + } + public function testCoreContainerAliasesAreRegisteredByDefault(): void { $app = new Application(); @@ -782,3 +795,15 @@ public function terminate() return self::$counter++; } } + +class FileExistsFake +{ + public string $pathRequested; + + public function exists(string $path): bool + { + $this->pathRequested = $path; + + return false; + } +} From 05a8d2296952e1cb38cbb64b20807b59a819709e Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sun, 9 Nov 2025 09:36:51 -0500 Subject: [PATCH 018/153] insert with hidden test (#57722) --- .../Database/DatabaseEloquentFactoryTest.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/Database/DatabaseEloquentFactoryTest.php b/tests/Database/DatabaseEloquentFactoryTest.php index 3ad58c33357e..d2920d8964f9 100644 --- a/tests/Database/DatabaseEloquentFactoryTest.php +++ b/tests/Database/DatabaseEloquentFactoryTest.php @@ -995,9 +995,21 @@ public function test_factory_can_insert() ->insert(); $this->assertCount(5, $posts = FactoryTestPost::query()->where('title', 'hello')->get()); $this->assertEquals(strtoupper($posts[0]->user->name), $posts[0]->upper_case_name); - $this->assertEquals(2, ($users = FactoryTestUser::query()->get())->count()); - $this->assertCount(1, $users->where('name', 'shaedrich')); + $this->assertEquals( + 2, + ($users = FactoryTestUser::query()->get())->count() + ); $this->assertCount(1, $users->where('name', 'totwell')); + $this->assertCount(1, $users->where('name', 'shaedrich')); + } + + public function test_factory_can_insert_with_hidden() + { + (new FactoryTestUserFactory())->forEachSequence(['name' => Name::Taylor, 'options' => 'abc'])->insert(); + $user = DB::table('users')->sole(); + $this->assertEquals('abc', $user->options); + $userModel = FactoryTestUser::query()->sole(); + $this->assertEquals('abc', $userModel->options); } /** @@ -1039,6 +1051,9 @@ class FactoryTestUser extends Eloquent use HasFactory; protected $table = 'users'; + protected $hidden = ['options']; + protected $withCount = ['posts']; + protected $with = ['posts']; public function posts() { From ae006118ae027c86e3b34a47287114ceebb5eb03 Mon Sep 17 00:00:00 2001 From: Vadim Dvorovenko Date: Sun, 9 Nov 2025 21:38:02 +0700 Subject: [PATCH 019/153] [12.x] Separate workflow for Redis integration tests (#57710) * [12.x] Separate workflow for Redis integration tests * [12.x] Skip tests failing with redis cluster --- .github/workflows/queues.yml | 107 -------------- .github/workflows/redis.yml | 132 ++++++++++++++++++ tests/Integration/Cache/MemoizedStoreTest.php | 8 ++ .../Cache/PhpRedisCacheLockTest.php | 9 +- tests/Integration/Cache/RedisStoreTest.php | 7 + 5 files changed, 155 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/redis.yml diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index 257e0d4bf29c..c7827ba04931 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -82,113 +82,6 @@ jobs: DB_CONNECTION: sqlite QUEUE_CONNECTION: database - redis: - runs-on: ubuntu-24.04 - - services: - redis: - image: redis:7.0 - ports: - - 6379:6379 - options: --entrypoint redis-server - - strategy: - fail-fast: true - matrix: - client: ['phpredis', 'predis'] - - name: Redis (${{ matrix.client}}) Driver - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr - tools: composer:v2 - coverage: none - - - name: Set Framework version - run: composer config version "12.x-dev" - - - name: Install dependencies - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - - name: Execute tests - run: vendor/bin/phpunit tests/Integration/Queue - env: - REDIS_CLIENT: ${{ matrix.client }} - QUEUE_CONNECTION: redis - - redis-cluster: - runs-on: ubuntu-24.04 - - strategy: - fail-fast: true - matrix: - client: ['phpredis', 'predis'] - - name: Redis Cluster (${{ matrix.client}}) Driver - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr - tools: composer:v2 - coverage: none - - - name: Set Framework version - run: composer config version "12.x-dev" - - - name: Install dependencies - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - - name: Create Redis Cluster - run: | - sudo apt update - sudo apt-get install -y --fix-missing redis-server - sudo service redis-server stop - redis-server --daemonize yes --port 7000 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7000.conf - redis-server --daemonize yes --port 7001 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7001.conf - redis-server --daemonize yes --port 7002 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7002.conf - redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 --cluster-replicas 0 --cluster-yes - - - name: Check Redis Cluster is ready - uses: nick-fields/retry@v3 - with: - timeout_seconds: 5 - max_attempts: 5 - retry_wait_seconds: 5 - retry_on: error - command: | - redis-cli -c -h 127.0.0.1 -p 7000 cluster info | grep "cluster_state:ok" - redis-cli -c -h 127.0.0.1 -p 7001 cluster info | grep "cluster_state:ok" - redis-cli -c -h 127.0.0.1 -p 7002 cluster info | grep "cluster_state:ok" - - - name: Execute tests - run: vendor/bin/phpunit tests/Integration/Queue - env: - REDIS_CLIENT: ${{ matrix.client }} - REDIS_CLUSTER_HOSTS_AND_PORTS: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 - REDIS_QUEUE: '{default}' - QUEUE_CONNECTION: redis - beanstalkd: runs-on: ubuntu-24.04 diff --git a/.github/workflows/redis.yml b/.github/workflows/redis.yml new file mode 100644 index 000000000000..cb5dda5c095c --- /dev/null +++ b/.github/workflows/redis.yml @@ -0,0 +1,132 @@ +name: Redis and Redis Cluster + +on: + push: + branches: + - master + - '*.x' + pull_request: + +jobs: + redis: + runs-on: ubuntu-24.04 + + services: + redis: + image: redis:7.0 + ports: + - 6379:6379 + options: --entrypoint redis-server + + strategy: + fail-fast: true + matrix: + client: ['phpredis', 'predis'] + + name: Redis (${{ matrix.client}}) Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Set Framework version + run: composer config version "12.x-dev" + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute Cache tests + run: vendor/bin/phpunit tests/Integration/Cache + env: + REDIS_CACHE_CONNECTION: cache + REDIS_CACHE_LOCK_CONNECTION: cache + REDIS_CLIENT: ${{ matrix.client }} + + - name: Execute Queue tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + REDIS_CLIENT: ${{ matrix.client }} + QUEUE_CONNECTION: redis + + redis-cluster: + runs-on: ubuntu-24.04 + + strategy: + fail-fast: true + matrix: + client: ['phpredis', 'predis'] + + name: Redis Cluster (${{ matrix.client}}) Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Set Framework version + run: composer config version "12.x-dev" + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Create Redis Cluster + run: | + sudo apt update + sudo apt-get install -y --fix-missing redis-server + sudo service redis-server stop + redis-server --daemonize yes --port 7000 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7000.conf + redis-server --daemonize yes --port 7001 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7001.conf + redis-server --daemonize yes --port 7002 --appendonly yes --cluster-enabled yes --cluster-config-file nodes-7002.conf + redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 --cluster-replicas 0 --cluster-yes + + - name: Check Redis Cluster is ready + uses: nick-fields/retry@v3 + with: + timeout_seconds: 5 + max_attempts: 5 + retry_wait_seconds: 5 + retry_on: error + command: | + redis-cli -c -h 127.0.0.1 -p 7000 cluster info | grep "cluster_state:ok" + redis-cli -c -h 127.0.0.1 -p 7001 cluster info | grep "cluster_state:ok" + redis-cli -c -h 127.0.0.1 -p 7002 cluster info | grep "cluster_state:ok" + + - name: Execute Cache tests + run: vendor/bin/phpunit tests/Integration/Cache + env: + REDIS_CACHE_CONNECTION: default + REDIS_CACHE_LOCK_CONNECTION: default + REDIS_CLIENT: ${{ matrix.client }} + REDIS_CLUSTER_HOSTS_AND_PORTS: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 + + - name: Execute Queue Tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + REDIS_CLIENT: ${{ matrix.client }} + REDIS_CLUSTER_HOSTS_AND_PORTS: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 + REDIS_QUEUE: '{default}' + QUEUE_CONNECTION: redis + diff --git a/tests/Integration/Cache/MemoizedStoreTest.php b/tests/Integration/Cache/MemoizedStoreTest.php index 009906f1555f..e9ec1b435d51 100644 --- a/tests/Integration/Cache/MemoizedStoreTest.php +++ b/tests/Integration/Cache/MemoizedStoreTest.php @@ -13,6 +13,8 @@ use Illuminate\Cache\Events\WritingKey; use Illuminate\Contracts\Cache\Store; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; +use Illuminate\Redis\Connections\PhpRedisClusterConnection; +use Illuminate\Redis\Connections\PredisClusterConnection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Event; @@ -31,6 +33,12 @@ protected function setUp(): void $this->setUpRedis(); + $connection = $this->app['redis']->connection(); + $this->markTestSkippedWhen( + $connection instanceof PhpRedisClusterConnection || $connection instanceof PredisClusterConnection, + 'flushAll and many currently not supported for Redis Cluster connections', + ); + Config::set('cache.default', 'redis'); Redis::flushAll(); } diff --git a/tests/Integration/Cache/PhpRedisCacheLockTest.php b/tests/Integration/Cache/PhpRedisCacheLockTest.php index 1ebc7cc94d87..de33bd263864 100644 --- a/tests/Integration/Cache/PhpRedisCacheLockTest.php +++ b/tests/Integration/Cache/PhpRedisCacheLockTest.php @@ -3,6 +3,8 @@ namespace Illuminate\Tests\Integration\Cache; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; +use Illuminate\Redis\Connections\PhpRedisClusterConnection; +use Illuminate\Redis\Connections\PhpRedisConnection; use Illuminate\Support\Facades\Cache; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\RequiresPhpExtension; @@ -17,6 +19,12 @@ protected function setUp(): void parent::setUp(); $this->setUpRedis(); + + $connection = $this->app['redis']->connection(); + $this->markTestSkippedUnless( + $connection instanceof PhpRedisConnection || $connection instanceof PhpRedisClusterConnection, + 'This test is for phpredis only', + ); } protected function tearDown(): void @@ -210,7 +218,6 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression() $this->markTestSkipped('Redis extension is not configured to support the lz4 compression.'); } - $this->app['config']->set('database.redis.client', 'phpredis'); $this->app['config']->set('cache.stores.redis.connection', 'default'); $this->app['config']->set('cache.stores.redis.lock_connection', 'default'); diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index 1cefcf252863..ede6e7c394f1 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -6,6 +6,7 @@ use Illuminate\Cache\RedisStore; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Redis\Connections\PhpRedisClusterConnection; +use Illuminate\Redis\Connections\PredisClusterConnection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Sleep; @@ -22,6 +23,12 @@ protected function setUp(): void { $this->afterApplicationCreated(function () { $this->setUpRedis(); + + $connection = $this->app['redis']->connection(); + $this->markTestSkippedWhen( + $connection instanceof PhpRedisClusterConnection || $connection instanceof PredisClusterConnection, + 'RedisStore currently does not support tags, many and some other on Redis Cluster cluster connections', + ); }); $this->beforeApplicationDestroyed(function () { From 2c0ae1d221130159b986f9f5c54e77b831687d49 Mon Sep 17 00:00:00 2001 From: Liam Duckett Date: Sun, 9 Nov 2025 14:43:02 +0000 Subject: [PATCH 020/153] [12.x] Types: HasDatabaseNotifications read/unread notifications (#57718) * add failing type test * add generics to has database notifications scoped relationships --- src/Illuminate/Notifications/HasDatabaseNotifications.php | 4 ++-- types/Database/Eloquent/Model.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Notifications/HasDatabaseNotifications.php b/src/Illuminate/Notifications/HasDatabaseNotifications.php index 10e2386f55ba..92b95d693c03 100644 --- a/src/Illuminate/Notifications/HasDatabaseNotifications.php +++ b/src/Illuminate/Notifications/HasDatabaseNotifications.php @@ -17,7 +17,7 @@ public function notifications() /** * Get the entity's read notifications. * - * @return \Illuminate\Database\Query\Builder + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ public function readNotifications() { @@ -27,7 +27,7 @@ public function readNotifications() /** * Get the entity's unread notifications. * - * @return \Illuminate\Database\Query\Builder + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ public function unreadNotifications() { diff --git a/types/Database/Eloquent/Model.php b/types/Database/Eloquent/Model.php index 663ec93bea21..39990e69e389 100644 --- a/types/Database/Eloquent/Model.php +++ b/types/Database/Eloquent/Model.php @@ -38,6 +38,7 @@ function test(User $user, Post $post, Comment $comment, Article $article): void assertType('Illuminate\Database\Eloquent\Builder', $user->withoutTrashed()); assertType('Illuminate\Database\Eloquent\Builder', $user->prunable()); assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $user->notifications()); + assertType('Illuminate\Database\Eloquent\Relations\MorphMany', $user->unreadNotifications()); assertType('Illuminate\Database\Eloquent\Collection<(int|string), User>', $user->newCollection([new User()])); assertType('Illuminate\Types\Model\Posts<(int|string), Illuminate\Types\Model\Post>', $post->newCollection(['foo' => new Post()])); From 8f47a313d8b63d9a0062e44d25e2a3f074a846db Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 10 Nov 2025 21:50:12 +0800 Subject: [PATCH 021/153] [12.x] Supports Symfony 7.4 (#57724) * [12.x] Supports Symfony 7.4 Signed-off-by: Mior Muhammad Zaki * Fix `symfony/console` 7.4 Signed-off-by: Mior Muhammad Zaki * update composer.json Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/Application.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 94d1cc2196e7..b7232fcece21 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -219,7 +219,7 @@ public function output() public function addCommands(array $commands): void { foreach ($commands as $command) { - $this->add($command); + $this->addCommand($command); } } @@ -231,6 +231,17 @@ public function addCommands(array $commands): void */ #[\Override] public function add(SymfonyCommand $command): ?SymfonyCommand + { + return $this->addCommand($command); + } + + /** + * Add a command to the console. + * + * @param \Symfony\Component\Console\Command\Command|callable $command + * @return \Symfony\Component\Console\Command\Command|null + */ + public function addCommand(SymfonyCommand|callable $command): ?SymfonyCommand { if ($command instanceof Command) { $command->setLaravel($this->laravel); @@ -247,6 +258,11 @@ public function add(SymfonyCommand $command): ?SymfonyCommand */ protected function addToParent(SymfonyCommand $command) { + if (method_exists(SymfonyApplication::class, 'addCommand')) { + /** @phpstan-ignore staticMethod.notFound */ + return parent::addCommand($command); + } + return parent::add($command); } From a21ad1603907ed4483a58963aaaab11469c652f1 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 10 Nov 2025 08:35:03 -0600 Subject: [PATCH 022/153] delete broadcast events that are missing models by default --- src/Illuminate/Broadcasting/BroadcastEvent.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Illuminate/Broadcasting/BroadcastEvent.php b/src/Illuminate/Broadcasting/BroadcastEvent.php index 2ef568dc6bb0..2ed30c530dbb 100644 --- a/src/Illuminate/Broadcasting/BroadcastEvent.php +++ b/src/Illuminate/Broadcasting/BroadcastEvent.php @@ -50,6 +50,13 @@ class BroadcastEvent implements ShouldQueue */ public $maxExceptions; + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; + /** * Create a new job handler instance. * From 5b3cc40b17723df33c0558f9f37f6a5b64de3559 Mon Sep 17 00:00:00 2001 From: Florian Raith <37345813+florianraith@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:51:39 +0100 Subject: [PATCH 023/153] [12.x] Revert lowercasing validation message placeholders (#57733) * Revert lowercasing validation message placeholders * Remove unused import * Opened the wrong file and commited prematurely --- .../Validation/Concerns/ReplacesAttributes.php | 2 +- tests/Validation/ValidationValidatorTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php index abd5ba154a5c..ef9bfaf6bca3 100644 --- a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php @@ -938,7 +938,7 @@ protected function replaceDoesntContain($message, $attribute, $rule, $parameters */ private function replaceWhileKeepingCase(string $message, array $mapping): string { - $fn = [Str::lower(...), Str::upper(...), Str::ucfirst(...)]; + $fn = [fn ($v) => $v, Str::upper(...), Str::ucfirst(...)]; $cases = array_reduce( array_keys($mapping), diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index b6ec5ac6722f..cc0df653d4d9 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -639,6 +639,12 @@ public function testCapitalizedDisplayableValuesAreReplaced() $v->messages()->setFormat(':message'); $this->assertSame('The foo field must be accepted when BAR is AAA.', $v->messages()->first('foo')); + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines(['validation.accepted_if' => 'The :attribute field must be accepted when :other is :value.'], 'en'); + $v = new Validator($trans, ['foo' => 'no', 'bar' => 'aAa'], ['foo' => 'accepted_if:bar,aAa']); + $this->assertFalse($v->passes()); + $this->assertSame('The foo field must be accepted when bar is aAa.', $v->messages()->first('foo')); + // in_array $trans = $this->getIlluminateArrayTranslator(); $trans->addLines(['validation.in_array' => 'The value of :attribute does not exist in :Other.'], 'en'); @@ -715,6 +721,12 @@ public function testCapitalizedDisplayableValuesAreReplaced() $this->assertFalse($v->passes()); $this->assertSame('The last field is required unless FIRST is in TAYLOR, SVEN.', $v->messages()->first('last')); + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines(['validation.required_unless' => 'The :attribute field is required unless :other is in :values.'], 'en'); + $v = new Validator($trans, ['firstName' => 'dAyle', 'lastName' => ''], ['lastName' => 'RequiredUnless:firstName,tAylor,sVen']); + $this->assertFalse($v->passes()); + $this->assertSame('The last name field is required unless first name is in tAylor, sVen.', $v->messages()->first('lastName')); + // required_with $trans = $this->getIlluminateArrayTranslator(); $trans->addLines(['validation.required_with' => 'The :attribute field is required when :Values is present.'], 'en'); @@ -753,6 +765,12 @@ public function testCapitalizedDisplayableValuesAreReplaced() $v = new Validator($trans, ['url' => 'laravel.com'], ['url' => 'starts_with:http,https']); $this->assertFalse($v->passes()); $this->assertSame('The url must start with one of the following values HTTP, HTTPS', $v->messages()->first('url')); + + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines(['validation.starts_with' => 'The :attribute must start with one of the following values :values'], 'en'); + $v = new Validator($trans, ['url' => 'laravel.com'], ['url' => 'starts_with:hTtp,hTtps']); + $this->assertFalse($v->passes()); + $this->assertSame('The url must start with one of the following values hTtp, hTtps', $v->messages()->first('url')); } public function testInputIsReplacedByItsDisplayableValue() From f5d5c5ad1b9342da3b0b0a117c785a7eab94391c Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Mon, 10 Nov 2025 16:55:17 -0600 Subject: [PATCH 024/153] [12.x] try another way to activate Broadcast routes (#57734) * try another way to activate Broadcast routes this command currently has 2 conditions it checks to try and activate the "channels" file in your `/bootstrap/app.php`. while this may cover a good chunk of users, it fails if the `commands:` named argument has been removed from the file. this change adds an additional check to look for the `->withRouting()` method call, which has a *very* high likelyhood of being in the file. placing it after the "commands" named argument is ideal because it maintains named argument order, but this is a good fallback. _if even this additional check fails_, it now returns an error message to the user telling them they need to manually activate the broadcast routes in their `/bootstrap/app.php` file. this will helpfully avoid confusion if the automatic enabling fails. * Update BroadcastingInstallCommand.php --------- Co-authored-by: Taylor Otwell --- .../Foundation/Console/BroadcastingInstallCommand.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php index 028825255c4d..4c32db5d83f4 100644 --- a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -161,6 +161,14 @@ protected function uncommentChannelsRoutesFile() 'commands: __DIR__.\'/../routes/console.php\','.PHP_EOL.' channels: __DIR__.\'/../routes/channels.php\',', $appBootstrapPath, ); + } elseif (str_contains($content, '->withRouting(')) { + (new Filesystem)->replaceInFile( + '->withRouting(', + '->withRouting('.PHP_EOL.' channels: __DIR__.\'/../routes/channels.php\',', + $appBootstrapPath, + ); + } else { + $this->components->error('Unable to register broadcast routes. Please register them manually in ['.$appBootstrapPath.'].'); } } From 5af305596240020de15629301c19107c305a9c3c Mon Sep 17 00:00:00 2001 From: Markus Machatschek Date: Tue, 11 Nov 2025 15:12:44 +0100 Subject: [PATCH 025/153] [12.x] Add environment information to json output of schedule:list command (#57741) * feat: add environment to json output of schedule:list * chore: actually make use of the display method to reduce duplicate code * fix: test command * wip --- .../Scheduling/ScheduleListCommand.php | 5 ++--- .../Scheduling/ScheduleListCommandTest.php | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 9822eafabdeb..1138ab7ffa52 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -67,9 +67,7 @@ public function handle(Schedule $schedule) $events = $this->sortEvents($events, $timezone); - $this->option('json') - ? $this->displayJson($events, $timezone) - : $this->displayForCli($events, $timezone); + $this->display($events, $timezone); } /** @@ -107,6 +105,7 @@ protected function displayJson(Collection $events, DateTimeZone $timezone) 'timezone' => $timezone->getName(), 'has_mutex' => $event->mutex->exists($event), 'repeat_seconds' => $event->isRepeatable() ? $event->repeatSeconds : null, + 'environments' => $event->environments, ]; })->values()->toJson()); } diff --git a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php index 633288e1188a..582de917b6e4 100644 --- a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php +++ b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php @@ -103,6 +103,8 @@ public function testDisplayScheduleAsJson() $this->assertStringContainsString('2023-04-01 00:00:00', $data[0]['next_due_date']); $this->assertSame('3 months from now', $data[0]['next_due_date_human']); $this->assertFalse($data[0]['has_mutex']); + $this->assertIsArray($data[0]['environments']); + $this->assertEmpty($data[0]['environments']); $this->assertSame('* * * * *', $data[2]['expression']); $this->assertSame('php artisan foobar a='.ProcessUtils::escapeArgument('b'), $data[2]['command']); @@ -117,6 +119,24 @@ public function testDisplayScheduleAsJson() $this->assertStringContainsString('ScheduleListCommandTest.php', $data[8]['command']); } + public function testDisplayScheduleAsJsonWithSpecificEnvironment() + { + $environment = 'production'; + $this->schedule->command(FooCommand::class)->quarterly()->environments($environment); + + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, ['--json' => true]); + $output = Artisan::output(); + + $this->assertJson($output); + $data = json_decode($output, true); + $this->assertIsArray($data); + $this->assertCount(1, $data); + + $this->assertIsArray($data[0]['environments']); + $this->assertNotEmpty($data[0]['environments']); + $this->assertContains($environment, $data[0]['environments']); + } + public function testDisplayScheduleWithSortAsJson() { $this->schedule->command(FooCommand::class)->quarterly(); From 21bb042f97d30bf03767031641c5b78ce26418a4 Mon Sep 17 00:00:00 2001 From: Jack Bayliss Date: Tue, 11 Nov 2025 14:13:21 +0000 Subject: [PATCH 026/153] Update DumpCommand.php (#57735) --- src/Illuminate/Database/Console/DumpCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Illuminate/Database/Console/DumpCommand.php b/src/Illuminate/Database/Console/DumpCommand.php index 0c038939f0de..fea8fc0554d2 100644 --- a/src/Illuminate/Database/Console/DumpCommand.php +++ b/src/Illuminate/Database/Console/DumpCommand.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Console; use Illuminate\Console\Command; +use Illuminate\Console\Prohibitable; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; @@ -15,6 +16,8 @@ #[AsCommand(name: 'schema:dump')] class DumpCommand extends Command { + use Prohibitable; + /** * The console command name. * @@ -41,6 +44,10 @@ class DumpCommand extends Command */ public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher) { + if ($this->isProhibited()) { + return Command::FAILURE; + } + $connection = $connections->connection($database = $this->input->getOption('database')); $this->schemaState($connection)->dump( From 3152112ae8aec40b45350d02f026df827b151b52 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 12 Nov 2025 23:42:43 +0700 Subject: [PATCH 027/153] Clean ConsoleApplicationTest (#57761) --- tests/Console/ConsoleApplicationTest.php | 154 +++++++++++------------ 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/tests/Console/ConsoleApplicationTest.php b/tests/Console/ConsoleApplicationTest.php index 7a78e2437905..814b4f7444c6 100755 --- a/tests/Console/ConsoleApplicationTest.php +++ b/tests/Console/ConsoleApplicationTest.php @@ -26,121 +26,121 @@ protected function tearDown(): void public function testAddSetsLaravelInstance() { - $app = $this->getMockConsole(['addToParent']); + $artisan = $this->getMockConsole(['addToParent']); $command = m::mock(Command::class); $command->shouldReceive('setLaravel')->once()->with(m::type(ApplicationContract::class)); - $app->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); - $result = $app->add($command); + $artisan->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); + $result = $artisan->add($command); - $this->assertEquals($command, $result); + $this->assertSame($command, $result); } public function testLaravelNotSetOnSymfonyCommands() { - $app = $this->getMockConsole(['addToParent']); + $artisan = $this->getMockConsole(['addToParent']); $command = m::mock(SymfonyCommand::class); $command->shouldReceive('setLaravel')->never(); - $app->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); - $result = $app->add($command); + $artisan->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); + $result = $artisan->add($command); - $this->assertEquals($command, $result); + $this->assertSame($command, $result); } public function testResolveAddsCommandViaApplicationResolution() { - $app = $this->getMockConsole(['addToParent']); + $artisan = $this->getMockConsole(['addToParent']); $command = m::mock(SymfonyCommand::class); - $app->getLaravel()->shouldReceive('make')->once()->with('foo')->andReturn(m::mock(SymfonyCommand::class)); - $app->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); - $result = $app->resolve('foo'); + $artisan->getLaravel()->shouldReceive('make')->once()->with('foo')->andReturn(m::mock(SymfonyCommand::class)); + $artisan->expects($this->once())->method('addToParent')->with($this->equalTo($command))->willReturn($command); + $result = $artisan->resolve('foo'); - $this->assertEquals($command, $result); + $this->assertSame($command, $result); } public function testResolvingCommandsWithAliasViaAttribute() { $container = new FoundationApplication(); - $app = new Application($container, new EventsDispatcher($container), $container->version()); - $app->resolve(CommandWithAliasViaAttribute::class); - $app->setContainerCommandLoader(); - - $this->assertInstanceOf(CommandWithAliasViaAttribute::class, $app->get('command-name')); - $this->assertInstanceOf(CommandWithAliasViaAttribute::class, $app->get('command-alias')); - $this->assertArrayHasKey('command-name', $app->all()); - $this->assertArrayHasKey('command-alias', $app->all()); + $artisan = new Application($container, new EventsDispatcher($container), $container->version()); + $artisan->resolve(CommandWithAliasViaAttribute::class); + $artisan->setContainerCommandLoader(); + + $this->assertInstanceOf(CommandWithAliasViaAttribute::class, $artisan->get('command-name')); + $this->assertInstanceOf(CommandWithAliasViaAttribute::class, $artisan->get('command-alias')); + $this->assertArrayHasKey('command-name', $artisan->all()); + $this->assertArrayHasKey('command-alias', $artisan->all()); } public function testResolvingCommandsWithAliasViaProperty() { $container = new FoundationApplication(); - $app = new Application($container, new EventsDispatcher($container), $container->version()); - $app->resolve(CommandWithAliasViaProperty::class); - $app->setContainerCommandLoader(); - - $this->assertInstanceOf(CommandWithAliasViaProperty::class, $app->get('command-name')); - $this->assertInstanceOf(CommandWithAliasViaProperty::class, $app->get('command-alias')); - $this->assertArrayHasKey('command-name', $app->all()); - $this->assertArrayHasKey('command-alias', $app->all()); + $artisan = new Application($container, new EventsDispatcher($container), $container->version()); + $artisan->resolve(CommandWithAliasViaProperty::class); + $artisan->setContainerCommandLoader(); + + $this->assertInstanceOf(CommandWithAliasViaProperty::class, $artisan->get('command-name')); + $this->assertInstanceOf(CommandWithAliasViaProperty::class, $artisan->get('command-alias')); + $this->assertArrayHasKey('command-name', $artisan->all()); + $this->assertArrayHasKey('command-alias', $artisan->all()); } public function testResolvingCommandsWithNoAliasViaAttribute() { $container = new FoundationApplication(); - $app = new Application($container, new EventsDispatcher($container), $container->version()); - $app->resolve(CommandWithNoAliasViaAttribute::class); - $app->setContainerCommandLoader(); + $artisan = new Application($container, new EventsDispatcher($container), $container->version()); + $artisan->resolve(CommandWithNoAliasViaAttribute::class); + $artisan->setContainerCommandLoader(); - $this->assertInstanceOf(CommandWithNoAliasViaAttribute::class, $app->get('command-name')); + $this->assertInstanceOf(CommandWithNoAliasViaAttribute::class, $artisan->get('command-name')); try { - $app->get('command-alias'); + $artisan->get('command-alias'); $this->fail(); } catch (Throwable $e) { $this->assertInstanceOf(CommandNotFoundException::class, $e); } - $this->assertArrayHasKey('command-name', $app->all()); - $this->assertArrayNotHasKey('command-alias', $app->all()); + $this->assertArrayHasKey('command-name', $artisan->all()); + $this->assertArrayNotHasKey('command-alias', $artisan->all()); } public function testResolvingCommandsWithNoAliasViaProperty() { $container = new FoundationApplication(); - $app = new Application($container, new EventsDispatcher($container), $container->version()); - $app->resolve(CommandWithNoAliasViaProperty::class); - $app->setContainerCommandLoader(); + $artisan = new Application($container, new EventsDispatcher($container), $container->version()); + $artisan->resolve(CommandWithNoAliasViaProperty::class); + $artisan->setContainerCommandLoader(); - $this->assertInstanceOf(CommandWithNoAliasViaProperty::class, $app->get('command-name')); + $this->assertInstanceOf(CommandWithNoAliasViaProperty::class, $artisan->get('command-name')); try { - $app->get('command-alias'); + $artisan->get('command-alias'); $this->fail(); } catch (Throwable $e) { $this->assertInstanceOf(CommandNotFoundException::class, $e); } - $this->assertArrayHasKey('command-name', $app->all()); - $this->assertArrayNotHasKey('command-alias', $app->all()); + $this->assertArrayHasKey('command-name', $artisan->all()); + $this->assertArrayNotHasKey('command-alias', $artisan->all()); } public function testCallFullyStringCommandLine() { - $app = new Application( - $app = m::mock(ApplicationContract::class, ['version' => '6.0']), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + $artisan = new Application( + m::mock(ApplicationContract::class, ['version' => '6.0']), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $codeOfCallingArrayInput = $app->call('help', [ + $codeOfCallingArrayInput = $artisan->call('help', [ '--raw' => true, '--format' => 'txt', '--no-interaction' => true, '--env' => 'testing', ]); - $outputOfCallingArrayInput = $app->output(); + $outputOfCallingArrayInput = $artisan->output(); - $codeOfCallingStringInput = $app->call( + $codeOfCallingStringInput = $artisan->call( 'help --raw --format=txt --no-interaction --env=testing' ); - $outputOfCallingStringInput = $app->output(); + $outputOfCallingStringInput = $artisan->output(); $this->assertSame($codeOfCallingArrayInput, $codeOfCallingStringInput); $this->assertSame($outputOfCallingArrayInput, $outputOfCallingStringInput); @@ -148,97 +148,97 @@ public function testCallFullyStringCommandLine() public function testCommandInputPromptsWhenRequiredArgumentIsMissing() { - $app = new Application( + $artisan = new Application( $laravel = new \Illuminate\Foundation\Application(__DIR__), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $app->addCommands([$command = new FakeCommandWithInputPrompting()]); + $artisan->addCommands([$command = new FakeCommandWithInputPrompting()]); $command->setLaravel($laravel); - $statusCode = $app->call('fake-command-for-testing'); + $exitCode = $artisan->call('fake-command-for-testing'); $this->assertTrue($command->prompted); $this->assertSame('foo', $command->argument('name')); - $this->assertSame(0, $statusCode); + $this->assertSame(0, $exitCode); } public function testCommandInputDoesntPromptWhenRequiredArgumentIsPassed() { - $app = new Application( - $app = new \Illuminate\Foundation\Application(__DIR__), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + $artisan = new Application( + new \Illuminate\Foundation\Application(__DIR__), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $app->addCommands([$command = new FakeCommandWithInputPrompting()]); + $artisan->addCommands([$command = new FakeCommandWithInputPrompting()]); - $statusCode = $app->call('fake-command-for-testing', [ + $exitCode = $artisan->call('fake-command-for-testing', [ 'name' => 'foo', ]); $this->assertFalse($command->prompted); $this->assertSame('foo', $command->argument('name')); - $this->assertSame(0, $statusCode); + $this->assertSame(0, $exitCode); } public function testCommandInputPromptsWhenRequiredArgumentsAreMissing() { - $app = new Application( + $artisan = new Application( $laravel = new \Illuminate\Foundation\Application(__DIR__), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $app->addCommands([$command = new FakeCommandWithArrayInputPrompting()]); + $artisan->addCommands([$command = new FakeCommandWithArrayInputPrompting()]); $command->setLaravel($laravel); - $statusCode = $app->call('fake-command-for-testing-array'); + $exitCode = $artisan->call('fake-command-for-testing-array'); $this->assertTrue($command->prompted); $this->assertSame(['foo'], $command->argument('names')); - $this->assertSame(0, $statusCode); + $this->assertSame(0, $exitCode); } public function testCommandInputDoesntPromptWhenRequiredArgumentsArePassed() { - $app = new Application( - $app = new \Illuminate\Foundation\Application(__DIR__), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + $artisan = new Application( + new \Illuminate\Foundation\Application(__DIR__), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $app->addCommands([$command = new FakeCommandWithArrayInputPrompting()]); + $artisan->addCommands([$command = new FakeCommandWithArrayInputPrompting()]); - $statusCode = $app->call('fake-command-for-testing-array', [ + $exitCode = $artisan->call('fake-command-for-testing-array', [ 'names' => ['foo', 'bar', 'baz'], ]); $this->assertFalse($command->prompted); $this->assertSame(['foo', 'bar', 'baz'], $command->argument('names')); - $this->assertSame(0, $statusCode); + $this->assertSame(0, $exitCode); } public function testCallMethodCanCallArtisanCommandUsingCommandClassObject() { - $app = new Application( + $artisan = new Application( $laravel = new \Illuminate\Foundation\Application(__DIR__), - $events = m::mock(Dispatcher::class, ['dispatch' => null, 'fire' => null]), + m::mock(Dispatcher::class, ['dispatch' => null]), 'testing' ); - $app->addCommands([$command = new FakeCommandWithInputPrompting()]); + $artisan->addCommands([$command = new FakeCommandWithInputPrompting()]); $command->setLaravel($laravel); - $statusCode = $app->call($command); + $exitCode = $artisan->call($command); $this->assertTrue($command->prompted); $this->assertSame('foo', $command->argument('name')); - $this->assertSame(0, $statusCode); + $this->assertSame(0, $exitCode); } protected function getMockConsole(array $methods) From f5514bc996323a6ee2675e11bd30c23da744bc1a Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 12 Nov 2025 23:43:12 +0700 Subject: [PATCH 028/153] Fix the docblock of the BroadcastManager::purge method. (#57758) --- src/Illuminate/Broadcasting/BroadcastManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index acb01d7bd31d..8b4c403d25f5 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -465,7 +465,7 @@ public function setDefaultDriver($name) } /** - * Disconnect the given disk and remove from local cache. + * Disconnect the given driver / connection and remove it from local cache. * * @param string|null $name * @return void From e8156d8931dbee67664757aae236dba954cd3914 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 13 Nov 2025 00:45:01 +0800 Subject: [PATCH 029/153] [12.x] Fix setting request exception truncating doesn't work on HTTP layer when configured inside `bootstrap/app.php` (#57759) * [12.x] Fix setting request exception truncating doesn't work on HTTP layer when configured inside `bootstrap/app.php` fix #57601 Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../Http/Client/RequestException.php | 33 ++++++++++++------- src/Illuminate/Http/Client/Response.php | 14 +------- tests/Http/HttpClientTest.php | 17 +++++++--- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/Illuminate/Http/Client/RequestException.php b/src/Illuminate/Http/Client/RequestException.php index a72f12873594..6f32fc9f64a0 100644 --- a/src/Illuminate/Http/Client/RequestException.php +++ b/src/Illuminate/Http/Client/RequestException.php @@ -14,7 +14,14 @@ class RequestException extends HttpClientException public $response; /** - * The truncation length for the exception message. + * The current truncation length for the exception message. + * + * @var int|false + */ + public $truncateExceptionsAt; + + /** + * The global truncation length for the exception message. * * @var int|false */ @@ -24,10 +31,13 @@ class RequestException extends HttpClientException * Create a new exception instance. * * @param \Illuminate\Http\Client\Response $response + * @param int|false|null $truncateExceptionsAt */ - public function __construct(Response $response) + public function __construct(Response $response, $truncateExceptionsAt = null) { - parent::__construct($this->prepareMessage($response), $response->status()); + parent::__construct("HTTP request returned status code {$response->status()}", $response->status()); + + $this->truncateExceptionsAt = $truncateExceptionsAt; $this->response = $response; } @@ -66,17 +76,18 @@ public static function dontTruncate() /** * Prepare the exception message. * - * @param \Illuminate\Http\Client\Response $response - * @return string + * @return void */ - protected function prepareMessage(Response $response) + public function report(): void { - $message = "HTTP request returned status code {$response->status()}"; + $truncateExceptionsAt = $this->truncateExceptionsAt ?? static::$truncateAt; - $summary = static::$truncateAt - ? Message::bodySummary($response->toPsrResponse(), static::$truncateAt) - : Message::toString($response->toPsrResponse()); + $summary = $truncateExceptionsAt + ? Message::bodySummary($this->response->toPsrResponse(), $truncateExceptionsAt) + : Message::toString($this->response->toPsrResponse()); - return is_null($summary) ? $message : $message .= ":\n{$summary}\n"; + if (! is_null($summary)) { + $this->message .= ":\n{$summary}\n"; + } } } diff --git a/src/Illuminate/Http/Client/Response.php b/src/Illuminate/Http/Client/Response.php index 27f0899cb517..2b34293a046f 100644 --- a/src/Illuminate/Http/Client/Response.php +++ b/src/Illuminate/Http/Client/Response.php @@ -304,19 +304,7 @@ public function toPsrResponse() public function toException() { if ($this->failed()) { - $originalTruncateAt = RequestException::$truncateAt; - - try { - if ($this->truncateExceptionsAt !== null) { - $this->truncateExceptionsAt === false - ? RequestException::dontTruncate() - : RequestException::truncateAt($this->truncateExceptionsAt); - } - - return new RequestException($this); - } finally { - RequestException::$truncateAt = $originalTruncateAt; - } + return new RequestException($this, $this->truncateExceptionsAt); } } diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 7d0ec71f427f..39c059dc9bd1 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1301,9 +1301,10 @@ public function testRequestExceptionSummary() 'message' => 'The Request can not be completed', ], ]; + $response = new Psr7Response(403, [], json_encode($error)); - throw new RequestException(new Response($response)); + throw tap(new RequestException(new Response($response)), fn ($exception) => $exception->report()); } public function testRequestExceptionTruncatedSummary() @@ -1319,7 +1320,7 @@ public function testRequestExceptionTruncatedSummary() ]; $response = new Psr7Response(403, [], json_encode($error)); - throw new RequestException(new Response($response)); + throw tap(new RequestException(new Response($response)), fn ($exception) => $exception->report()); } public function testRequestExceptionWithoutTruncatedSummary() @@ -1337,7 +1338,7 @@ public function testRequestExceptionWithoutTruncatedSummary() ]; $response = new Psr7Response(403, [], json_encode($error)); - throw new RequestException(new Response($response)); + throw tap(new RequestException(new Response($response)), fn ($exception) => $exception->report()); } public function testRequestExceptionWithCustomTruncatedSummary() @@ -1355,7 +1356,7 @@ public function testRequestExceptionWithCustomTruncatedSummary() ]; $response = new Psr7Response(403, [], json_encode($error)); - throw new RequestException(new Response($response)); + throw tap(new RequestException(new Response($response)), fn ($exception) => $exception->report()); } public function testRequestLevelTruncationLevelOnRequestException() @@ -1373,6 +1374,8 @@ public function testRequestLevelTruncationLevelOnRequestException() $exception = $e; } + $exception->report(); + $this->assertEquals("HTTP request returned status code 403:\n[\"e (truncated...)\n", $exception->getMessage()); $this->assertEquals(60, RequestException::$truncateAt); @@ -1394,6 +1397,8 @@ public function testNoTruncationOnRequestLevel() $exception = $e; } + $exception->report(); + $this->assertEquals("HTTP request returned status code 403:\nHTTP/1.1 403 Forbidden\r\nContent-Type: application/json\r\n\r\n[\"error\"]\n", $exception->getMessage()); $this->assertEquals(60, RequestException::$truncateAt); @@ -1414,6 +1419,8 @@ public function testRequestExceptionDoesNotTruncateButRequestDoes() $exception = $e; } + $exception->report(); + $this->assertEquals("HTTP request returned status code 403:\n[\"e (truncated...)\n", $exception->getMessage()); $this->assertFalse(RequestException::$truncateAt); @@ -1428,6 +1435,8 @@ public function testAsyncRequestExceptionsRespectRequestTruncation() $exception = $this->factory->async()->throw()->truncateExceptionsAt(4)->get('http://foo.com/json')->wait(); + $exception->report(); + $this->assertInstanceOf(RequestException::class, $exception); $this->assertEquals("HTTP request returned status code 403:\n[\"er (truncated...)\n", $exception->getMessage()); $this->assertFalse(RequestException::$truncateAt); From 1c30f547a3117bac99dc62a0afe767810cb112fa Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:51:30 +0000 Subject: [PATCH 030/153] Update version to v12.38.0 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 1a4de5ac328b..0d5199046669 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.37.0'; + const VERSION = '12.38.0'; /** * The base path for the Laravel installation. From e857cb53d6d6c992aa356d16b0cb568c8736ba24 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:52:59 +0000 Subject: [PATCH 031/153] Update CHANGELOG --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37a52dc09819..fc6fdca454e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,35 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.37.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.38.0...12.x) + +## [v12.38.0](https://github.com/laravel/framework/compare/v12.37.0...v12.38.0) - 2025-11-12 + +* [12.x] Cache the result of `configurationIsCached()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57665 +* [12.x] model:show command prompt for missing input with model suggestion by [@mrazinshaikh](https://github.com/mrazinshaikh) in https://github.com/laravel/framework/pull/57671 +* Don't call Model::toArray() to get attributes for factory insert by [@riesjart](https://github.com/riesjart) in https://github.com/laravel/framework/pull/57670 +* [12.x] Introduce `WithCachedRoutes` testing trait by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57623 +* [12.x] Add missing separators to `Stringable::ucwords` by [@kichetof](https://github.com/kichetof) in https://github.com/laravel/framework/pull/57688 +* [12.x] Cache result of `Application@routesAreCached()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57687 +* fix phpdoc return of HasAttributes::getArrayAttributeWithValue by [@chuckadams](https://github.com/chuckadams) in https://github.com/laravel/framework/pull/57691 +* [12.x] Remove unnecessary imports from BackgroundQueue and DeferredQueue. by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/57699 +* [12.x] add SQLite support for whereNotMorphedTo method by [@faisuc](https://github.com/faisuc) in https://github.com/laravel/framework/pull/57698 +* [12.x] Handle AWS ElasticCache failover by reconnecting when READONLY by [@wsamoht](https://github.com/wsamoht) in https://github.com/laravel/framework/pull/57685 +* [12.x] Introduce `WithCachedConfig` testing trait by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57663 +* [12.x] Fix WithCachedConfig@tearDownWithCachedConfig() by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57708 +* [12.x] Reorder some core aliases in alphabetical order. by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/57706 +* Allow Resend ^1.0 by [@ziming](https://github.com/ziming) in https://github.com/laravel/framework/pull/57713 +* [12.x] memoize result of `Application@eventsAreCached()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57709 +* [12.x] Test `Factory@insert()` with hidden by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/57722 +* [12.x] Separate workflow for Redis integration tests by [@vadimonus](https://github.com/vadimonus) in https://github.com/laravel/framework/pull/57710 +* [12.x] Types: HasDatabaseNotifications read/unread notifications by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/57718 +* [12.x] Supports Symfony 7.4 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/57724 +* [12.x] Revert lowercasing validation message placeholders by [@florianraith](https://github.com/florianraith) in https://github.com/laravel/framework/pull/57733 +* [12.x] try another way to activate Broadcast routes by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/57734 +* [12.x] Add environment information to json output of schedule:list command by [@mmachatschek](https://github.com/mmachatschek) in https://github.com/laravel/framework/pull/57741 +* [12.x] Make DumpCommand prohibitable by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/57735 +* [12.x] Clean ConsoleApplicationTest by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/57761 +* [12.x] Fix the docblock of the BroadcastManager::purge method. by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/57758 +* [12.x] Fix setting request exception truncating doesn't work on HTTP layer when configured inside `bootstrap/app.php` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/57759 ## [v12.37.0](https://github.com/laravel/framework/compare/v12.36.1...v12.37.0) - 2025-11-04 From 3187767f30378492d91ae971573c17488c8cdc74 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 13 Nov 2025 10:10:21 +0800 Subject: [PATCH 032/153] [12.x] Fix `GeneratorCommand` missing `possibleModels()` method (#57769) Regression issue introduced via #57671 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/GeneratorCommand.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index a66a5e3b9747..216f064909b0 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -242,6 +242,18 @@ protected function qualifyModel(string $model) : $rootNamespace.$model; } + /** + * Get a list of possible model names. + * + * @return array + * + * @deprecated 12.38.0 Use `findAvailableModels()` method instead. + */ + protected function possibleModels() + { + return $this->findAvailableModels(); + } + /** * Get a list of possible event names. * From 7f3012af6059f5f64a12930701cd8caed6cf7c17 Mon Sep 17 00:00:00 2001 From: crynobone <172966+crynobone@users.noreply.github.com> Date: Thu, 13 Nov 2025 02:12:47 +0000 Subject: [PATCH 033/153] Update version to v12.38.1 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0d5199046669..56e3bac132fe 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.38.0'; + const VERSION = '12.38.1'; /** * The base path for the Laravel installation. From e9282accc55cff0fe87de0edd7460bd8a8fe9d7a Mon Sep 17 00:00:00 2001 From: crynobone <172966+crynobone@users.noreply.github.com> Date: Thu, 13 Nov 2025 02:14:10 +0000 Subject: [PATCH 034/153] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6fdca454e9..d2daff4818bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.38.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.38.1...12.x) + +## [v12.38.1](https://github.com/laravel/framework/compare/v12.38.0...v12.38.1) - 2025-11-13 + +* [12.x] Fix `GeneratorCommand` missing `possibleModels()` method by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/57769 ## [v12.38.0](https://github.com/laravel/framework/compare/v12.37.0...v12.38.0) - 2025-11-12 From e45a8a686b7488757c08433aeac240ef1af7864e Mon Sep 17 00:00:00 2001 From: yousef kadah Date: Thu, 13 Nov 2025 23:01:16 +0200 Subject: [PATCH 035/153] Add Queue Pause/Resume Feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a new feature that allows pausing and resuming individual queues without stopping workers, providing granular control over queue processing. Features: - Queue::pause($queue, $ttl) - Pause a specific queue with optional TTL - Queue::resume($queue) - Resume a paused queue - Queue::isPaused($queue) - Check if a queue is paused - Queue::getPausedQueues() - Get list of all paused queues Artisan Commands: - queue:pause {queue} - Pause a queue - queue:resume {queue} - Resume a queue - queue:pause:list - List all paused queues Implementation: - Modified Worker to skip paused queues when looking for jobs - Uses cache to track paused queue status with configurable TTL - Works with all queue drivers (Redis, Database, SQS, etc.) - No jobs are lost - they remain in queue until resumed - Fully backwards compatible with existing queue functionality Files Modified: - src/Illuminate/Queue/QueueManager.php - src/Illuminate/Queue/Worker.php - src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php Files Created: - src/Illuminate/Queue/Console/PauseCommand.php - src/Illuminate/Queue/Console/ResumeCommand.php - src/Illuminate/Queue/Console/PauseListCommand.php - QUEUE_PAUSE_RESUME_FEATURE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Providers/ArtisanServiceProvider.php | 6 ++ src/Illuminate/Queue/Console/PauseCommand.php | 62 ++++++++++++++ .../Queue/Console/PauseListCommand.php | 69 ++++++++++++++++ .../Queue/Console/ResumeCommand.php | 66 +++++++++++++++ src/Illuminate/Queue/QueueManager.php | 82 +++++++++++++++++++ src/Illuminate/Queue/Worker.php | 20 +++++ 6 files changed, 305 insertions(+) create mode 100644 src/Illuminate/Queue/Console/PauseCommand.php create mode 100644 src/Illuminate/Queue/Console/PauseListCommand.php create mode 100644 src/Illuminate/Queue/Console/ResumeCommand.php diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index d2ae86019342..57aff2f3df33 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -97,9 +97,12 @@ use Illuminate\Queue\Console\ListenCommand as QueueListenCommand; use Illuminate\Queue\Console\ListFailedCommand as ListFailedQueueCommand; use Illuminate\Queue\Console\MonitorCommand as QueueMonitorCommand; +use Illuminate\Queue\Console\PauseCommand as QueuePauseCommand; +use Illuminate\Queue\Console\PauseListCommand as QueuePauseListCommand; use Illuminate\Queue\Console\PruneBatchesCommand as QueuePruneBatchesCommand; use Illuminate\Queue\Console\PruneFailedJobsCommand as QueuePruneFailedJobsCommand; use Illuminate\Queue\Console\RestartCommand as QueueRestartCommand; +use Illuminate\Queue\Console\ResumeCommand as QueueResumeCommand; use Illuminate\Queue\Console\RetryBatchCommand as QueueRetryBatchCommand; use Illuminate\Queue\Console\RetryCommand as QueueRetryCommand; use Illuminate\Queue\Console\TableCommand; @@ -150,9 +153,12 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'QueueForget' => ForgetFailedQueueCommand::class, 'QueueListen' => QueueListenCommand::class, 'QueueMonitor' => QueueMonitorCommand::class, + 'QueuePause' => QueuePauseCommand::class, + 'QueuePauseList' => QueuePauseListCommand::class, 'QueuePruneBatches' => QueuePruneBatchesCommand::class, 'QueuePruneFailedJobs' => QueuePruneFailedJobsCommand::class, 'QueueRestart' => QueueRestartCommand::class, + 'QueueResume' => QueueResumeCommand::class, 'QueueRetry' => QueueRetryCommand::class, 'QueueRetryBatch' => QueueRetryBatchCommand::class, 'QueueWork' => QueueWorkCommand::class, diff --git a/src/Illuminate/Queue/Console/PauseCommand.php b/src/Illuminate/Queue/Console/PauseCommand.php new file mode 100644 index 000000000000..66b8398637e1 --- /dev/null +++ b/src/Illuminate/Queue/Console/PauseCommand.php @@ -0,0 +1,62 @@ +manager = $manager; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + $queue = $this->argument('queue'); + $ttl = (int) $this->option('ttl'); + + $this->manager->pause($queue, $ttl); + + $this->components->info("Queue [{$queue}] has been paused."); + + return 0; + } +} diff --git a/src/Illuminate/Queue/Console/PauseListCommand.php b/src/Illuminate/Queue/Console/PauseListCommand.php new file mode 100644 index 000000000000..e881d3242778 --- /dev/null +++ b/src/Illuminate/Queue/Console/PauseListCommand.php @@ -0,0 +1,69 @@ +manager = $manager; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + $pausedQueues = $this->manager->getPausedQueues(); + + if (empty($pausedQueues)) { + $this->components->info('No queues are currently paused.'); + + return 0; + } + + $this->components->info('Paused Queues:'); + + $this->table( + ['Queue Name'], + collect($pausedQueues)->map(fn ($queue) => [$queue])->toArray() + ); + + return 0; + } +} diff --git a/src/Illuminate/Queue/Console/ResumeCommand.php b/src/Illuminate/Queue/Console/ResumeCommand.php new file mode 100644 index 000000000000..bd214db502d9 --- /dev/null +++ b/src/Illuminate/Queue/Console/ResumeCommand.php @@ -0,0 +1,66 @@ +manager = $manager; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + $queue = $this->argument('queue'); + + if (! $this->manager->isPaused($queue)) { + $this->components->warn("Queue [{$queue}] is not paused."); + + return 1; + } + + $this->manager->resume($queue); + + $this->components->info("Queue [{$queue}] has been resumed."); + + return 0; + } +} diff --git a/src/Illuminate/Queue/QueueManager.php b/src/Illuminate/Queue/QueueManager.php index 3eefe965db8d..9a1a48ed9845 100755 --- a/src/Illuminate/Queue/QueueManager.php +++ b/src/Illuminate/Queue/QueueManager.php @@ -295,6 +295,88 @@ public function setApplication($app) return $this; } + /** + * Pause a queue by name. + * + * @param string $queue + * @param int $ttl + * @return void + */ + public function pause($queue, $ttl = 86400) + { + $cache = $this->app['cache']->store(); + + $cache->put("queue_paused:{$queue}", true, $ttl); + + // Add to the list of paused queues + $pausedQueues = $this->getPausedQueues(); + + if (! in_array($queue, $pausedQueues)) { + $pausedQueues[] = $queue; + $this->storePausedQueuesList($pausedQueues); + } + } + + /** + * Resume a paused queue by name. + * + * @param string $queue + * @return void + */ + public function resume($queue) + { + $cache = $this->app['cache']->store(); + + $cache->forget("queue_paused:{$queue}"); + + // Remove from the list of paused queues + $pausedQueues = $this->getPausedQueues(); + + if (($key = array_search($queue, $pausedQueues)) !== false) { + unset($pausedQueues[$key]); + $this->storePausedQueuesList(array_values($pausedQueues)); + } + } + + /** + * Determine if a queue is paused. + * + * @param string $queue + * @return bool + */ + public function isPaused($queue) + { + $cache = $this->app['cache']->store(); + + return (bool) $cache->get("queue_paused:{$queue}", false); + } + + /** + * Get all paused queues. + * + * @return array + */ + public function getPausedQueues() + { + $cache = $this->app['cache']->store(); + + // This implementation stores a list of paused queues + return $cache->get('queue_paused_list', []); + } + + /** + * Store the list of paused queues. + * + * @param array $queues + * @return void + */ + protected function storePausedQueuesList($queues) + { + $cache = $this->app['cache']->store(); + + $cache->forever('queue_paused_list', $queues); + } + /** * Dynamically pass calls to the default connection. * diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index b89fddd044e5..faf2e4461f63 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -372,6 +372,11 @@ protected function getNextJob($connection, $queue) } foreach (explode(',', $queue) as $index => $queue) { + // Skip paused queues + if ($this->isQueuePaused($queue)) { + continue; + } + if (! is_null($job = $popJobCallback($queue, $index))) { $this->raiseAfterJobPopEvent($connection->getConnectionName(), $job); @@ -387,6 +392,21 @@ protected function getNextJob($connection, $queue) } } + /** + * Determine if a queue is paused. + * + * @param string $queue + * @return bool + */ + protected function isQueuePaused($queue) + { + if (! $this->cache) { + return false; + } + + return (bool) $this->cache->get("queue_paused:{$queue}", false); + } + /** * Process the given job. * From 045855253e0a2013c823e76940195feab74583ef Mon Sep 17 00:00:00 2001 From: yousef kadah Date: Thu, 13 Nov 2025 23:07:56 +0200 Subject: [PATCH 036/153] Add pause/resume methods to Queue Factory contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the Factory interface to include the new pause/resume methods. This resolves IDE warnings and provides proper type hints for the queue pause/resume functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/Illuminate/Contracts/Queue/Factory.php | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Illuminate/Contracts/Queue/Factory.php b/src/Illuminate/Contracts/Queue/Factory.php index 9a0bdeb12577..de9a6033a634 100644 --- a/src/Illuminate/Contracts/Queue/Factory.php +++ b/src/Illuminate/Contracts/Queue/Factory.php @@ -11,4 +11,36 @@ interface Factory * @return \Illuminate\Contracts\Queue\Queue */ public function connection($name = null); + + /** + * Pause a queue by name. + * + * @param string $queue + * @param int $ttl + * @return void + */ + public function pause($queue, $ttl = 86400); + + /** + * Resume a paused queue by name. + * + * @param string $queue + * @return void + */ + public function resume($queue); + + /** + * Determine if a queue is paused. + * + * @param string $queue + * @return bool + */ + public function isPaused($queue); + + /** + * Get all paused queues. + * + * @return array + */ + public function getPausedQueues(); } From 1d5fa563b7392b17e50efa6f23fb7b9c0004f587 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:56:07 -0500 Subject: [PATCH 037/153] Update ApplicationBuilder.php (#57778) --- .../Configuration/ApplicationBuilder.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index e386d8273c8e..e9bad20719b3 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -363,7 +363,7 @@ public function withSchedule(callable $callback) /** * Register and configure the application's exception handler. * - * @param callable|null $using + * @param callable(\Illuminate\Foundation\Configuration\Exceptions)|null $using * @return $this */ public function withExceptions(?callable $using = null) @@ -373,12 +373,12 @@ public function withExceptions(?callable $using = null) \Illuminate\Foundation\Exceptions\Handler::class ); - $using ??= fn () => true; - - $this->app->afterResolving( - \Illuminate\Foundation\Exceptions\Handler::class, - fn ($handler) => $using(new Exceptions($handler)), - ); + if ($using !== null) { + $this->app->afterResolving( + \Illuminate\Foundation\Exceptions\Handler::class, + fn ($handler) => $using(new Exceptions($handler)), + ); + } return $this; } From 41496fde971e59f0b11863cfe665f83b5d69a4ca Mon Sep 17 00:00:00 2001 From: Cas Ebbers <617080+CasEbb@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:57:06 +0100 Subject: [PATCH 038/153] Carry `--force` to `make:test` for generators with `--test` (#57777) --- src/Illuminate/Console/Concerns/CreatesMatchingTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Console/Concerns/CreatesMatchingTest.php b/src/Illuminate/Console/Concerns/CreatesMatchingTest.php index 864ad5262fa5..b19accdcacdd 100644 --- a/src/Illuminate/Console/Concerns/CreatesMatchingTest.php +++ b/src/Illuminate/Console/Concerns/CreatesMatchingTest.php @@ -40,6 +40,7 @@ protected function handleTestCreation($path) 'name' => (new Stringable($path))->after($this->laravel['path'])->beforeLast('.php')->append('Test')->replace('\\', '/'), '--pest' => $this->option('pest'), '--phpunit' => $this->option('phpunit'), + '--force' => $this->hasOption('force') && $this->option('force'), ]) == 0; } } From 6fa63817ebdc0c73a73e973d1917e4c9325425f4 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 14 Nov 2025 22:57:32 +0800 Subject: [PATCH 039/153] [12.x] Fix `Request::getAcceptableContentTypes()` changes in Symfony 7.4 (#57783) * [12.x] Fix `Request::getAcceptableContentTypes()` changes in Symfony 7.4 Symfony 7.4 changes the return value of `getAcceptableContentType()` to include additional value after `;`. E,g: `application/json; charset=utf-8` returns `application/json` in 7.3 and below while 7.4 return the full value. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../Http/Concerns/InteractsWithContentTypes.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php b/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php index 0d5f62fc7532..ec7a3000c26b 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php +++ b/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php @@ -55,6 +55,10 @@ public function accepts($contentTypes) $types = (array) $contentTypes; foreach ($accepts as $accept) { + if ($accept && $pos = strpos($accept, ';')) { + $accept = trim(substr($accept, 0, $pos)); + } + if ($accept === '*/*' || $accept === '*') { return true; } @@ -86,6 +90,10 @@ public function prefers($contentTypes) $contentTypes = (array) $contentTypes; foreach ($accepts as $accept) { + if ($accept && $pos = strpos($accept, ';')) { + $accept = trim(substr($accept, 0, $pos)); + } + if (in_array($accept, ['*/*', '*'])) { return $contentTypes[0]; } From ee3af91824a094d1df99de20feed46fc3b2958a4 Mon Sep 17 00:00:00 2001 From: Pjotr van der Horst Date: Fri, 14 Nov 2025 16:29:05 +0100 Subject: [PATCH 040/153] Accept string bindings in give attribute (#57747) --- src/Illuminate/Container/Attributes/Give.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Container/Attributes/Give.php b/src/Illuminate/Container/Attributes/Give.php index f669c58d1abb..35891a2d0d4b 100644 --- a/src/Illuminate/Container/Attributes/Give.php +++ b/src/Illuminate/Container/Attributes/Give.php @@ -12,9 +12,7 @@ class Give implements ContextualAttribute /** * Provide a concrete class implementation for dependency injection. * - * @template T - * - * @param class-string $class + * @param string $class * @param array|null $params */ public function __construct( From 4ae58938e9d5839cfc33e8385f9c20417579375c Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:33:35 -0500 Subject: [PATCH 041/153] [12.x] Fix `WithCachedConfig` to work with parallel test runs (#57785) * Update TestCase.php * test * Update TestCase.php --- src/Illuminate/Testing/Concerns/TestDatabases.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Illuminate/Testing/Concerns/TestDatabases.php b/src/Illuminate/Testing/Concerns/TestDatabases.php index d347c336c7de..bf1b910e3b7d 100644 --- a/src/Illuminate/Testing/Concerns/TestDatabases.php +++ b/src/Illuminate/Testing/Concerns/TestDatabases.php @@ -19,6 +19,13 @@ trait TestDatabases */ protected static $schemaIsUpToDate = false; + /** + * The root database name prior to concatenating the token. + * + * @var null|string + */ + protected static $originalDatabaseName = null; + /** * Boot a test database. * @@ -186,6 +193,12 @@ protected function switchToDatabase($database) */ protected function testDatabase($database) { + if (! isset(self::$originalDatabaseName)) { + self::$originalDatabaseName = $database; + } else { + $database = self::$originalDatabaseName; + } + $token = ParallelTesting::token(); return "{$database}_test_{$token}"; From 6f88214250c1993c1f587a33448692a31ba1da59 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sun, 16 Nov 2025 08:36:17 -0600 Subject: [PATCH 042/153] [12.x] Tailwind pagination styling/accessibility updates (#57793) * Tailwind pagination styling/accessibility updates - improve contrast ratios to pass AAA compliance - better indication of selected item - better indication hovered item - show appropriate cursors for non-interactable elements - use a gap for flexed items. `.justify-between` alone does not guarantee space - move `.relative` as it does not do anything - remove unnecessary z-indexes - use a simple `{{ }}` echo in HTML attributes, as they do not need embedded HTML - use `rel="next"` and `rel="prev"` consistently * Update tailwind.blade.php * Update tailwind.blade.php --- .../resources/views/simple-tailwind.blade.php | 14 +++---- .../resources/views/tailwind.blade.php | 37 +++++++++++-------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php b/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php index 34ab49d16b8d..79514721554a 100644 --- a/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php +++ b/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php @@ -1,25 +1,25 @@ @if ($paginator->hasPages()) -