From 98b527a440cab15a0fe21603d3ab2874b7197c82 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:00:08 +0000 Subject: [PATCH 001/103] Update CHANGELOG --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e5f8e99db1..cb060adf4e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.28.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.28.1...12.x) + +## [v12.28.1](https://github.com/laravel/framework/compare/v12.28.0...v12.28.1) - 2025-09-04 + +* [12.x] Rename `group` to `messageGroup` property by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/56919 +* Fix PHP_CLI_SERVER_WORKERS inside laravel/sail by [@akyrey](https://github.com/akyrey) in https://github.com/laravel/framework/pull/56923 +* Allow RouteRegistrar to be Macroable by [@moshe-autoleadstar](https://github.com/moshe-autoleadstar) in https://github.com/laravel/framework/pull/56921 +* [12.x] Fix SesV2Transport docblock by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/56917 +* [12.x] Prevent unnecessary query logging on exceptions with a custom renderer by [@luanfreitasdev](https://github.com/luanfreitasdev) in https://github.com/laravel/framework/pull/56874 +* [12.x] Reduce meaningless intermediate variables by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/56927 ## [v12.28.0](https://github.com/laravel/framework/compare/v12.27.1...v12.28.0) - 2025-09-03 From 8df7de34fd7ecde3d6c57ba0420a45a9a522ee3a Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 5 Sep 2025 01:28:47 +1000 Subject: [PATCH 002/103] Ensure cached and uncached routes share same precedence when resolving actions and names (#56920) * Ensure cached and uncached routes share same precedence * formatting * Formatting * formatting * Fix tests --- src/Illuminate/Routing/RouteCollection.php | 32 ++++++++++++++++--- .../Routing/CompiledRouteCollectionTest.php | 32 +++++++++++++++++++ tests/Integration/Routing/Fixtures/app.php | 18 +++++++++++ .../Fixtures/bootstrap/cache/.gitignore | 2 ++ .../Routing/Fixtures/cache/.gitignore | 2 ++ tests/Routing/RouteRegistrarTest.php | 7 ++++ 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 tests/Integration/Routing/Fixtures/app.php create mode 100644 tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore create mode 100644 tests/Integration/Routing/Fixtures/cache/.gitignore diff --git a/src/Illuminate/Routing/RouteCollection.php b/src/Illuminate/Routing/RouteCollection.php index 2898f0fa5eb6..11d8e79b32ba 100644 --- a/src/Illuminate/Routing/RouteCollection.php +++ b/src/Illuminate/Routing/RouteCollection.php @@ -91,7 +91,7 @@ protected function addLookups($route) // If the route has a name, we will add it to the name look-up table, so that we // will quickly be able to find the route associated with a name and not have // to iterate through every route every time we need to find a named route. - if ($name = $route->getName()) { + if (($name = $route->getName()) && ! $this->inNameLookup($name)) { $this->nameList[$name] = $route; } @@ -100,7 +100,7 @@ protected function addLookups($route) // processing a request and easily generate URLs to the given controllers. $action = $route->getAction(); - if (isset($action['controller'])) { + if (($controller = $action['controller'] ?? null) && ! $this->inActionLookup($controller)) { $this->addToActionList($action, $route); } } @@ -117,6 +117,28 @@ protected function addToActionList($action, $route) $this->actionList[trim($action['controller'], '\\')] = $route; } + /** + * Determine if the given controller is in the action lookup table. + * + * @param string $controller + * @return bool + */ + protected function inActionLookup($controller) + { + return array_key_exists($controller, $this->actionList); + } + + /** + * Determine if the given name is in the name lookup table. + * + * @param string $name + * @return bool + */ + protected function inNameLookup($name) + { + return array_key_exists($name, $this->nameList); + } + /** * Refresh the name look-up table. * @@ -129,8 +151,8 @@ public function refreshNameLookups() $this->nameList = []; foreach ($this->allRoutes as $route) { - if ($route->getName()) { - $this->nameList[$route->getName()] = $route; + if (($name = $route->getName()) && ! $this->inNameLookup($name)) { + $this->nameList[$name] = $route; } } } @@ -147,7 +169,7 @@ public function refreshActionLookups() $this->actionList = []; foreach ($this->allRoutes as $route) { - if (isset($route->getAction()['controller'])) { + if (($controller = $route->getAction()['controller'] ?? null) && ! $this->inActionLookup($controller)) { $this->addToActionList($route->getAction(), $route); } } diff --git a/tests/Integration/Routing/CompiledRouteCollectionTest.php b/tests/Integration/Routing/CompiledRouteCollectionTest.php index fcaa4dad5d4e..35d12753b5ca 100644 --- a/tests/Integration/Routing/CompiledRouteCollectionTest.php +++ b/tests/Integration/Routing/CompiledRouteCollectionTest.php @@ -94,6 +94,38 @@ public function testRouteCollectionCanRetrieveByAction() $this->assertSame($action, Arr::except($route->getAction(), 'as')); } + public function testCompiledAndNonCompiledUrlResolutionHasSamePrecedenceForActions() + { + @unlink(__DIR__.'/Fixtures/cache/routes-v7.php'); + $this->app->useBootstrapPath(__DIR__.'/Fixtures'); + $app = (static function () { + $refresh = true; + + return require __DIR__.'/Fixtures/app.php'; + })(); + $app['router']->get('/foo/{bar}', ['FooController', 'show']); + $app['router']->get('/foo/{bar}/{baz}', ['FooController', 'show']); + $app['router']->getRoutes()->refreshActionLookups(); + + $this->assertSame('foo/{bar}', $app['router']->getRoutes()->getByAction('FooController@show')->uri); + + $this->artisan('route:cache')->assertExitCode(0); + require __DIR__.'/Fixtures/cache/routes-v7.php'; + + $this->assertSame('foo/{bar}', $app['router']->getRoutes()->getByAction('FooController@show')->uri); + + unlink(__DIR__.'/Fixtures/cache/routes-v7.php'); + } + + public function testCompiledAndNonCompiledUrlResolutionHasSamePrecedenceForNames() + { + $this->router->get('/foo/{bar}', ['FooController', 'show'])->name('foo.show'); + $this->router->get('/foo/{bar}/{baz}', ['FooController', 'show'])->name('foo.show'); + $this->router->getRoutes()->refreshNameLookups(); + + $this->assertSame('foo/{bar}', $this->router->getRoutes()->getByName('foo.show')->uri); + } + public function testRouteCollectionCanGetIterator() { $this->routeCollection->add($this->newRoute('GET', 'foo/index', [ diff --git a/tests/Integration/Routing/Fixtures/app.php b/tests/Integration/Routing/Fixtures/app.php new file mode 100644 index 000000000000..f0921fd8b3ae --- /dev/null +++ b/tests/Integration/Routing/Fixtures/app.php @@ -0,0 +1,18 @@ +create(); +} else { + return AppCache::$app ??= Application::configure(basePath: __DIR__)->create(); +} diff --git a/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore b/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Integration/Routing/Fixtures/cache/.gitignore b/tests/Integration/Routing/Fixtures/cache/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Integration/Routing/Fixtures/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index d92967a758c3..2c0c4cca9350 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -719,6 +719,8 @@ public function testCanSetShallowOptionOnRegisteredResource() public function testCanSetScopedOptionOnRegisteredResource() { $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped(); + $this->router->getRoutes()->refreshNameLookups(); + $this->assertSame( ['user' => null], $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() @@ -731,6 +733,7 @@ public function testCanSetScopedOptionOnRegisteredResource() $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped([ 'task' => 'slug', ]); + $this->router->getRoutes()->refreshNameLookups(); $this->assertSame( ['user' => null], $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() @@ -904,6 +907,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredResource() ->middlewareFor('index', RouteRegistrarMiddlewareStub::class) ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.index')->gatherMiddleware(), ['default', RouteRegistrarMiddlewareStub::class]); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['default', 'one']); @@ -918,6 +922,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredResource() ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']) ->middleware('default'); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.index')->gatherMiddleware(), [RouteRegistrarMiddlewareStub::class, 'default']); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['one', 'default']); @@ -1448,6 +1453,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredSingletonReso ->middlewareFor('show', RouteRegistrarMiddlewareStub::class) ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['default', 'one']); $this->assertEquals($this->router->getRoutes()->getByName('users.store')->gatherMiddleware(), ['default', 'one']); @@ -1463,6 +1469,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredSingletonReso ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']) ->middleware('default'); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['one', 'default']); $this->assertEquals($this->router->getRoutes()->getByName('users.store')->gatherMiddleware(), ['one', 'default']); From 66a7fda555c8733f3ea655d644a79a5e4544f35b Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Thu, 4 Sep 2025 18:34:14 +0300 Subject: [PATCH 003/103] Re-enable previously commented assertions (#56930) --- tests/Support/SupportStrTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index cb36d9eb98c1..2049190e94e7 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -1357,8 +1357,8 @@ public function testWordCount() $this->assertEquals(2, Str::wordCount('Hello, world!')); $this->assertEquals(10, Str::wordCount('Hi, this is my first contribution to the Laravel framework.')); - // $this->assertEquals(0, Str::wordCount('мама')); - // $this->assertEquals(0, Str::wordCount('мама мыла раму')); + $this->assertEquals(0, Str::wordCount('мама')); + $this->assertEquals(0, Str::wordCount('мама мыла раму')); $this->assertEquals(1, Str::wordCount('мама', 'абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')); $this->assertEquals(3, Str::wordCount('мама мыла раму', 'абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')); From 5ae26cb0c45c80445c5557b5034aa43028b5916d Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:35:42 +0300 Subject: [PATCH 004/103] Reorder .gitignore entries for consistency and readability (#56963) --- .gitignore | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 39397245b7ec..496161c0903d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ +.DS_Store +.phpunit.result.cache +/.fleet +/.idea /.phpunit.cache +/phpunit.xml +/.vscode /vendor composer.phar composer.lock -.DS_Store Thumbs.db -/phpunit.xml -/.idea -/.fleet -/.vscode -.phpunit.result.cache From f8f282048f107c6cd28fb2897efba87db85fea1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Mon, 8 Sep 2025 00:39:11 +0200 Subject: [PATCH 005/103] [12.x] SQLite: Allow setting any pragmas (#56962) * SQLite: Allow setting any pragmas * formatting --------- Co-authored-by: Taylor Otwell --- config/database.php | 1 + .../Database/Connectors/SQLiteConnector.php | 19 +++++++++++++++++++ .../Database/Sqlite/ConnectorTest.php | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/config/database.php b/config/database.php index 77f5134c858d..5127e8ca48ac 100644 --- a/config/database.php +++ b/config/database.php @@ -42,6 +42,7 @@ 'journal_mode' => null, 'synchronous' => null, 'transaction_mode' => 'DEFERRED', + 'pragmas' => [], ], 'mysql' => [ diff --git a/src/Illuminate/Database/Connectors/SQLiteConnector.php b/src/Illuminate/Database/Connectors/SQLiteConnector.php index 2e2ed8758919..858549ec55de 100755 --- a/src/Illuminate/Database/Connectors/SQLiteConnector.php +++ b/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -20,6 +20,7 @@ public function connect(array $config) $connection = $this->createConnection("sqlite:{$path}", $config, $options); + $this->configurePragmas($connection, $config); $this->configureForeignKeyConstraints($connection, $config); $this->configureBusyTimeout($connection, $config); $this->configureJournalMode($connection, $config); @@ -62,6 +63,24 @@ protected function parseDatabasePath(string $path): string return $path; } + /** + * Set miscellaneous user-configured pragmas. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configurePragmas($connection, array $config): void + { + if (! isset($config['pragmas'])) { + return; + } + + foreach ($config['pragmas'] as $pragma => $value) { + $connection->prepare("pragma {$pragma} = {$value}")->execute(); + } + } + /** * Enable or disable foreign key constraints if configured. * diff --git a/tests/Integration/Database/Sqlite/ConnectorTest.php b/tests/Integration/Database/Sqlite/ConnectorTest.php index 04ead4ba1ff9..95f273f6745f 100644 --- a/tests/Integration/Database/Sqlite/ConnectorTest.php +++ b/tests/Integration/Database/Sqlite/ConnectorTest.php @@ -41,12 +41,16 @@ public function testConnectionConfigurations() 'busy_timeout' => 12345, 'journal_mode' => 'wal', 'synchronous' => 'normal', + 'pragmas' => [ + 'query_only' => true, + ], ])->getSchemaBuilder(); $this->assertSame(1, $schema->pragma('foreign_keys')); $this->assertSame(12345, $schema->pragma('busy_timeout')); $this->assertSame('wal', $schema->pragma('journal_mode')); $this->assertSame(1, $schema->pragma('synchronous')); + $this->assertSame(1, $schema->pragma('query_only')); $schema->pragma('foreign_keys', 0); $schema->pragma('busy_timeout', 54321); From d6e4cbb45fd5bf5f97d4e023739cb42925b2a56b Mon Sep 17 00:00:00 2001 From: Ali Khosrojerdi Date: Mon, 8 Sep 2025 02:11:20 +0330 Subject: [PATCH 006/103] refactor: remove unused array from docblock (#56961) --- src/Illuminate/Routing/PendingResourceRegistration.php | 2 +- src/Illuminate/Routing/PendingSingletonResourceRegistration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Routing/PendingResourceRegistration.php b/src/Illuminate/Routing/PendingResourceRegistration.php index 7e16f7e87ee9..a190538e9536 100644 --- a/src/Illuminate/Routing/PendingResourceRegistration.php +++ b/src/Illuminate/Routing/PendingResourceRegistration.php @@ -198,7 +198,7 @@ public function middlewareFor($methods, $middleware) * Specify middleware that should be removed from the resource routes. * * @param array|string $middleware - * @return $this|array + * @return $this */ public function withoutMiddleware($middleware) { diff --git a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php index 2d845d300d2e..2c20e3c3168f 100644 --- a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php +++ b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php @@ -222,7 +222,7 @@ public function middlewareFor($methods, $middleware) * Specify middleware that should be removed from the resource routes. * * @param array|string $middleware - * @return $this|array + * @return $this */ public function withoutMiddleware($middleware) { From cd76f26f1b6ef3e49721df56aad96e5a58fe14bc Mon Sep 17 00:00:00 2001 From: Moshe Brodsky <44633930+moshe-autoleadstar@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:41:43 +0300 Subject: [PATCH 007/103] PendingResourceRegistration withoutMiddleware never returns array (#56959) From 888c47b1410e71a268a162c31a30308a150b2853 Mon Sep 17 00:00:00 2001 From: Sjors Ottjes Date: Mon, 8 Sep 2025 00:47:49 +0200 Subject: [PATCH 008/103] allow fakerphp/faker to not be installed (#56953) --- composer.json | 2 +- src/Illuminate/Database/DatabaseServiceProvider.php | 4 ++++ src/Illuminate/Database/Eloquent/Factories/Factory.php | 6 +++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 76d21a4bbb23..cd6cf1f77640 100644 --- a/composer.json +++ b/composer.json @@ -180,7 +180,7 @@ "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", diff --git a/src/Illuminate/Database/DatabaseServiceProvider.php b/src/Illuminate/Database/DatabaseServiceProvider.php index 794090b34776..82ae4568fc51 100755 --- a/src/Illuminate/Database/DatabaseServiceProvider.php +++ b/src/Illuminate/Database/DatabaseServiceProvider.php @@ -96,6 +96,10 @@ protected function registerConnectionServices() */ protected function registerFakerGenerator() { + if (! class_exists(FakerGenerator::class)) { + return; + } + $this->app->singleton(FakerGenerator::class, function ($app, $parameters) { $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'); diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index e3636c373e69..ed80b9fe5305 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -953,10 +953,14 @@ public static function dontExpandRelationshipsByDefault() /** * Get a new Faker instance. * - * @return \Faker\Generator + * @return \Faker\Generator|null */ protected function withFaker() { + if (! class_exists(Generator::class)) { + return; + } + return Container::getInstance()->make(Generator::class); } From 0dede4fcf9c35e889d02930abdc27a47d91559cb Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:59:05 +0300 Subject: [PATCH 009/103] Fix Validator placeholderHash PHPDoc (#56947) --- src/Illuminate/Validation/Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 0fcd7eb50b40..5d18ceba448f 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -309,7 +309,7 @@ class Validator implements ValidatorContract /** * The current random hash for the validator. * - * @var string + * @var string|null */ protected static $placeholderHash; From 6079a250a8c000c5fd643a5e54d62da53aa55ff4 Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Sun, 7 Sep 2025 23:59:52 +0100 Subject: [PATCH 010/103] Handle MariaDB innodb_snapshot_isolation=ON (#56945) Fixes #56944 --- src/Illuminate/Database/ConcurrencyErrorDetector.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/ConcurrencyErrorDetector.php b/src/Illuminate/Database/ConcurrencyErrorDetector.php index 0b388111bb65..3a5cb1dcd0a6 100644 --- a/src/Illuminate/Database/ConcurrencyErrorDetector.php +++ b/src/Illuminate/Database/ConcurrencyErrorDetector.php @@ -33,6 +33,7 @@ public function causedByConcurrencyError(Throwable $e): bool 'has been chosen as the deadlock victim', 'Lock wait timeout exceeded; try restarting transaction', 'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction', + 'Record has changed since last read in table', ]); } } From d2de325fc75ad14b9bf9f48e41a440a9cb38f8e8 Mon Sep 17 00:00:00 2001 From: Tuan Do Pham Date: Mon, 8 Sep 2025 06:02:42 +0700 Subject: [PATCH 011/103] [12.x] Add PhpRedis pack ignore numbers option (#56941) * Add phpredis pack ignore numbers support * Update PhpRedisConnector.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Redis/Connectors/PhpRedisConnector.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php index ec0dd08e333b..77be9335a5cf 100644 --- a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php +++ b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php @@ -143,6 +143,11 @@ protected function createClient(array $config) if (array_key_exists('compression_level', $config)) { $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']); } + + if (defined('Redis::OPT_PACK_IGNORE_NUMBERS') && + array_key_exists('pack_ignore_numbers', $config)) { + $client->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $config['pack_ignore_numbers']); + } }); } From 336cf2b43eba314c20ed31598859fe2077b01c1e Mon Sep 17 00:00:00 2001 From: Iman Parvizi <70389233+realpvz@users.noreply.github.com> Date: Mon, 8 Sep 2025 02:34:34 +0330 Subject: [PATCH 012/103] test(support): add edge-case tuples for preg_replace_array (#56937) --- tests/Support/SupportHelpersTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 1074e97dd6d9..fd6a28cf5c50 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -1493,6 +1493,12 @@ public static function providesPregReplaceArrayData() ['/%s/', ['a', 'b', 'c'], 'Hi', 'Hi'], ['//', [], '', ''], ['/%s/', ['a'], '', ''], + // non-sequential numeric keys → should still consume in natural order + ['/%s/', [2 => 'A', 10 => 'B'], '%s %s', 'A B'], + // associative keys → order should be insertion order, not keys/pointer + ['/%s/', ['first' => 'A', 'second' => 'B'], '%s %s', 'A B'], + // values that are "falsy" but must not be treated as empty by mistake, false->'' , null->'' + ['/%s/', ['0', 0, false, null], '%s|%s|%s|%s', '0|0||'], // The internal pointer of this array is not at the beginning ['/%s/', $pointerArray, 'Hi, %s %s', 'Hi, Taylor Otwell'], ]; From 17b273f050cc962e6983c53afc0b74a4475dafe2 Mon Sep 17 00:00:00 2001 From: Sandro Gehri Date: Mon, 8 Sep 2025 01:06:00 +0200 Subject: [PATCH 013/103] [12.x] Allow for BackedEnum on dynamic blade component (#56940) * Allow for BackedEnum on dynamic blade component * use enum_value() helper function Co-authored-by: Andrew Brown * Fix missing function import --------- Co-authored-by: Andrew Brown --- src/Illuminate/View/DynamicComponent.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/View/DynamicComponent.php b/src/Illuminate/View/DynamicComponent.php index b34d3759d086..30ddd9114ab5 100644 --- a/src/Illuminate/View/DynamicComponent.php +++ b/src/Illuminate/View/DynamicComponent.php @@ -2,11 +2,14 @@ namespace Illuminate\View; +use BackedEnum; use Illuminate\Container\Container; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\View\Compilers\ComponentTagCompiler; +use function Illuminate\Support\enum_value; + class DynamicComponent extends Component { /** @@ -33,11 +36,11 @@ class DynamicComponent extends Component /** * Create a new component instance. * - * @param string $component + * @param \BackedEnum|string $component */ - public function __construct(string $component) + public function __construct(BackedEnum|string $component) { - $this->component = $component; + $this->component = (string) enum_value($component); } /** From 18af3061764c298a63ecc89431b601a42c568fdc Mon Sep 17 00:00:00 2001 From: Vincent van Hoven <11269255+vincentvanhoven@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:09:09 +0200 Subject: [PATCH 014/103] [12.x] Remove one redundant array access (#56931) * [REMOVE] Redundant array access * [retrigger tests] * Revert "[retrigger tests]" This reverts commit 9e83eb8b704aa7a46d9b72e8e56eb1453ef939da. --- src/Illuminate/Collections/Arr.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 51bd12c743ec..78d778d8cb31 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -446,7 +446,7 @@ public static function get($array, $key, $default = null) } if (! str_contains($key, '.')) { - return $array[$key] ?? value($default); + return value($default); } foreach (explode('.', $key) as $segment) { From 608f97ecc183d2a3a832903ccd416c1a090973bb Mon Sep 17 00:00:00 2001 From: Hocine Saad Date: Mon, 8 Sep 2025 00:30:00 +0100 Subject: [PATCH 015/103] [12.x] Add withoutGlobalScopesExcept() to keep only specified global scopes (#56957) * Add method to remove all except specified global scopes * Fix parameter type hint * Add test for removing all global scopes except specified ones * Update Builder.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Builder.php | 15 +++++++++++++++ .../Database/DatabaseEloquentGlobalScopesTest.php | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 51e071b9c9b9..6a7a5a074c32 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -242,6 +242,21 @@ public function withoutGlobalScopes(?array $scopes = null) return $this; } + /** + * Remove all global scopes except the given scopes. + * + * @param array $scopes + * @return $this + */ + public function withoutGlobalScopesExcept(array $scopes = []) + { + $this->withoutGlobalScopes( + array_diff(array_keys($this->scopes), $scopes) + ); + + return $this; + } + /** * Get an array of global scopes that were removed from the query. * diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index 8e6423797fdd..ebc44ec686ff 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -108,6 +108,18 @@ public function testAllGlobalScopesCanBeRemoved() $this->assertEquals([], $query->getBindings()); } + public function testAllGlobalScopesCanBeRemovedExceptSpecified() + { + $model = new EloquentClosureGlobalScopesTestModel; + $query = $model->newQuery()->withoutGlobalScopesExcept(['active_scope']); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + + $query = EloquentClosureGlobalScopesTestModel::withoutGlobalScopesExcept(['active_scope']); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + } + public function testGlobalScopesWithOrWhereConditionsAreNested() { $model = new EloquentClosureGlobalScopesWithOrTestModel; From e48bd1f6aab75896fcb52889126ff5b52bb16ddd Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:15:19 +0300 Subject: [PATCH 016/103] Make visibility consistent (#56970) --- src/Illuminate/Validation/Concerns/ReplacesAttributes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php index be9abf169e8b..23cbfb9c0448 100644 --- a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php @@ -598,7 +598,7 @@ protected function replaceRequiredIfAccepted($message, $attribute, $rule, $param * @param array $parameters * @return string */ - public function replaceRequiredIfDeclined($message, $attribute, $rule, $parameters) + protected function replaceRequiredIfDeclined($message, $attribute, $rule, $parameters) { return $this->replaceRequiredIfAccepted($message, $attribute, $rule, $parameters); } @@ -662,7 +662,7 @@ protected function replaceProhibitedIfAccepted($message, $attribute, $rule, $par * @param array $parameters * @return string */ - public function replaceProhibitedIfDeclined($message, $attribute, $rule, $parameters) + protected function replaceProhibitedIfDeclined($message, $attribute, $rule, $parameters) { return $this->replaceRequiredIfAccepted($message, $attribute, $rule, $parameters); } From 6d358c154acfb1a202de81f9430e76d32b6762e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:15:37 +0200 Subject: [PATCH 017/103] Change list to tuple in PHPDoc block (#56967) --- .../Database/Eloquent/Concerns/TransformsToResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php index dfd11a86d70d..379db813e5d7 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php +++ b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php @@ -42,7 +42,7 @@ protected function guessResource(): JsonResource /** * Guess the resource class name for the model. * - * @return array> + * @return array{class-string<\Illuminate\Http\Resources\Json\JsonResource>, class-string<\Illuminate\Http\Resources\Json\JsonResource>} */ public static function guessResourceName(): array { From c7ebbf62be5674cdbf454a9761636d820e4f5845 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:15:53 -0400 Subject: [PATCH 018/103] aggregate service provider docblocks (#56968) --- src/Illuminate/Support/AggregateServiceProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/AggregateServiceProvider.php b/src/Illuminate/Support/AggregateServiceProvider.php index d7425c5c2586..3d162c6ab95b 100644 --- a/src/Illuminate/Support/AggregateServiceProvider.php +++ b/src/Illuminate/Support/AggregateServiceProvider.php @@ -7,14 +7,14 @@ class AggregateServiceProvider extends ServiceProvider /** * The provider class names. * - * @var array + * @var array> */ protected $providers = []; /** * An array of the service provider instances. * - * @var array + * @var array */ protected $instances = []; @@ -35,7 +35,7 @@ public function register() /** * Get the services provided by the provider. * - * @return array + * @return array */ public function provides() { From 9c507efeb354f52083e59cde6083a5ef3da5a243 Mon Sep 17 00:00:00 2001 From: Thojo0 <53666000+thojo0@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:17:25 +0200 Subject: [PATCH 019/103] [12.x] add --whisper option to schedule:work command (#56969) * add --wisper option to schedule:work * Update ScheduleWorkCommand.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php index 647c4201b2d9..f91d3dc8b6ba 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php @@ -18,7 +18,9 @@ class ScheduleWorkCommand extends Command * * @var string */ - protected $signature = 'schedule:work {--run-output-file= : The file to direct schedule:run output to}'; + protected $signature = 'schedule:work + {--run-output-file= : The file to direct schedule:run output to} + {--whisper : Do not output message indicating that no jobs were ready to run}'; /** * The console command description. @@ -43,6 +45,10 @@ public function handle() $command = Application::formatCommandString('schedule:run'); + if ($this->option('whisper')) { + $command .= ' --whisper'; + } + if ($this->option('run-output-file')) { $command .= ' >> '.ProcessUtils::escapeArgument($this->option('run-output-file')).' 2>&1'; } From 4bb896968c9b8bde8d19c86117694b50909804f8 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Tue, 9 Sep 2025 01:21:12 +0300 Subject: [PATCH 020/103] Update Faker suggestion to match skeleton version (#56974) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cd6cf1f77640..382e7dab44a9 100644 --- a/composer.json +++ b/composer.json @@ -180,7 +180,7 @@ "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", - "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", From d47515fd7f71357287becfb2b0fabfcce4f9acf9 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Tue, 9 Sep 2025 19:07:14 -0500 Subject: [PATCH 021/103] remove unnecessary `with()` helper call (#56975) if you're not passing a second parameter with a closure to `with()`, it doesn't do anything other than return the value. this is verrrry old legacy code from 10+ years ago that isn't necessary anymore to chain. --- src/Illuminate/Console/QuestionHelper.php | 2 +- src/Illuminate/Console/View/Components/Component.php | 2 +- src/Illuminate/Console/View/Components/Error.php | 2 +- src/Illuminate/Console/View/Components/Factory.php | 2 +- src/Illuminate/Console/View/Components/Info.php | 2 +- src/Illuminate/Console/View/Components/Success.php | 2 +- src/Illuminate/Console/View/Components/Warn.php | 3 +-- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Console/QuestionHelper.php b/src/Illuminate/Console/QuestionHelper.php index 5cbab6f0f1bc..64f8f26dbcd4 100644 --- a/src/Illuminate/Console/QuestionHelper.php +++ b/src/Illuminate/Console/QuestionHelper.php @@ -62,7 +62,7 @@ protected function writePrompt(OutputInterface $output, Question $question): voi if ($question instanceof ChoiceQuestion) { foreach ($question->getChoices() as $key => $value) { - with(new TwoColumnDetail($output))->render($value, $key); + (new TwoColumnDetail($output))->render($value, $key); } } diff --git a/src/Illuminate/Console/View/Components/Component.php b/src/Illuminate/Console/View/Components/Component.php index f515f916ff62..af1ce0ea9aaa 100644 --- a/src/Illuminate/Console/View/Components/Component.php +++ b/src/Illuminate/Console/View/Components/Component.php @@ -103,7 +103,7 @@ protected function mutate($data, $mutators) */ protected function usingQuestionHelper($callable) { - $property = with(new ReflectionClass(OutputStyle::class)) + $property = (new ReflectionClass(OutputStyle::class)) ->getParentClass() ->getProperty('questionHelper'); diff --git a/src/Illuminate/Console/View/Components/Error.php b/src/Illuminate/Console/View/Components/Error.php index 73196cc8440e..bb19ed42f106 100644 --- a/src/Illuminate/Console/View/Components/Error.php +++ b/src/Illuminate/Console/View/Components/Error.php @@ -15,6 +15,6 @@ class Error extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('error', $string, $verbosity); + (new Line($this->output))->render('error', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Factory.php b/src/Illuminate/Console/View/Components/Factory.php index 2929279057ee..2dde02f72384 100644 --- a/src/Illuminate/Console/View/Components/Factory.php +++ b/src/Illuminate/Console/View/Components/Factory.php @@ -56,6 +56,6 @@ public function __call($method, $parameters) 'Console component [%s] not found.', $method ))); - return with(new $component($this->output))->render(...$parameters); + return (new $component($this->output))->render(...$parameters); } } diff --git a/src/Illuminate/Console/View/Components/Info.php b/src/Illuminate/Console/View/Components/Info.php index 765142246fed..ac6652907844 100644 --- a/src/Illuminate/Console/View/Components/Info.php +++ b/src/Illuminate/Console/View/Components/Info.php @@ -15,6 +15,6 @@ class Info extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('info', $string, $verbosity); + (new Line($this->output))->render('info', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Success.php b/src/Illuminate/Console/View/Components/Success.php index 927cafe51e94..9b1419102516 100644 --- a/src/Illuminate/Console/View/Components/Success.php +++ b/src/Illuminate/Console/View/Components/Success.php @@ -15,6 +15,6 @@ class Success extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('success', $string, $verbosity); + (new Line($this->output))->render('success', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Warn.php b/src/Illuminate/Console/View/Components/Warn.php index 20adb1f272b7..d00656ab010e 100644 --- a/src/Illuminate/Console/View/Components/Warn.php +++ b/src/Illuminate/Console/View/Components/Warn.php @@ -15,7 +15,6 @@ class Warn extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output)) - ->render('warn', $string, $verbosity); + (new Line($this->output))->render('warn', $string, $verbosity); } } From 0789a84112419ed664741cf4afb9d23214e4c5c9 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Wed, 10 Sep 2025 03:08:00 +0300 Subject: [PATCH 022/103] Config: Move some items into pragmas (#56980) --- config/database.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/config/database.php b/config/database.php index 5127e8ca48ac..8ac877358719 100644 --- a/config/database.php +++ b/config/database.php @@ -38,11 +38,12 @@ 'prefix' => '', 'prefix_indexes' => null, 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), - 'busy_timeout' => null, - 'journal_mode' => null, - 'synchronous' => null, - 'transaction_mode' => 'DEFERRED', - 'pragmas' => [], + 'pragmas' => [ + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + ], ], 'mysql' => [ From 6ebd4887570e2ef27a25ea671e526c13409e3ac4 Mon Sep 17 00:00:00 2001 From: Kamil Kozak Date: Wed, 10 Sep 2025 03:38:44 +0200 Subject: [PATCH 023/103] Add callback support to takeUntilTimeout in LazyCollection (#56981) * Add callback support to takeUntilTimeout in LazyCollection The takeUntilTimeout method now accepts an optional callback that is invoked when the timeout is reached, providing the last yielded value and key. Tests and type assertions have been updated to cover the new callback functionality. * Fix indentation * Simplify initialization of `$timedOutWith` in SupportLazyCollectionTest --- src/Illuminate/Collections/LazyCollection.php | 15 ++++++++++++--- tests/Support/SupportLazyCollectionTest.php | 7 ++++++- types/Support/LazyCollection.php | 4 ++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 6e1e5c2bc4ab..bb82e2125e1b 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1630,17 +1630,22 @@ public function takeUntil($value) } /** - * Take items in the collection until a given point in time. + * Take items in the collection until a given point in time, with an optional callback on timeout. * * @param \DateTimeInterface $timeout + * @param callable(TValue|null, TKey|null): mixed|null $callback * @return static */ - public function takeUntilTimeout(DateTimeInterface $timeout) + public function takeUntilTimeout(DateTimeInterface $timeout, ?callable $callback = null) { $timeout = $timeout->getTimestamp(); - return new static(function () use ($timeout) { + return new static(function () use ($timeout, $callback) { if ($this->now() >= $timeout) { + if ($callback) { + $callback(null, null); + } + return; } @@ -1648,6 +1653,10 @@ public function takeUntilTimeout(DateTimeInterface $timeout) yield $key => $value; if ($this->now() >= $timeout) { + if ($callback) { + $callback($value, $key); + } + break; } } diff --git a/tests/Support/SupportLazyCollectionTest.php b/tests/Support/SupportLazyCollectionTest.php index 5dcedbc824c6..7c0f2a87093e 100644 --- a/tests/Support/SupportLazyCollectionTest.php +++ b/tests/Support/SupportLazyCollectionTest.php @@ -186,6 +186,8 @@ public function testTakeUntilTimeout() $mock = m::mock(LazyCollection::class.'[now]'); + $timedOutWith = []; + $results = $mock ->times(10) ->tap(function ($collection) use ($mock, $timeout) { @@ -200,10 +202,13 @@ public function testTakeUntilTimeout() $timeout->getTimestamp() ); }) - ->takeUntilTimeout($timeout) + ->takeUntilTimeout($timeout, function ($value, $key) use (&$timedOutWith) { + $timedOutWith = [$value, $key]; + }) ->all(); $this->assertSame([1, 2], $results); + $this->assertSame([2, 1], $timedOutWith); m::close(); } diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index 4e96ba431f70..d9f49e8593aa 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -751,6 +751,10 @@ public function toArray(): array })); assertType('Illuminate\Support\LazyCollection', $collection->takeUntilTimeout(new DateTime())); +assertType('Illuminate\Support\LazyCollection', $collection->takeUntilTimeout(new DateTime(), function ($user, $int) { + assertType('User|null', $user); + // assertType('int|null', $int); +})); assertType('Illuminate\Support\LazyCollection', $collection->make([1])->takeWhile(1)); assertType('Illuminate\Support\LazyCollection', $collection->takeWhile(new User)); From af4caf06bb9fdd1520d98681b52166038eb13f65 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:04:39 +0300 Subject: [PATCH 024/103] Utilize is_finite() (#56990) --- src/Illuminate/Cache/RedisStore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index c53e74efe3a8..8cff1af0c224 100755 --- a/src/Illuminate/Cache/RedisStore.php +++ b/src/Illuminate/Cache/RedisStore.php @@ -474,7 +474,7 @@ protected function serialize($value) */ protected function shouldBeStoredWithoutSerialization($value): bool { - return is_numeric($value) && ! in_array($value, [INF, -INF]) && ! is_nan($value); + return is_numeric($value) && is_finite($value); } /** From a8e00c4037839b577ae2fde1269309f8adfe8c02 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:04:57 -0400 Subject: [PATCH 025/103] property promotion (#56989) --- src/Illuminate/Log/Events/MessageLogged.php | 37 +++++---------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/Illuminate/Log/Events/MessageLogged.php b/src/Illuminate/Log/Events/MessageLogged.php index b3458815af5d..7734578a41cd 100644 --- a/src/Illuminate/Log/Events/MessageLogged.php +++ b/src/Illuminate/Log/Events/MessageLogged.php @@ -4,38 +4,17 @@ class MessageLogged { - /** - * The log "level". - * - * @var string - */ - public $level; - - /** - * The log message. - * - * @var string - */ - public $message; - - /** - * The log context. - * - * @var array - */ - public $context; - /** * Create a new event instance. * - * @param string $level - * @param string $message - * @param array $context + * @param "emergency"|"alert"|"critical"|"error"|"warning"|"notice"|"info"|"debug" $level The log "level". + * @param string $message The log message. + * @param array $context The log context. */ - public function __construct($level, $message, array $context = []) - { - $this->level = $level; - $this->message = $message; - $this->context = $context; + public function __construct( + public $level, + public $message, + public array $context = [] + ) { } } From 4871ce5433fe78a5fbc2d2a27b3948d7166619c0 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 10 Sep 2025 07:06:07 -0500 Subject: [PATCH 026/103] do not use `with()` helper when no second argument is passed (#56986) if no second argument is passed to `with()` it simply returns the value, so there's no reason to call it when we only pass 1 argument. while the performance impact it probably negligible, it does take 1 thing out of the call stack, which is helpful. --- src/Illuminate/Auth/Access/Gate.php | 2 +- .../Database/Schema/SqliteSchemaState.php | 4 +-- src/Illuminate/Database/Seeder.php | 4 +-- .../Foundation/Exceptions/Handler.php | 6 ++--- tests/Console/View/ComponentsTest.php | 26 +++++++++---------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index f64f584ffc78..2341efd8109d 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -186,7 +186,7 @@ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResp $response = $condition; } - return with($response instanceof Response ? $response : new Response( + return ($response instanceof Response ? $response : new Response( (bool) $response === $allowWhenResponseIs, $message, $code ))->authorize(); } diff --git a/src/Illuminate/Database/Schema/SqliteSchemaState.php b/src/Illuminate/Database/Schema/SqliteSchemaState.php index bda420fefe31..23cf3896f41b 100644 --- a/src/Illuminate/Database/Schema/SqliteSchemaState.php +++ b/src/Illuminate/Database/Schema/SqliteSchemaState.php @@ -16,7 +16,7 @@ class SqliteSchemaState extends SchemaState */ public function dump(Connection $connection, $path) { - with($process = $this->makeProcess( + ($process = $this->makeProcess( $this->baseCommand().' ".schema --indent"' ))->setTimeout(null)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // @@ -39,7 +39,7 @@ public function dump(Connection $connection, $path) */ protected function appendMigrationData(string $path) { - with($process = $this->makeProcess( + ($process = $this->makeProcess( $this->baseCommand().' ".dump \''.$this->getMigrationTable().'\'"' ))->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // diff --git a/src/Illuminate/Database/Seeder.php b/src/Illuminate/Database/Seeder.php index 08e57b2d7834..67b617ec8936 100755 --- a/src/Illuminate/Database/Seeder.php +++ b/src/Illuminate/Database/Seeder.php @@ -50,7 +50,7 @@ public function call($class, $silent = false, array $parameters = []) $name = get_class($seeder); if ($silent === false && isset($this->command)) { - with(new TwoColumnDetail($this->command->getOutput()))->render( + (new TwoColumnDetail($this->command->getOutput()))->render( $name, 'RUNNING' ); @@ -63,7 +63,7 @@ public function call($class, $silent = false, array $parameters = []) if ($silent === false && isset($this->command)) { $runTime = number_format((microtime(true) - $startTime) * 1000); - with(new TwoColumnDetail($this->command->getOutput()))->render( + (new TwoColumnDetail($this->command->getOutput()))->render( $name, "$runTime ms DONE" ); diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 52342f698e80..cdd7b7d16736 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -1046,12 +1046,12 @@ public function renderForConsole($output, Throwable $e) if (! empty($alternatives = $e->getAlternatives())) { $message .= '. Did you mean one of these?'; - with(new Error($output))->render($message); - with(new BulletList($output))->render($alternatives); + (new Error($output))->render($message); + (new BulletList($output))->render($alternatives); $output->writeln(''); } else { - with(new Error($output))->render($message); + (new Error($output))->render($message); } return; diff --git a/tests/Console/View/ComponentsTest.php b/tests/Console/View/ComponentsTest.php index 31958ba6cb8f..82c789196717 100644 --- a/tests/Console/View/ComponentsTest.php +++ b/tests/Console/View/ComponentsTest.php @@ -21,7 +21,7 @@ public function testAlert() { $output = new BufferedOutput(); - with(new Components\Alert($output))->render('The application is in the [production] environment'); + (new Components\Alert($output))->render('The application is in the [production] environment'); $this->assertStringContainsString( 'THE APPLICATION IS IN THE [PRODUCTION] ENVIRONMENT.', @@ -33,7 +33,7 @@ public function testBulletList() { $output = new BufferedOutput(); - with(new Components\BulletList($output))->render([ + (new Components\BulletList($output))->render([ 'ls -la', 'php artisan inspire', ]); @@ -48,7 +48,7 @@ public function testSuccess() { $output = new BufferedOutput(); - with(new Components\Success($output))->render('The application is in the [production] environment'); + (new Components\Success($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('SUCCESS The application is in the [production] environment.', $output->fetch()); } @@ -57,7 +57,7 @@ public function testError() { $output = new BufferedOutput(); - with(new Components\Error($output))->render('The application is in the [production] environment'); + (new Components\Error($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('ERROR The application is in the [production] environment.', $output->fetch()); } @@ -66,7 +66,7 @@ public function testInfo() { $output = new BufferedOutput(); - with(new Components\Info($output))->render('The application is in the [production] environment'); + (new Components\Info($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('INFO The application is in the [production] environment.', $output->fetch()); } @@ -80,7 +80,7 @@ public function testConfirm() ->once() ->andReturnTrue(); - $result = with(new Components\Confirm($output))->render('Question?'); + $result = (new Components\Confirm($output))->render('Question?'); $this->assertTrue($result); $output->shouldReceive('confirm') @@ -88,7 +88,7 @@ public function testConfirm() ->once() ->andReturnTrue(); - $result = with(new Components\Confirm($output))->render('Question?', true); + $result = (new Components\Confirm($output))->render('Question?', true); $this->assertTrue($result); } @@ -101,7 +101,7 @@ public function testChoice() ->once() ->andReturn('a'); - $result = with(new Components\Choice($output))->render('Question?', ['a', 'b']); + $result = (new Components\Choice($output))->render('Question?', ['a', 'b']); $this->assertSame('a', $result); } @@ -109,17 +109,17 @@ public function testTask() { $output = new BufferedOutput(); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Success->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Success->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('DONE', $result); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Failure->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Failure->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('FAIL', $result); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Skipped->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Skipped->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('SKIPPED', $result); @@ -129,7 +129,7 @@ public function testTwoColumnDetail() { $output = new BufferedOutput(); - with(new Components\TwoColumnDetail($output))->render('First', 'Second'); + (new Components\TwoColumnDetail($output))->render('First', 'Second'); $result = $output->fetch(); $this->assertStringContainsString('First', $result); $this->assertStringContainsString('Second', $result); @@ -139,7 +139,7 @@ public function testWarn() { $output = new BufferedOutput(); - with(new Components\Warn($output))->render('The application is in the [production] environment'); + (new Components\Warn($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('WARN The application is in the [production] environment.', $output->fetch()); } From 144afb30b042358f65e53ef76e1c24d7f840dace Mon Sep 17 00:00:00 2001 From: Seth Phat Date: Wed, 10 Sep 2025 19:06:32 +0700 Subject: [PATCH 027/103] fixed wrong type (#56987) --- src/Illuminate/Database/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 7bd24041d179..4dd99cfc82f9 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -879,7 +879,7 @@ protected function getElapsedTime($start) * Register a callback to be invoked when the connection queries for longer than a given amount of time. * * @param \DateTimeInterface|\Carbon\CarbonInterval|float|int $threshold - * @param (callable(\Illuminate\Database\Connection, class-string<\Illuminate\Database\Events\QueryExecuted>): mixed) $handler + * @param (callable(\Illuminate\Database\Connection, \Illuminate\Database\Events\QueryExecuted): mixed) $handler * @return void */ public function whenQueryingForLongerThan($threshold, $handler) From a178dcfa84e6ebe71ead8ad3f1fe6e5c5a534e7b Mon Sep 17 00:00:00 2001 From: Hocine Saad Date: Wed, 10 Sep 2025 15:35:16 +0100 Subject: [PATCH 028/103] [12.x] Some quick fixes (#56991) * fix: correct typo in docblock for setName method * fix: update return type in getRouteDomain method to string|null --- src/Illuminate/Redis/Connections/Connection.php | 2 +- src/Illuminate/Routing/RouteUrlGenerator.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Redis/Connections/Connection.php b/src/Illuminate/Redis/Connections/Connection.php index 3e5338ee49a7..54fed48e7fe2 100644 --- a/src/Illuminate/Redis/Connections/Connection.php +++ b/src/Illuminate/Redis/Connections/Connection.php @@ -170,7 +170,7 @@ public function getName() } /** - * Set the connections name. + * Set the connection's name. * * @param string $name * @return $this diff --git a/src/Illuminate/Routing/RouteUrlGenerator.php b/src/Illuminate/Routing/RouteUrlGenerator.php index 7798bdc2841f..245c4c01156d 100644 --- a/src/Illuminate/Routing/RouteUrlGenerator.php +++ b/src/Illuminate/Routing/RouteUrlGenerator.php @@ -117,7 +117,7 @@ public function to($route, $parameters = [], $absolute = false) * * @param \Illuminate\Routing\Route $route * @param array $parameters - * @return string + * @return string|null */ protected function getRouteDomain($route, &$parameters) { From 174d14e07f3b1f67d0fece7dc237928d72fc630c Mon Sep 17 00:00:00 2001 From: Iman Parvizi <70389233+realpvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:07:37 +0330 Subject: [PATCH 029/103] tests: Ensure transaction callbacks run in FIFO order (#56973) --- .../DatabaseTransactionsManagerTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Database/DatabaseTransactionsManagerTest.php b/tests/Database/DatabaseTransactionsManagerTest.php index 0cf4f8bdc34a..f1564aa8f3ec 100755 --- a/tests/Database/DatabaseTransactionsManagerTest.php +++ b/tests/Database/DatabaseTransactionsManagerTest.php @@ -109,6 +109,31 @@ public function testCallbacksAreAddedToTheCurrentTransaction() $this->assertCount(1, $manager->getPendingTransactions()[2]->getCallbacks()); } + public function testCallbacksRunInFifoOrder() + { + $manager = (new DatabaseTransactionsManager); + + $order = []; + + $manager->begin('default', 1); + + $manager->addCallback(function () use (&$order) { + $order[] = 1; + }); + + $manager->addCallback(function () use (&$order) { + $order[] = 2; + }); + + $manager->addCallback(function () use (&$order) { + $order[] = 3; + }); + + $manager->commit('default', 1, 0); + + $this->assertSame([1, 2, 3], $order); + } + public function testCommittingTransactionsExecutesCallbacks() { $callbacks = []; From 085a3d8e5d3cf7537ce3e3bef4f85ba41fb248c5 Mon Sep 17 00:00:00 2001 From: Fritz <4413963+fritz-c@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:44:07 -0500 Subject: [PATCH 030/103] pass attributes and parent arguments to Factory Sequence as in ->state() (#56972) --- .../Database/Eloquent/Factories/Sequence.php | 6 ++-- .../Database/DatabaseEloquentFactoryTest.php | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Factories/Sequence.php b/src/Illuminate/Database/Eloquent/Factories/Sequence.php index 11971eced7da..4d350d2ad193 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Sequence.php +++ b/src/Illuminate/Database/Eloquent/Factories/Sequence.php @@ -51,11 +51,13 @@ public function count(): int /** * Get the next value in the sequence. * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent * @return mixed */ - public function __invoke() + public function __invoke($attributes = [], $parent = null) { - return tap(value($this->sequence[$this->index % $this->count], $this), function () { + return tap(value($this->sequence[$this->index % $this->count], $this, $attributes, $parent), function () { $this->index = $this->index + 1; }); } diff --git a/tests/Database/DatabaseEloquentFactoryTest.php b/tests/Database/DatabaseEloquentFactoryTest.php index ac98ceb2fa41..5bfa2f2a6540 100644 --- a/tests/Database/DatabaseEloquentFactoryTest.php +++ b/tests/Database/DatabaseEloquentFactoryTest.php @@ -533,6 +533,39 @@ public function test_counted_sequence() $this->assertSame(3, $value); } + public function test_sequence_with_has_many_relationship() + { + $users = FactoryTestUserFactory::times(2) + ->sequence( + ['name' => 'Abigail Otwell'], + ['name' => 'Taylor Otwell'], + ) + ->has( + FactoryTestPostFactory::times(3) + ->state(['title' => 'Post']) + ->sequence(function ($sequence, $attributes, $user) { + return ['title' => $user->name.' '.$attributes['title'].' '.($sequence->index % 3 + 1)]; + }), + 'posts' + ) + ->create(); + + $this->assertCount(2, FactoryTestUser::all()); + $this->assertCount(6, FactoryTestPost::all()); + $this->assertCount(3, FactoryTestUser::latest()->first()->posts); + $this->assertEquals( + FactoryTestPost::orderBy('title')->pluck('title')->all(), + [ + 'Abigail Otwell Post 1', + 'Abigail Otwell Post 2', + 'Abigail Otwell Post 3', + 'Taylor Otwell Post 1', + 'Taylor Otwell Post 2', + 'Taylor Otwell Post 3', + ] + ); + } + public function test_cross_join_sequences() { $assert = function ($users) { From d9bdd6d970d7b40eed1cb94f102bca37f621c092 Mon Sep 17 00:00:00 2001 From: Justin Seliga Date: Wed, 10 Sep 2025 10:56:34 -0400 Subject: [PATCH 031/103] support `Castable` on `Enum` (#56977) --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index f86538ebdbed..7591252b5148 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1788,6 +1788,10 @@ protected function isEnumCastable($key) return false; } + if (is_subclass_of($castType, Castable::class)) { + return false; + } + return enum_exists($castType); } From c5fbab2516fd76d168db4452f9a22967b16b3b91 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 10 Sep 2025 09:56:47 -0500 Subject: [PATCH 032/103] add trailling commas in multline method signatures (#56992) helps with better diffs used the following regex to find them, not perfect, but it gets you close: ``` [^,] (\ *)\)\ \{ ``` --- src/Illuminate/Container/Attributes/Give.php | 2 +- src/Illuminate/Container/BoundMethod.php | 2 +- src/Illuminate/Database/Events/ModelPruningStarting.php | 2 +- src/Illuminate/Database/Query/Expression.php | 2 +- src/Illuminate/Log/Events/MessageLogged.php | 2 +- src/Illuminate/Queue/Events/WorkerStarting.php | 2 +- src/Illuminate/Queue/Events/WorkerStopping.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Container/Attributes/Give.php b/src/Illuminate/Container/Attributes/Give.php index 41523a84cc8c..f669c58d1abb 100644 --- a/src/Illuminate/Container/Attributes/Give.php +++ b/src/Illuminate/Container/Attributes/Give.php @@ -19,7 +19,7 @@ class Give implements ContextualAttribute */ public function __construct( public string $class, - public array $params = [] + public array $params = [], ) { } diff --git a/src/Illuminate/Container/BoundMethod.php b/src/Illuminate/Container/BoundMethod.php index 32c1da23ef32..9293a818d619 100644 --- a/src/Illuminate/Container/BoundMethod.php +++ b/src/Illuminate/Container/BoundMethod.php @@ -166,7 +166,7 @@ protected static function addDependencyForCallParameter( $container, $parameter, array &$parameters, - &$dependencies + &$dependencies, ) { $pendingDependencies = []; diff --git a/src/Illuminate/Database/Events/ModelPruningStarting.php b/src/Illuminate/Database/Events/ModelPruningStarting.php index a45f912dc283..581d3da8a4bc 100644 --- a/src/Illuminate/Database/Events/ModelPruningStarting.php +++ b/src/Illuminate/Database/Events/ModelPruningStarting.php @@ -10,7 +10,7 @@ class ModelPruningStarting * @param array $models The class names of the models that will be pruned. */ public function __construct( - public $models + public $models, ) { } } diff --git a/src/Illuminate/Database/Query/Expression.php b/src/Illuminate/Database/Query/Expression.php index 1568e1ff9436..839dea833a2c 100755 --- a/src/Illuminate/Database/Query/Expression.php +++ b/src/Illuminate/Database/Query/Expression.php @@ -16,7 +16,7 @@ class Expression implements ExpressionContract * @param TValue $value */ public function __construct( - protected $value + protected $value, ) { } diff --git a/src/Illuminate/Log/Events/MessageLogged.php b/src/Illuminate/Log/Events/MessageLogged.php index 7734578a41cd..f7232700c5e7 100644 --- a/src/Illuminate/Log/Events/MessageLogged.php +++ b/src/Illuminate/Log/Events/MessageLogged.php @@ -14,7 +14,7 @@ class MessageLogged public function __construct( public $level, public $message, - public array $context = [] + public array $context = [], ) { } } diff --git a/src/Illuminate/Queue/Events/WorkerStarting.php b/src/Illuminate/Queue/Events/WorkerStarting.php index 89ada14873c0..dbe172457f40 100644 --- a/src/Illuminate/Queue/Events/WorkerStarting.php +++ b/src/Illuminate/Queue/Events/WorkerStarting.php @@ -14,7 +14,7 @@ class WorkerStarting public function __construct( public $connectionName, public $queue, - public $workerOptions + public $workerOptions, ) { } } diff --git a/src/Illuminate/Queue/Events/WorkerStopping.php b/src/Illuminate/Queue/Events/WorkerStopping.php index ae38a3d2c786..d4cf0ef1fbf0 100644 --- a/src/Illuminate/Queue/Events/WorkerStopping.php +++ b/src/Illuminate/Queue/Events/WorkerStopping.php @@ -12,7 +12,7 @@ class WorkerStopping */ public function __construct( public $status = 0, - public $workerOptions = null + public $workerOptions = null, ) { } } From 78a7a2a5aa66e339328e5bcc4b162e1b1fee14a9 Mon Sep 17 00:00:00 2001 From: Amir Hossein Shokri Date: Wed, 10 Sep 2025 22:35:55 +0330 Subject: [PATCH 033/103] Improve docblocks for nullable parameters (#56995) --- src/Illuminate/Broadcasting/BroadcastEvent.php | 2 +- src/Illuminate/Collections/Collection.php | 2 +- src/Illuminate/Collections/Enumerable.php | 2 +- src/Illuminate/Console/View/Components/Ask.php | 2 +- src/Illuminate/Console/View/Components/AskWithCompletion.php | 2 +- src/Illuminate/Console/View/Components/Choice.php | 2 +- src/Illuminate/Database/Eloquent/Casts/AsCollection.php | 2 +- .../Database/Eloquent/Casts/AsEncryptedCollection.php | 2 +- .../Http/Middleware/AddLinkHeadersForPreloadedAssets.php | 2 +- src/Illuminate/Validation/Concerns/FilterEmailValidation.php | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Broadcasting/BroadcastEvent.php b/src/Illuminate/Broadcasting/BroadcastEvent.php index 7c03641ace53..022a89717e72 100644 --- a/src/Illuminate/Broadcasting/BroadcastEvent.php +++ b/src/Illuminate/Broadcasting/BroadcastEvent.php @@ -196,7 +196,7 @@ public function middleware(): array /** * Handle a job failure. * - * @param \Throwable $e + * @param \Throwable|null $e * @return void */ public function failed(?Throwable $e = null): void diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index b01db682f42b..02303837aeb6 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1386,7 +1386,7 @@ public function splitIn($numberOfGroups) /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index e6c6554b2143..599950420865 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -985,7 +985,7 @@ public function sole($key = null, $operator = null, $value = null); /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue diff --git a/src/Illuminate/Console/View/Components/Ask.php b/src/Illuminate/Console/View/Components/Ask.php index dfd414ad885d..b731408958b0 100644 --- a/src/Illuminate/Console/View/Components/Ask.php +++ b/src/Illuminate/Console/View/Components/Ask.php @@ -10,7 +10,7 @@ class Ask extends Component * Renders the component using the given arguments. * * @param string $question - * @param string $default + * @param string|null $default * @param bool $multiline * @return mixed */ diff --git a/src/Illuminate/Console/View/Components/AskWithCompletion.php b/src/Illuminate/Console/View/Components/AskWithCompletion.php index 103d73071b7a..c28f91236eba 100644 --- a/src/Illuminate/Console/View/Components/AskWithCompletion.php +++ b/src/Illuminate/Console/View/Components/AskWithCompletion.php @@ -11,7 +11,7 @@ class AskWithCompletion extends Component * * @param string $question * @param array|callable $choices - * @param string $default + * @param string|null $default * @return mixed */ public function render($question, $choices, $default = null) diff --git a/src/Illuminate/Console/View/Components/Choice.php b/src/Illuminate/Console/View/Components/Choice.php index ed215527ae8e..1f7818f742fe 100644 --- a/src/Illuminate/Console/View/Components/Choice.php +++ b/src/Illuminate/Console/View/Components/Choice.php @@ -12,7 +12,7 @@ class Choice extends Component * @param string $question * @param array $choices * @param mixed $default - * @param int $attempts + * @param int|null $attempts * @param bool $multiple * @return mixed */ diff --git a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php index e36b13df2184..5f7d0845c8ff 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php @@ -80,7 +80,7 @@ public static function of($map) * Specify the collection type for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|null $map * @return string */ public static function using($class, $map = null) diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index b5912fa20b10..ab122ef3af40 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -79,7 +79,7 @@ public static function of($map) * Specify the collection for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|null $map * @return string */ public static function using($class, $map = null) diff --git a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php index 247c1507c506..b16b3c1bff25 100644 --- a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php +++ b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php @@ -24,7 +24,7 @@ public static function using($limit) * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @param int $limit + * @param int|null $limit * @return \Illuminate\Http\Response */ public function handle($request, $next, $limit = null) diff --git a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php index 50acbcf1311a..6cc4b8730982 100644 --- a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php +++ b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php @@ -18,7 +18,7 @@ class FilterEmailValidation implements EmailValidation /** * Create a new validation instance. * - * @param int $flags + * @param int|null $flags */ public function __construct($flags = null) { From cf159ac912fa2de7303c8f4671f23d98b5869cde Mon Sep 17 00:00:00 2001 From: Amir Hossein Shokri Date: Wed, 10 Sep 2025 22:44:17 +0330 Subject: [PATCH 034/103] Improve docblocks for nullable parameters (#56996) --- src/Illuminate/Collections/LazyCollection.php | 4 ++-- .../Database/Eloquent/Relations/HasOneOrManyThrough.php | 2 +- src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php | 2 +- .../Foundation/Testing/Concerns/InteractsWithDatabase.php | 2 +- src/Illuminate/Http/Concerns/InteractsWithInput.php | 2 +- src/Illuminate/Http/Exceptions/HttpResponseException.php | 2 +- src/Illuminate/Queue/InteractsWithQueue.php | 2 +- src/Illuminate/Support/Facades/Http.php | 2 +- src/Illuminate/Support/Fluent.php | 2 +- src/Illuminate/Support/InteractsWithTime.php | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index bb82e2125e1b..95b61720afc4 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1344,7 +1344,7 @@ public function split($numberOfGroups) /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1369,7 +1369,7 @@ public function sole($key = null, $operator = null, $value = null) /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php index 0c3029f1ab18..4be008110d82 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php @@ -468,7 +468,7 @@ public function get($columns = ['*']) * @param int|null $perPage * @param array $columns * @param string $pageName - * @param int $page + * @param int|null $page * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) diff --git a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php index c7f66d19bb96..2501bebe68ab 100644 --- a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php @@ -46,7 +46,7 @@ public function constrained($table = null, $column = null, $indexName = null) * Specify which column this foreign ID references on another table. * * @param string $column - * @param string $indexName + * @param string|null $indexName * @return \Illuminate\Database\Schema\ForeignKeyDefinition */ public function references($column, $indexName = null) diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php index c23ce0de4e37..db89e067c160 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php @@ -285,7 +285,7 @@ public function castAsJson($value, $connection = null) * Get the database connection. * * @param string|null $connection - * @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model>|string $table + * @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model>|string|null $table * @return \Illuminate\Database\Connection */ protected function getConnection($connection = null, $table = null) diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index a4e9173746a7..e4e1f647226c 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -250,7 +250,7 @@ public function file($key = null, $default = null) /** * Retrieve data from the instance. * - * @param string $key + * @param string|null $key * @param mixed $default * @return mixed */ diff --git a/src/Illuminate/Http/Exceptions/HttpResponseException.php b/src/Illuminate/Http/Exceptions/HttpResponseException.php index eeafe3205d60..ffa6d795b4f5 100644 --- a/src/Illuminate/Http/Exceptions/HttpResponseException.php +++ b/src/Illuminate/Http/Exceptions/HttpResponseException.php @@ -19,7 +19,7 @@ class HttpResponseException extends RuntimeException * Create a new HTTP response exception instance. * * @param \Symfony\Component\HttpFoundation\Response $response - * @param \Throwable $previous + * @param \Throwable|null $previous */ public function __construct(Response $response, ?Throwable $previous = null) { diff --git a/src/Illuminate/Queue/InteractsWithQueue.php b/src/Illuminate/Queue/InteractsWithQueue.php index 850c7c1c54bc..4a9728ae0824 100644 --- a/src/Illuminate/Queue/InteractsWithQueue.php +++ b/src/Illuminate/Queue/InteractsWithQueue.php @@ -211,7 +211,7 @@ public function assertNotFailed() /** * Assert that the job was released back onto the queue. * - * @param \DateTimeInterface|\DateInterval|int $delay + * @param \DateTimeInterface|\DateInterval|int|null $delay * @return $this */ public function assertReleased($delay = null) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index f4c423c74f60..6ec81c4a19c7 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -117,7 +117,7 @@ protected static function getFacadeAccessor() /** * Register a stub callable that will intercept requests and be able to return stub responses. * - * @param \Closure|array $callback + * @param \Closure|array|null $callback * @return \Illuminate\Http\Client\Factory */ public static function fake($callback = null) diff --git a/src/Illuminate/Support/Fluent.php b/src/Illuminate/Support/Fluent.php index e504aa78d036..a15085727086 100755 --- a/src/Illuminate/Support/Fluent.php +++ b/src/Illuminate/Support/Fluent.php @@ -153,7 +153,7 @@ public function all($keys = null) /** * Get data from the fluent instance. * - * @param string $key + * @param string|null $key * @param mixed $default * @return mixed */ diff --git a/src/Illuminate/Support/InteractsWithTime.php b/src/Illuminate/Support/InteractsWithTime.php index 6b78be16c9b0..43a747cb2e4e 100644 --- a/src/Illuminate/Support/InteractsWithTime.php +++ b/src/Illuminate/Support/InteractsWithTime.php @@ -67,7 +67,7 @@ protected function currentTime() * Given a start time, format the total run time for human readability. * * @param float $startTime - * @param float $endTime + * @param float|null $endTime * @return string */ protected function runTimeForHumans($startTime, $endTime = null) From c5de8e9580fd54b06fe5c16585906e7c544dc3c4 Mon Sep 17 00:00:00 2001 From: Amir Hossein Shokri Date: Wed, 10 Sep 2025 22:45:00 +0330 Subject: [PATCH 035/103] Improve docblocks for nullable parameters (#56997) --- src/Illuminate/Cache/RedisTagSet.php | 2 +- .../Database/Eloquent/SupportsPartialRelations.php | 2 +- .../Database/Eloquent/Concerns/QueriesRelationships.php | 2 +- src/Illuminate/Database/Migrations/MigrationCreator.php | 2 +- src/Illuminate/Foundation/Configuration/Middleware.php | 4 ++-- src/Illuminate/Support/ServiceProvider.php | 2 +- src/Illuminate/Support/Stringable.php | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php index 88cb4a753ad3..82369ff4ed00 100644 --- a/src/Illuminate/Cache/RedisTagSet.php +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -13,7 +13,7 @@ class RedisTagSet extends TagSet * * @param string $key * @param int|null $ttl - * @param string $updateWhen + * @param string|null $updateWhen * @return void */ public function addEntry(string $key, ?int $ttl = null, $updateWhen = null) diff --git a/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php b/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php index c82125aa0c10..19cb15a825eb 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php +++ b/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php @@ -9,7 +9,7 @@ interface SupportsPartialRelations * * @param string|null $column * @param string|\Closure|null $aggregate - * @param string $relation + * @param string|null $relation * @return $this */ public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null); diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 805cca3b21de..d51a862f17d2 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -837,7 +837,7 @@ public function orWhereAttachedTo($related, $relationshipName = null) * * @param mixed $relations * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $function + * @param string|null $function * @return $this */ public function withAggregate($relations, $column, $function = null) diff --git a/src/Illuminate/Database/Migrations/MigrationCreator.php b/src/Illuminate/Database/Migrations/MigrationCreator.php index ba98eb658148..8f6b5bd45a48 100755 --- a/src/Illuminate/Database/Migrations/MigrationCreator.php +++ b/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -82,7 +82,7 @@ public function create($name, $path, $table = null, $create = false) * Ensure that a migration with the given name doesn't already exist. * * @param string $name - * @param string $migrationPath + * @param string|null $migrationPath * @return void * * @throws \InvalidArgumentException diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php index 20b50499aedf..a458bf88ef47 100644 --- a/src/Illuminate/Foundation/Configuration/Middleware.php +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -555,8 +555,8 @@ public function redirectUsersTo(callable|string $redirect) /** * Configure where users are redirected by the authentication and guest middleware. * - * @param callable|string $guests - * @param callable|string $users + * @param callable|string|null $guests + * @param callable|string|null $users * @return $this */ public function redirectTo(callable|string|null $guests = null, callable|string|null $users = null) diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index 85b0ee116791..ce2b526d2be1 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -547,7 +547,7 @@ public static function defaultProviders() * Add the given provider to the application's provider bootstrap file. * * @param string $provider - * @param string $path + * @param string|null $path * @return bool */ public static function addProviderToBootstrapFile(string $provider, ?string $path = null) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 16d524eb93f6..a8b116b80e62 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1045,7 +1045,7 @@ public function take(int $limit) /** * Trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function trim($characters = null) @@ -1056,7 +1056,7 @@ public function trim($characters = null) /** * Left trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function ltrim($characters = null) @@ -1067,7 +1067,7 @@ public function ltrim($characters = null) /** * Right trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function rtrim($characters = null) From 13d8caeea891b8783520995252efa3349722bcb3 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 10 Sep 2025 14:30:01 -0500 Subject: [PATCH 036/103] Revert "Config: Move some items into pragmas (#56980)" (#57003) This reverts commit 52a1f6e07f88f94ff9019c4836cc794d95257afa. --- config/database.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/database.php b/config/database.php index 8ac877358719..5127e8ca48ac 100644 --- a/config/database.php +++ b/config/database.php @@ -38,12 +38,11 @@ 'prefix' => '', 'prefix_indexes' => null, 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), - 'pragmas' => [ - 'busy_timeout' => null, - 'journal_mode' => null, - 'synchronous' => null, - 'transaction_mode' => 'DEFERRED', - ], + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + 'pragmas' => [], ], 'mysql' => [ From 38f7d03c8d1db0a3972301f6094dbc61f3134348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Lopes?= Date: Wed, 10 Sep 2025 16:32:49 -0300 Subject: [PATCH 037/103] [12.x]: Cache Session Driver (#56887) * feat(session): adding new method to remeber value for a given time * wip * feat(cache): adding session driver skeleton * feat(cache): finish cache session driver * test(cache): adding tests for cache session driver * wip * wip * wip * wip * refactor(cache): some adjusts * wip * formatting * refactor(cache): some adjusts in cache session driver * Update cache.php * Update CacheManager.php * formatting --------- Co-authored-by: Taylor Otwell --- config/cache.php | 5 + src/Illuminate/Cache/CacheManager.php | 143 ++++++++++------ src/Illuminate/Cache/SessionStore.php | 206 +++++++++++++++++++++++ src/Illuminate/Session/Store.php | 11 ++ tests/Cache/CacheSessionStoreTest.php | 229 ++++++++++++++++++++++++++ 5 files changed, 540 insertions(+), 54 deletions(-) create mode 100644 src/Illuminate/Cache/SessionStore.php create mode 100755 tests/Cache/CacheSessionStoreTest.php diff --git a/config/cache.php b/config/cache.php index c2d927d79978..3bb312f013ae 100644 --- a/config/cache.php +++ b/config/cache.php @@ -38,6 +38,11 @@ 'serialize' => false, ], + 'session' => [ + 'driver' => 'session', + 'key' => env('SESSION_CACHE_KEY', '_cache'), + ], + 'database' => [ 'driver' => 'database', 'connection' => env('DB_CACHE_CONNECTION'), diff --git a/src/Illuminate/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index 0a0c2de5e171..2e793f6083d2 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -169,6 +169,82 @@ protected function createArrayDriver(array $config) return $this->repository(new ArrayStore($config['serialize'] ?? false), $config); } + /** + * Create an instance of the database cache driver. + * + * @param array $config + * @return \Illuminate\Cache\Repository + */ + protected function createDatabaseDriver(array $config) + { + $connection = $this->app['db']->connection($config['connection'] ?? null); + + $store = new DatabaseStore( + $connection, + $config['table'], + $this->getPrefix($config), + $config['lock_table'] ?? 'cache_locks', + $config['lock_lottery'] ?? [2, 100], + $config['lock_timeout'] ?? 86400, + ); + + return $this->repository( + $store->setLockConnection( + $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) + ), + $config + ); + } + + /** + * Create an instance of the DynamoDB cache driver. + * + * @param array $config + * @return \Illuminate\Cache\Repository + */ + protected function createDynamodbDriver(array $config) + { + $client = $this->newDynamodbClient($config); + + return $this->repository( + new DynamoDbStore( + $client, + $config['table'], + $config['attributes']['key'] ?? 'key', + $config['attributes']['value'] ?? 'value', + $config['attributes']['expiration'] ?? 'expires_at', + $this->getPrefix($config) + ), + $config + ); + } + + /** + * Create new DynamoDb Client instance. + * + * @return \Aws\DynamoDb\DynamoDbClient + */ + protected function newDynamodbClient(array $config) + { + $dynamoConfig = [ + 'region' => $config['region'], + 'version' => 'latest', + 'endpoint' => $config['endpoint'] ?? null, + ]; + + if (! empty($config['key']) && ! empty($config['secret'])) { + $dynamoConfig['credentials'] = Arr::only( + $config, ['key', 'secret'] + ); + + if (! empty($config['token'])) { + $dynamoConfig['credentials']['token'] = $config['token']; + } + } + + return new DynamoDbClient($dynamoConfig); + } + /** * Create an instance of the file cache driver. * @@ -235,79 +311,38 @@ protected function createRedisDriver(array $config) } /** - * Create an instance of the database cache driver. + * Create an instance of the session cache driver. * * @param array $config * @return \Illuminate\Cache\Repository */ - protected function createDatabaseDriver(array $config) + protected function createSessionDriver(array $config) { - $connection = $this->app['db']->connection($config['connection'] ?? null); - - $store = new DatabaseStore( - $connection, - $config['table'], - $this->getPrefix($config), - $config['lock_table'] ?? 'cache_locks', - $config['lock_lottery'] ?? [2, 100], - $config['lock_timeout'] ?? 86400, - ); - return $this->repository( - $store->setLockConnection( - $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) + new SessionStore( + $this->getSession(), + $config['key'] ?? '_cache', ), $config ); } /** - * Create an instance of the DynamoDB cache driver. + * Get the session store implementation. * - * @param array $config - * @return \Illuminate\Cache\Repository - */ - protected function createDynamodbDriver(array $config) - { - $client = $this->newDynamodbClient($config); - - return $this->repository( - new DynamoDbStore( - $client, - $config['table'], - $config['attributes']['key'] ?? 'key', - $config['attributes']['value'] ?? 'value', - $config['attributes']['expiration'] ?? 'expires_at', - $this->getPrefix($config) - ), - $config - ); - } - - /** - * Create new DynamoDb Client instance. + * @return \Illuminate\Contracts\Session\Session * - * @return \Aws\DynamoDb\DynamoDbClient + * @throws \InvalidArgumentException */ - protected function newDynamodbClient(array $config) + protected function getSession() { - $dynamoConfig = [ - 'region' => $config['region'], - 'version' => 'latest', - 'endpoint' => $config['endpoint'] ?? null, - ]; - - if (! empty($config['key']) && ! empty($config['secret'])) { - $dynamoConfig['credentials'] = Arr::only( - $config, ['key', 'secret'] - ); + $session = $this->app['session'] ?? null; - if (! empty($config['token'])) { - $dynamoConfig['credentials']['token'] = $config['token']; - } + if (! $session) { + throw new InvalidArgumentException('Session store requires session manager to be available in container.'); } - return new DynamoDbClient($dynamoConfig); + return $session; } /** diff --git a/src/Illuminate/Cache/SessionStore.php b/src/Illuminate/Cache/SessionStore.php new file mode 100644 index 000000000000..0ed5a4da0609 --- /dev/null +++ b/src/Illuminate/Cache/SessionStore.php @@ -0,0 +1,206 @@ +key = $key; + $this->session = $session; + } + + /** + * Get all of the cached values and their expiration times. + * + * @return array + */ + public function all() + { + return $this->session->get($this->key, []); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + if (! $this->session->exists($this->itemKey($key))) { + return; + } + + $item = $this->session->get($this->itemKey($key)); + + $expiresAt = $item['expiresAt'] ?? 0; + + if ($this->isExpired($expiresAt)) { + $this->forget($key); + + return; + } + + return $item['value']; + } + + /** + * Determine if the given expiration time is expired. + * + * @param int|float $expiresAt + * @return bool + */ + protected function isExpired($expiresAt) + { + return $expiresAt !== 0 && (Carbon::now()->getPreciseTimestamp(3) / 1000) >= $expiresAt; + } + + /** + * Store an item in the cache for a given number of seconds. + * + * @param string $key + * @param mixed $value + * @param int $seconds + * @return bool + */ + public function put($key, $value, $seconds) + { + $this->session->put($this->itemKey($key), [ + 'value' => $value, + 'expiresAt' => $this->toTimestamp($seconds), + ]); + + return true; + } + + /** + * Get the UNIX timestamp, with milliseconds, for the given number of seconds in the future. + * + * @param int $seconds + * @return float + */ + protected function toTimestamp($seconds) + { + return $seconds > 0 ? (Carbon::now()->getPreciseTimestamp(3) / 1000) + $seconds : 0; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + if (! is_null($existing = $this->get($key))) { + return tap(((int) $existing) + $value, function ($incremented) use ($key) { + $this->session->put($this->itemKey("{$key}.value"), $incremented); + }); + } + + $this->forever($key, $value); + + return $value; + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return $this->increment($key, $value * -1); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return bool + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + if ($this->session->exists($this->itemKey($key))) { + $this->session->forget($this->itemKey($key)); + + return true; + } + + return false; + } + + /** + * Remove all items from the cache. + * + * @return bool + */ + public function flush() + { + $this->session->put($this->key, []); + + return true; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function itemKey($key) + { + return "{$this->key}.{$key}"; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } +} diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index 6e76072fc6e6..e9f98383ec2c 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Session\Session; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Date; use Illuminate\Support\MessageBag; use Illuminate\Support\Str; @@ -539,6 +540,16 @@ public function flashInput(array $value) $this->flash('_old_input', $value); } + /** + * Get the session cache instance. + * + * @return \Illuminate\Contracts\Cache\Repository + */ + public function cache() + { + return Cache::store('session'); + } + /** * Remove an item from the session, returning its value. * diff --git a/tests/Cache/CacheSessionStoreTest.php b/tests/Cache/CacheSessionStoreTest.php new file mode 100755 index 000000000000..08ac6d4cdd7a --- /dev/null +++ b/tests/Cache/CacheSessionStoreTest.php @@ -0,0 +1,229 @@ +put('foo', 'bar', 10); + $this->assertTrue($result); + $this->assertSame('bar', $store->get('foo')); + } + + public function testCacheTtl(): void + { + $store = new SessionStore(self::getSession()); + + Carbon::setTestNow('2000-01-01 00:00:00.500'); // 500 milliseconds past + $store->put('hello', 'world', 1); + + Carbon::setTestNow('2000-01-01 00:00:01.499'); // progress 0.999 seconds + $this->assertSame('world', $store->get('hello')); + + Carbon::setTestNow('2000-01-01 00:00:01.500'); // progress 0.001 seconds. 1 second since putting into cache. + $this->assertNull($store->get('hello')); + } + + public function testMultipleItemsCanBeSetAndRetrieved() + { + $store = new SessionStore(self::getSession()); + $result = $store->put('foo', 'bar', 10); + $resultMany = $store->putMany([ + 'fizz' => 'buz', + 'quz' => 'baz', + ], 10); + $this->assertTrue($result); + $this->assertTrue($resultMany); + $this->assertEquals([ + 'foo' => 'bar', + 'fizz' => 'buz', + 'quz' => 'baz', + 'norf' => null, + ], $store->many(['foo', 'fizz', 'quz', 'norf'])); + } + + public function testItemsCanExpire() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + + $store->put('foo', 'bar', 10); + Carbon::setTestNow(Carbon::now()->addSeconds(10)->addSecond()); + $result = $store->get('foo'); + + $this->assertNull($result); + } + + public function testStoreItemForeverProperlyStoresInArray() + { + $mock = $this->getMockBuilder(SessionStore::class) + ->setConstructorArgs([self::getSession()]) + ->onlyMethods(['put']) + ->getMock(); + $mock->expects($this->once()) + ->method('put')->with($this->equalTo('foo'), $this->equalTo('bar'), $this->equalTo(0)) + ->willReturn(true); + $result = $mock->forever('foo', 'bar'); + $this->assertTrue($result); + } + + public function testValuesCanBeIncremented() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 1, 10); + $result = $store->increment('foo'); + $this->assertEquals(2, $result); + $this->assertEquals(2, $store->get('foo')); + + $result = $store->increment('foo', 2); + $this->assertEquals(4, $result); + $this->assertEquals(4, $store->get('foo')); + } + + public function testValuesGetCastedByIncrementOrDecrement() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', '1', 10); + $result = $store->increment('foo'); + $this->assertEquals(2, $result); + $this->assertEquals(2, $store->get('foo')); + + $store->put('bar', '1', 10); + $result = $store->decrement('bar'); + $this->assertEquals(0, $result); + $this->assertEquals(0, $store->get('bar')); + } + + public function testIncrementNonNumericValues() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'I am string', 10); + $result = $store->increment('foo'); + $this->assertEquals(1, $result); + $this->assertEquals(1, $store->get('foo')); + } + + public function testNonExistingKeysCanBeIncremented() + { + $store = new SessionStore(self::getSession()); + $result = $store->increment('foo'); + $this->assertEquals(1, $result); + $this->assertEquals(1, $store->get('foo')); + + // Will be there forever + Carbon::setTestNow(Carbon::now()->addYears(10)); + $this->assertEquals(1, $store->get('foo')); + } + + public function testExpiredKeysAreIncrementedLikeNonExistingKeys() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + + $store->put('foo', 999, 10); + Carbon::setTestNow(Carbon::now()->addSeconds(10)->addSecond()); + $result = $store->increment('foo'); + + $this->assertEquals(1, $result); + } + + public function testValuesCanBeDecremented() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 1, 10); + $result = $store->decrement('foo'); + $this->assertEquals(0, $result); + $this->assertEquals(0, $store->get('foo')); + + $result = $store->decrement('foo', 2); + $this->assertEquals(-2, $result); + $this->assertEquals(-2, $store->get('foo')); + } + + public function testItemsCanBeRemoved() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + $this->assertTrue($store->forget('foo')); + $this->assertNull($store->get('foo')); + $this->assertFalse($store->forget('foo')); + } + + public function testItemsCanBeFlushed() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + $store->put('baz', 'boom', 10); + $result = $store->flush(); + $this->assertTrue($result); + $this->assertNull($store->get('foo')); + $this->assertNull($store->get('baz')); + } + + public function testCacheKey() + { + $store = new SessionStore(self::getSession()); + $this->assertEmpty($store->getPrefix()); + } + + public function testItemKey() + { + $store = new SessionStore(self::getSession(), 'custom_prefix'); + $this->assertEquals('custom_prefix.foo', $store->itemKey('foo')); + } + + public function testValuesAreStoredByReference() + { + $store = new SessionStore(self::getSession()); + $object = new stdClass; + $object->foo = true; + + $store->put('object', $object, 10); + $object->bar = true; + + $retrievedObject = $store->get('object'); + + $this->assertTrue($retrievedObject->foo); + $this->assertTrue($retrievedObject->bar); + } + + public function testCanGetAll() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + + $this->assertEquals([ + 'foo' => ['value' => 'bar', 'expiresAt' => Carbon::now()->addSeconds(10)->getPreciseTimestamp(3) / 1000], + ], $store->all()); + } + + protected static function getSession() + { + return new Store( + name: 'name', + serialization: 'php', + handler: new ArraySessionHandler(10), + id: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + ); + } +} From 59fd4594395c17b94cca38ff472b2c15f2f5d29f Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 10 Sep 2025 19:33:14 +0000 Subject: [PATCH 038/103] Update facade docblocks --- src/Illuminate/Support/Facades/Session.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Session.php b/src/Illuminate/Support/Facades/Session.php index 0a124624c249..8bc63adfaf78 100755 --- a/src/Illuminate/Support/Facades/Session.php +++ b/src/Illuminate/Support/Facades/Session.php @@ -41,6 +41,7 @@ * @method static void reflash() * @method static void keep(mixed $keys = null) * @method static void flashInput(array $value) + * @method static \Illuminate\Contracts\Cache\Repository cache() * @method static mixed remove(string $key) * @method static void forget(string|array $keys) * @method static void flush() From 6eaaa84742a4a0581f2e0dc9df751875df1dd358 Mon Sep 17 00:00:00 2001 From: Luca Patera Date: Wed, 10 Sep 2025 21:48:05 +0200 Subject: [PATCH 039/103] [12.x] Add support for #[UseResource(...)] and #[UseResourceCollection(...)] attributes on models (#56966) * test: add fixture resources for Eloquent resource collection tests * feat: add UseResource and UseResourceCollection attributes for model resource transformation * refactor: update parameter type hints for resource and resource collection attributes * formatting --------- Co-authored-by: Taylor Otwell --- .../Traits/TransformsToResourceCollection.php | 53 +++++++++++++++++++ .../Eloquent/Attributes/UseResource.php | 18 +++++++ .../Attributes/UseResourceCollection.php | 18 +++++++ .../Concerns/TransformsToResource.php | 27 ++++++++++ ...DatabaseEloquentResourceCollectionTest.php | 32 +++++++++-- .../DatabaseEloquentResourceModelTest.php | 16 ++++-- ...tResourceModelWithUseResourceAttribute.php | 13 +++++ ...odelWithUseResourceCollectionAttribute.php | 13 +++++ ...EloquentResourceCollectionTestResource.php | 10 ++++ .../EloquentResourceTestJsonResource.php | 10 ++++ ...uentResourceTestJsonResourceCollection.php | 10 ++++ 11 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/Attributes/UseResource.php create mode 100644 src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php create mode 100644 tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php create mode 100644 tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceCollectionAttribute.php create mode 100644 tests/Database/Fixtures/Resources/EloquentResourceCollectionTestResource.php create mode 100644 tests/Database/Fixtures/Resources/EloquentResourceTestJsonResource.php create mode 100644 tests/Database/Fixtures/Resources/EloquentResourceTestJsonResourceCollection.php diff --git a/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php index 22143b356c48..86aaebd4b11d 100644 --- a/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php +++ b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php @@ -2,9 +2,12 @@ namespace Illuminate\Support\Traits; +use Illuminate\Database\Eloquent\Attributes\UseResource; +use Illuminate\Database\Eloquent\Attributes\UseResourceCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Resources\Json\ResourceCollection; use LogicException; +use ReflectionClass; trait TransformsToResourceCollection { @@ -47,6 +50,18 @@ protected function guessResourceCollection(): ResourceCollection throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className)); + $useResourceCollection = $this->resolveResourceCollectionFromAttribute($className); + + if ($useResourceCollection !== null && class_exists($useResourceCollection)) { + return new $useResourceCollection($this); + } + + $useResource = $this->resolveResourceFromAttribute($className); + + if ($useResource !== null && class_exists($useResource)) { + return $useResource::collection($this); + } + $resourceClasses = $className::guessResourceName(); foreach ($resourceClasses as $resourceClass) { @@ -65,4 +80,42 @@ protected function guessResourceCollection(): ResourceCollection throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className)); } + + /** + * Get the resource class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class + * @return class-string<*>|null + */ + protected function resolveResourceFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResource::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } + + /** + * Get the resource collection class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\ResourceCollection> $class + * @return class-string<*>|null + */ + protected function resolveResourceCollectionFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResourceCollection::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } } diff --git a/src/Illuminate/Database/Eloquent/Attributes/UseResource.php b/src/Illuminate/Database/Eloquent/Attributes/UseResource.php new file mode 100644 index 000000000000..a1cbc48f3a90 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/UseResource.php @@ -0,0 +1,18 @@ + $class + */ + public function __construct(public string $class) + { + } +} diff --git a/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php b/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php new file mode 100644 index 000000000000..c17e1f1768dd --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php @@ -0,0 +1,18 @@ + $class + */ + public function __construct(public string $class) + { + } +} diff --git a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php index 379db813e5d7..35e3f98cf9af 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php +++ b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php @@ -2,9 +2,11 @@ namespace Illuminate\Database\Eloquent\Concerns; +use Illuminate\Database\Eloquent\Attributes\UseResource; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Str; use LogicException; +use ReflectionClass; trait TransformsToResource { @@ -30,6 +32,12 @@ public function toResource(?string $resourceClass = null): JsonResource */ protected function guessResource(): JsonResource { + $resourceClass = $this->resolveResourceFromAttribute(static::class); + + if ($resourceClass !== null && class_exists($resourceClass)) { + return $resourceClass::make($this); + } + foreach (static::guessResourceName() as $resourceClass) { if (is_string($resourceClass) && class_exists($resourceClass)) { return $resourceClass::make($this); @@ -67,4 +75,23 @@ class_basename($modelClass) return [$potentialResource.'Resource', $potentialResource]; } + + /** + * Get the resource class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class + * @return class-string<*>|null + */ + protected function resolveResourceFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResource::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } } diff --git a/tests/Database/DatabaseEloquentResourceCollectionTest.php b/tests/Database/DatabaseEloquentResourceCollectionTest.php index e2a1232c3694..b51c93ed05a4 100644 --- a/tests/Database/DatabaseEloquentResourceCollectionTest.php +++ b/tests/Database/DatabaseEloquentResourceCollectionTest.php @@ -3,8 +3,14 @@ namespace Illuminate\Tests\Database; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceCollectionTestModel; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceCollectionAttribute; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceCollectionTestResource; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection; use PHPUnit\Framework\TestCase; class DatabaseEloquentResourceCollectionTest extends TestCase @@ -43,9 +49,27 @@ class_alias(EloquentResourceCollectionTestResource::class, 'Illuminate\Tests\Dat $this->assertInstanceOf(JsonResource::class, $resource); } -} -class EloquentResourceCollectionTestResource extends JsonResource -{ - // + public function testItCanTransformToResourceViaUseResourceAttribute() + { + $collection = new Collection([ + new EloquentResourceTestResourceModelWithUseResourceCollectionAttribute(), + ]); + + $resource = $collection->toResourceCollection(); + + $this->assertInstanceOf(EloquentResourceTestJsonResourceCollection::class, $resource); + } + + public function testItCanTransformToResourceViaUseResourceCollectionAttribute() + { + $collection = new Collection([ + new EloquentResourceTestResourceModelWithUseResourceAttribute(), + ]); + + $resource = $collection->toResourceCollection(); + + $this->assertInstanceOf(AnonymousResourceCollection::class, $resource); + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource[0]); + } } diff --git a/tests/Database/DatabaseEloquentResourceModelTest.php b/tests/Database/DatabaseEloquentResourceModelTest.php index 0be4eb3ad83d..cc2a9f6c3813 100644 --- a/tests/Database/DatabaseEloquentResourceModelTest.php +++ b/tests/Database/DatabaseEloquentResourceModelTest.php @@ -2,9 +2,10 @@ namespace Illuminate\Tests\Database; -use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModel; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithGuessableResource; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource; use PHPUnit\Framework\TestCase; class DatabaseEloquentResourceModelTest extends TestCase @@ -59,9 +60,14 @@ public function testItCanGuessResourceName() 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModel', ], $model::guessResourceName()); } -} -class EloquentResourceTestJsonResource extends JsonResource -{ - // + public function testItCanTransformToResourceViaUseResourceAttribute() + { + $model = new EloquentResourceTestResourceModelWithUseResourceAttribute(); + + $resource = $model->toResource(); + + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource); + $this->assertSame($model, $resource->resource); + } } diff --git a/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php b/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php new file mode 100644 index 000000000000..12ff9d1ea528 --- /dev/null +++ b/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php @@ -0,0 +1,13 @@ + Date: Thu, 11 Sep 2025 14:41:47 +0800 Subject: [PATCH 040/103] [12.x] Test Improvements (#57010) Signed-off-by: Mior Muhammad Zaki --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5c2f45f43324..222d2319bd34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: ports: - 11211:11211 mysql: - image: mysql:5.7 + image: mysql:8 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: forge From 525855dc1d9c8bfd787d12a2acdb928e802c8b70 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 12 Sep 2025 08:44:15 +0800 Subject: [PATCH 041/103] [12.x] Test Improvements (#57031) * [12.x] CI Improvements Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .github/workflows/tests.yml | 18 ++++++++++++++++++ tests/Support/SupportStrTest.php | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 222d2319bd34..c2e6b4f8b98a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,6 +72,15 @@ jobs: - name: Set Framework version run: composer config version "13.x-dev" + - name: Set Minimum dependencies for `prefer-lowest` + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require opis/string:2.0.1 --no-interaction --no-update + shell: bash + if: matrix.stability == 'prefer-lowest' + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -136,6 +145,15 @@ jobs: - name: Set Framework version run: composer config version "13.x-dev" + - name: Set Minimum dependencies for `prefer-lowest` + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require opis/string:2.0.1 --no-interaction --no-update + shell: bash + if: matrix.stability == 'prefer-lowest' + - name: Install dependencies uses: nick-fields/retry@v3 with: diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 2049190e94e7..fd98244285bf 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -12,6 +12,13 @@ class SupportStrTest extends TestCase { + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + Str::createRandomStringsNormally(); + } + public function testStringCanBeLimitedByWords(): void { $this->assertSame('Taylor...', Str::words('Taylor Otwell', 1)); From 92b4da040f906d4a479b14853987c51cb51629fa Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 12 Sep 2025 16:35:11 +0200 Subject: [PATCH 042/103] [12.x] Infinite method chaining in contextual binding builder (#57026) Co-authored-by: Sergey Danilchenko --- .../Container/ContextualBindingBuilder.php | 12 +++++++----- .../Contracts/Container/ContextualBindingBuilder.php | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Container/ContextualBindingBuilder.php b/src/Illuminate/Container/ContextualBindingBuilder.php index 0f3163f9403a..aeed0cfd1c16 100644 --- a/src/Illuminate/Container/ContextualBindingBuilder.php +++ b/src/Illuminate/Container/ContextualBindingBuilder.php @@ -57,24 +57,26 @@ public function needs($abstract) * Define the implementation for the contextual binding. * * @param \Closure|string|array $implementation - * @return void + * @return $this */ public function give($implementation) { foreach (Util::arrayWrap($this->concrete) as $concrete) { $this->container->addContextualBinding($concrete, $this->needs, $implementation); } + + return $this; } /** * Define tagged services to be used as the implementation for the contextual binding. * * @param string $tag - * @return void + * @return $this */ public function giveTagged($tag) { - $this->give(function ($container) use ($tag) { + return $this->give(function ($container) use ($tag) { $taggedServices = $container->tagged($tag); return is_array($taggedServices) ? $taggedServices : iterator_to_array($taggedServices); @@ -86,10 +88,10 @@ public function giveTagged($tag) * * @param string $key * @param mixed $default - * @return void + * @return $this */ public function giveConfig($key, $default = null) { - $this->give(fn ($container) => $container->get('config')->get($key, $default)); + return $this->give(fn ($container) => $container->get('config')->get($key, $default)); } } diff --git a/src/Illuminate/Contracts/Container/ContextualBindingBuilder.php b/src/Illuminate/Contracts/Container/ContextualBindingBuilder.php index 1d84ef8c709d..38399acc72a7 100644 --- a/src/Illuminate/Contracts/Container/ContextualBindingBuilder.php +++ b/src/Illuminate/Contracts/Container/ContextualBindingBuilder.php @@ -16,7 +16,7 @@ public function needs($abstract); * Define the implementation for the contextual binding. * * @param \Closure|string|array $implementation - * @return void + * @return $this */ public function give($implementation); @@ -24,7 +24,7 @@ public function give($implementation); * Define tagged services to be used as the implementation for the contextual binding. * * @param string $tag - * @return void + * @return $this */ public function giveTagged($tag); @@ -33,7 +33,7 @@ public function giveTagged($tag); * * @param string $key * @param mixed $default - * @return void + * @return $this */ public function giveConfig($key, $default = null); } From bc578364a64a47a2dbee5fa1b95cd8e2919a363b Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 12 Sep 2025 16:35:26 +0200 Subject: [PATCH 043/103] [12.x] Improved manager typehints (#57024) Co-authored-by: Sergey Danilchenko --- src/Illuminate/Session/SessionManager.php | 2 +- src/Illuminate/Support/Manager.php | 2 +- tests/Integration/Support/Fixtures/NullableManager.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Session/SessionManager.php b/src/Illuminate/Session/SessionManager.php index 578fc2107ab2..0b176ef6d01a 100755 --- a/src/Illuminate/Session/SessionManager.php +++ b/src/Illuminate/Session/SessionManager.php @@ -269,7 +269,7 @@ public function getSessionConfig() /** * Get the default session driver name. * - * @return string + * @return string|null */ public function getDefaultDriver() { diff --git a/src/Illuminate/Support/Manager.php b/src/Illuminate/Support/Manager.php index ebd95c85a03c..5a1052e490d0 100755 --- a/src/Illuminate/Support/Manager.php +++ b/src/Illuminate/Support/Manager.php @@ -50,7 +50,7 @@ public function __construct(Container $container) /** * Get the default driver name. * - * @return string + * @return string|null */ abstract public function getDefaultDriver(); diff --git a/tests/Integration/Support/Fixtures/NullableManager.php b/tests/Integration/Support/Fixtures/NullableManager.php index c1e9e84ce0c2..3a8b741412a9 100644 --- a/tests/Integration/Support/Fixtures/NullableManager.php +++ b/tests/Integration/Support/Fixtures/NullableManager.php @@ -9,7 +9,7 @@ class NullableManager extends Manager /** * Get the default driver name. * - * @return string + * @return string|null */ public function getDefaultDriver() { From 048377af4784d839d1accff269c247ca6a4bd257 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:35:44 -0500 Subject: [PATCH 044/103] Bump vite in /src/Illuminate/Foundation/resources/exceptions/renderer (#57009) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.19 to 5.4.20. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.20 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../resources/exceptions/renderer/package-lock.json | 8 ++++---- .../Foundation/resources/exceptions/renderer/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json index 7c464521a836..0c48191756bb 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json @@ -11,7 +11,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.4.19", + "vite": "^5.4.20", "vite-require": "^0.2.3" } }, @@ -2107,9 +2107,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json index d9f2b42b685e..a81cdb432cdf 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json @@ -12,7 +12,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.4.19", + "vite": "^5.4.20", "vite-require": "^0.2.3" } } From bfca3c4725c4cae50316dbe9095529b9b88b0b06 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:36:19 +0000 Subject: [PATCH 045/103] Update facade docblocks --- src/Illuminate/Support/Facades/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Session.php b/src/Illuminate/Support/Facades/Session.php index 8bc63adfaf78..837243ed3443 100755 --- a/src/Illuminate/Support/Facades/Session.php +++ b/src/Illuminate/Support/Facades/Session.php @@ -8,7 +8,7 @@ * @method static int defaultRouteBlockLockSeconds() * @method static int defaultRouteBlockWaitSeconds() * @method static array getSessionConfig() - * @method static string getDefaultDriver() + * @method static string|null getDefaultDriver() * @method static void setDefaultDriver(string $name) * @method static mixed driver(string|null $driver = null) * @method static \Illuminate\Session\SessionManager extend(string $driver, \Closure $callback) From 4f8f93671020aa5a86f77f7736b65949bda9084d Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:36:34 +0400 Subject: [PATCH 046/103] Correct APC cache store docblock types (#57020) Co-authored-by: Xurshudyan --- src/Illuminate/Cache/ApcStore.php | 10 +++++----- src/Illuminate/Cache/ApcWrapper.php | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Cache/ApcStore.php b/src/Illuminate/Cache/ApcStore.php index 0c4f62710bf3..052c452c539e 100755 --- a/src/Illuminate/Cache/ApcStore.php +++ b/src/Illuminate/Cache/ApcStore.php @@ -36,7 +36,7 @@ public function __construct(ApcWrapper $apc, $prefix = '') * Retrieve an item from the cache by key. * * @param string $key - * @return mixed + * @return mixed|null */ public function get($key) { @@ -60,8 +60,8 @@ public function put($key, $value, $seconds) * Increment the value of an item in the cache. * * @param string $key - * @param mixed $value - * @return int|bool + * @param int $value + * @return int|false */ public function increment($key, $value = 1) { @@ -72,8 +72,8 @@ public function increment($key, $value = 1) * Decrement the value of an item in the cache. * * @param string $key - * @param mixed $value - * @return int|bool + * @param int $value + * @return int|false */ public function decrement($key, $value = 1) { diff --git a/src/Illuminate/Cache/ApcWrapper.php b/src/Illuminate/Cache/ApcWrapper.php index 43c6e328a8fe..d7593c90a172 100755 --- a/src/Illuminate/Cache/ApcWrapper.php +++ b/src/Illuminate/Cache/ApcWrapper.php @@ -8,7 +8,7 @@ class ApcWrapper * Get an item from the cache. * * @param string $key - * @return mixed + * @return mixed|null */ public function get($key) { @@ -23,7 +23,7 @@ public function get($key) * @param string $key * @param mixed $value * @param int $seconds - * @return array|bool + * @return bool */ public function put($key, $value, $seconds) { @@ -34,8 +34,8 @@ public function put($key, $value, $seconds) * Increment the value of an item in the cache. * * @param string $key - * @param mixed $value - * @return int|bool + * @param int $value + * @return int|false */ public function increment($key, $value) { @@ -46,8 +46,8 @@ public function increment($key, $value) * Decrement the value of an item in the cache. * * @param string $key - * @param mixed $value - * @return int|bool + * @param int $value + * @return int|false */ public function decrement($key, $value) { From 1e528f38f957a6e875afc1a62fc45d35f73d11bd Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 12 Sep 2025 09:43:08 -0500 Subject: [PATCH 047/103] fix type hint --- src/Illuminate/Collections/Arr.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 78d778d8cb31..d9505e6a3568 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -63,8 +63,10 @@ public static function add($array, $key, $value) /** * Get an array item from an array using "dot" notation. + * + * @return array|null */ - public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = null): array + public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = null) { $value = Arr::get($array, $key, $default); From 986c42a5e02b3a7309be45f52323c97db16f878e Mon Sep 17 00:00:00 2001 From: George Lioumpas Date: Fri, 12 Sep 2025 17:43:55 +0300 Subject: [PATCH 048/103] Allow dynamic tries() method on Queueable listeners (#57014) --- tests/Events/QueuedEventsTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Events/QueuedEventsTest.php b/tests/Events/QueuedEventsTest.php index 4f9ec170ba1f..9a5c7f814553 100644 --- a/tests/Events/QueuedEventsTest.php +++ b/tests/Events/QueuedEventsTest.php @@ -179,6 +179,24 @@ public function testQueuePropagateRetryUntilAndMaxExceptions() }); } + public function testQueuePropagateTries() + { + $d = new Dispatcher; + + $fakeQueue = new QueueFake(new Container); + + $d->setQueueResolver(function () use ($fakeQueue) { + return $fakeQueue; + }); + + $d->listen('some.event', TestDispatcherOptions::class.'@handle'); + $d->dispatch('some.event', ['foo', 'bar']); + + $fakeQueue->assertPushed(CallQueuedListener::class, function ($job) { + return $job->tries === 5; + }); + } + public function testQueuePropagateMiddleware() { $d = new Dispatcher; @@ -294,6 +312,11 @@ public function retryUntil() return now()->addHour(1); } + public function tries() + { + return 5; + } + public function handle() { // From 179967bfe50e1f62cbab4144a0003a4a1e30070c Mon Sep 17 00:00:00 2001 From: Danny Foster Date: Fri, 12 Sep 2025 10:31:06 -0500 Subject: [PATCH 049/103] [12.x] Add --json option to ScheduleListCommand (#57006) * feat: add `--json` option to `schedule:list` * formatting --------- Co-authored-by: Taylor Otwell --- .../Scheduling/ScheduleListCommand.php | 80 ++++++++++- .../Scheduling/ScheduleListCommandTest.php | 132 ++++++++++++++++++ 2 files changed, 207 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 0bb8f11ab498..8ba7ce997aa9 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -24,6 +24,7 @@ class ScheduleListCommand extends Command protected $signature = 'schedule:list {--timezone= : The timezone that times should be displayed in} {--next : Sort the listed tasks by their next due date} + {--json : Output the scheduled tasks as JSON} '; /** @@ -53,21 +54,78 @@ public function handle(Schedule $schedule) $events = new Collection($schedule->events()); if ($events->isEmpty()) { - $this->components->info('No scheduled tasks have been defined.'); + if ($this->option('json')) { + $this->output->writeln('[]'); + } else { + $this->components->info('No scheduled tasks have been defined.'); + } return; } + $timezone = new DateTimeZone($this->option('timezone') ?? config('app.timezone')); + + $events = $this->sortEvents($events, $timezone); + + $this->option('json') + ? $this->displayJson($events, $timezone) + : $this->displayForCli($events, $timezone); + } + + /** + * Render the scheduled tasks information as JSON. + * + * @param \Illuminate\Support\Collection $events + * @param \DateTimeZone $timezone + * @return void + */ + protected function displayJson(Collection $events, DateTimeZone $timezone) + { + $this->output->writeln($events->map(function ($event) use ($timezone) { + $nextDueDate = $this->getNextDueDateForEvent($event, $timezone); + + $command = $event->command ?? ''; + + if (! $this->output->isVerbose()) { + $command = $event->normalizeCommand($command); + } + + if ($event instanceof CallbackEvent) { + $command = $event->getSummaryForDisplay(); + + if (in_array($command, ['Closure', 'Callback'])) { + $command = 'Closure at: '.$this->getClosureLocation($event); + } + } + + return [ + 'expression' => $event->expression, + 'command' => $command, + 'description' => $event->description ?? null, + 'next_due_date' => $nextDueDate->format('Y-m-d H:i:s P'), + 'next_due_date_human' => $nextDueDate->diffForHumans(), + 'timezone' => $timezone->getName(), + 'has_mutex' => $event->mutex->exists($event), + 'repeat_seconds' => $event->isRepeatable() ? $event->repeatSeconds : null, + ]; + })->values()->toJson()); + } + + /** + * Render the scheduled tasks information formatted for the CLI. + * + * @param \Illuminate\Support\Collection $events + * @param \DateTimeZone $timezone + * @return void + */ + protected function displayForCli(Collection $events, DateTimeZone $timezone) + { $terminalWidth = self::getTerminalWidth(); $expressionSpacing = $this->getCronExpressionSpacing($events); $repeatExpressionSpacing = $this->getRepeatExpressionSpacing($events); - $timezone = new DateTimeZone($this->option('timezone') ?? config('app.timezone')); - - $events = $this->sortEvents($events, $timezone); - $events = $events->map(function ($event) use ($terminalWidth, $expressionSpacing, $repeatExpressionSpacing, $timezone) { return $this->listEvent($event, $terminalWidth, $expressionSpacing, $repeatExpressionSpacing, $timezone); }); @@ -194,6 +252,18 @@ private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone : $events; } + /** + * Render the scheduled tasks information. + * + * @param \Illuminate\Support\Collection $events + * @param \DateTimeZone $timezone + * @return void + */ + protected function display(Collection $events, DateTimeZone $timezone) + { + $this->option('json') ? $this->displayJson($events, $timezone) : $this->displayForCli($events, $timezone); + } + /** * Get the next due date for an event. * diff --git a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php index b65ab28d218e..633288e1188a 100644 --- a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php +++ b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Console\Scheduling; +use Illuminate\Console\Application; use Illuminate\Console\Command; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\ScheduleListCommand; @@ -31,6 +32,15 @@ public function testDisplayEmptySchedule() ->expectsOutputToContain('No scheduled tasks have been defined.'); } + public function testDisplayEmptyScheduleAsJson() + { + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, ['--json' => true]); + $output = Artisan::output(); + + $this->assertJson($output); + $this->assertJsonStringEqualsJsonString('[]', $output); + } + public function testDisplaySchedule() { $this->schedule->command(FooCommand::class)->quarterly(); @@ -65,6 +75,128 @@ public function testDisplaySchedule() ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now'); } + public function testDisplayScheduleAsJson() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->twiceDaily(14, 18); + $this->schedule->command('foobar', ['a' => 'b'])->everyMinute(); + $this->schedule->job(FooJob::class)->everyMinute(); + $this->schedule->job(new FooParamJob('test'))->everyMinute(); + $this->schedule->job(FooJob::class)->name('foo-named-job')->everyMinute(); + $this->schedule->job(new FooParamJob('test'))->name('foo-named-param-job')->everyMinute(); + $this->schedule->command('inspire')->cron('0 9,17 * * *'); + $this->schedule->call(fn () => '')->everyMinute(); + + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, ['--json' => true]); + $output = Artisan::output(); + + $this->assertJson($output); + $data = json_decode($output, true); + + $this->assertIsArray($data); + $this->assertCount(9, $data); + + $this->assertSame('0 0 1 1-12/3 *', $data[0]['expression']); + $this->assertNull($data[0]['repeat_seconds']); + $this->assertSame('php artisan foo:command', $data[0]['command']); + $this->assertSame('This is the description of the command.', $data[0]['description']); + $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->assertSame('* * * * *', $data[2]['expression']); + $this->assertSame('php artisan foobar a='.ProcessUtils::escapeArgument('b'), $data[2]['command']); + $this->assertNull($data[2]['description']); + $this->assertSame('1 minute from now', $data[2]['next_due_date_human']); + + $this->assertSame('Illuminate\Tests\Integration\Console\Scheduling\FooJob', $data[3]['command']); + + $this->assertSame('foo-named-job', $data[5]['command']); + + $this->assertStringContainsString('Closure at:', $data[8]['command']); + $this->assertStringContainsString('ScheduleListCommandTest.php', $data[8]['command']); + } + + public function testDisplayScheduleWithSortAsJson() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->twiceDaily(14, 18); + $this->schedule->command('foobar', ['a' => 'b'])->everyMinute(); + + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, [ + '--next' => true, + '--json' => true, + ]); + $output = Artisan::output(); + + $this->assertJson($output); + $data = json_decode($output, true); + + $this->assertIsArray($data); + $this->assertCount(3, $data); + + $this->assertSame('* * * * *', $data[0]['expression']); + $this->assertSame('1 minute from now', $data[0]['next_due_date_human']); + $this->assertSame('php artisan foobar a='.ProcessUtils::escapeArgument('b'), $data[0]['command']); + + $this->assertSame('0 14,18 * * *', $data[1]['expression']); + $this->assertSame('14 hours from now', $data[1]['next_due_date_human']); + $this->assertSame('php artisan inspire', $data[1]['command']); + + $this->assertSame('0 0 1 1-12/3 *', $data[2]['expression']); + $this->assertSame('3 months from now', $data[2]['next_due_date_human']); + $this->assertSame('php artisan foo:command', $data[2]['command']); + } + + public function testDisplayScheduleAsJsonWithTimezone() + { + $this->schedule->command('inspire')->daily(); + + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, [ + '--timezone' => 'America/Chicago', + '--json' => true, + ]); + $output = Artisan::output(); + + $this->assertJson($output); + $data = json_decode($output, true); + + $this->assertIsArray($data); + $this->assertCount(1, $data); + $this->assertSame('America/Chicago', $data[0]['timezone']); + $this->assertStringContainsString('-06:00', $data[0]['next_due_date']); + $this->assertSame('php artisan inspire', $data[0]['command']); + } + + public function testDisplayScheduleAsJsonInVerboseMode() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->everyMinute(); + $this->schedule->call(fn () => '')->everyMinute(); + + $this->withoutMockingConsoleOutput()->artisan(ScheduleListCommand::class, [ + '--json' => true, + '-v' => true, + ]); + $output = Artisan::output(); + + $this->assertJson($output); + $data = json_decode($output, true); + + $this->assertIsArray($data); + $this->assertCount(3, $data); + + $this->assertSame('0 0 1 1-12/3 *', $data[0]['expression']); + $this->assertSame(Application::phpBinary().' '.Application::artisanBinary().' foo:command', $data[0]['command']); + $this->assertSame('This is the description of the command.', $data[0]['description']); + + $this->assertSame('* * * * *', $data[1]['expression']); + $this->assertSame(Application::phpBinary().' '.Application::artisanBinary().' inspire', $data[1]['command']); + + $this->assertStringContainsString('Closure at:', $data[2]['command']); + $this->assertStringContainsString('ScheduleListCommandTest.php', $data[2]['command']); + } + public function testDisplayScheduleWithSort() { $this->schedule->command(FooCommand::class)->quarterly(); From 91f4dd653c686d194d53a0db978acf9a3f67efee Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 12 Sep 2025 15:31:34 +0000 Subject: [PATCH 050/103] Apply fixes from StyleCI --- src/Illuminate/Console/Scheduling/ScheduleListCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 8ba7ce997aa9..1ec8f1e18c8f 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -81,7 +81,7 @@ public function handle(Schedule $schedule) */ protected function displayJson(Collection $events, DateTimeZone $timezone) { - $this->output->writeln($events->map(function ($event) use ($timezone) { + $this->output->writeln($events->map(function ($event) use ($timezone) { $nextDueDate = $this->getNextDueDateForEvent($event, $timezone); $command = $event->command ?? ''; From a79308951fc058a0aa5967691ceec0bbb5d3b655 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 12 Sep 2025 14:42:13 -0500 Subject: [PATCH 051/103] [12.x] `with()` helper call simplification (#57041) * remove unecessary `with()` helper calls when we have a well defined closure, there is no reason to use the `with()` helper, or even the closure at all. in most cases, but using a temporary variable we achieve the same effect with arguably cleaner easier to read code. * style fixes * revert refactor I can't figure out how to fix the tests right now, so reverting these --- src/Illuminate/Bus/Batch.php | 12 +++--- src/Illuminate/Console/Signals.php | 18 ++++---- .../Foundation/Bootstrap/HandleExceptions.php | 42 +++++++++---------- .../Foundation/Console/DocsCommand.php | 38 ++++++++--------- .../Concerns/InteractsWithDatabase.php | 32 +++++++------- src/Illuminate/Mail/Attachment.php | 12 +++--- .../Routing/RoutingServiceProvider.php | 20 ++++----- .../Concerns/ValidatesAttributes.php | 7 ++-- .../Bootstrap/HandleExceptionsTest.php | 16 ++----- 9 files changed, 94 insertions(+), 103 deletions(-) diff --git a/src/Illuminate/Bus/Batch.php b/src/Illuminate/Bus/Batch.php index 717d1c4ab11d..51f84ed86822 100644 --- a/src/Illuminate/Bus/Batch.php +++ b/src/Illuminate/Bus/Batch.php @@ -168,12 +168,12 @@ public function add($jobs) if (is_array($job)) { $count += count($job); - return with($this->prepareBatchedChain($job), function ($chain) { - return $chain->first() - ->allOnQueue($this->options['queue'] ?? null) - ->allOnConnection($this->options['connection'] ?? null) - ->chain($chain->slice(1)->values()->all()); - }); + $chain = $this->prepareBatchedChain($job); + + return $chain->first() + ->allOnQueue($this->options['queue'] ?? null) + ->allOnConnection($this->options['connection'] ?? null) + ->chain($chain->slice(1)->values()->all()); } else { $job->withBatchId($this->id); diff --git a/src/Illuminate/Console/Signals.php b/src/Illuminate/Console/Signals.php index 425352594c88..341e0de2681f 100644 --- a/src/Illuminate/Console/Signals.php +++ b/src/Illuminate/Console/Signals.php @@ -51,21 +51,21 @@ public function register($signal, $callback) { $this->previousHandlers[$signal] ??= $this->initializeSignal($signal); - with($this->getHandlers(), function ($handlers) use ($signal) { - $handlers[$signal] ??= $this->initializeSignal($signal); + $handlers = $this->getHandlers(); - $this->setHandlers($handlers); - }); + $handlers[$signal] ??= $this->initializeSignal($signal); + + $this->setHandlers($handlers); $this->registry->register($signal, $callback); - with($this->getHandlers(), function ($handlers) use ($signal) { - $lastHandlerInserted = array_pop($handlers[$signal]); + $handlers = $this->getHandlers(); + + $lastHandlerInserted = array_pop($handlers[$signal]); - array_unshift($handlers[$signal], $lastHandlerInserted); + array_unshift($handlers[$signal], $lastHandlerInserted); - $this->setHandlers($handlers); - }); + $this->setHandlers($handlers); } /** diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index a06b655dffad..a5588cf0694e 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -132,21 +132,21 @@ protected function shouldIgnoreDeprecationErrors() */ protected function ensureDeprecationLoggerIsConfigured() { - with(static::$app['config'], function ($config) { - if ($config->get('logging.channels.deprecations')) { - return; - } + $config = static::$app['config']; - $this->ensureNullLogDriverIsConfigured(); + if ($config->get('logging.channels.deprecations')) { + return; + } - if (is_array($options = $config->get('logging.deprecations'))) { - $driver = $options['channel'] ?? 'null'; - } else { - $driver = $options ?? 'null'; - } + $this->ensureNullLogDriverIsConfigured(); - $config->set('logging.channels.deprecations', $config->get("logging.channels.{$driver}")); - }); + if (is_array($options = $config->get('logging.deprecations'))) { + $driver = $options['channel'] ?? 'null'; + } else { + $driver = $options ?? 'null'; + } + + $config->set('logging.channels.deprecations', $config->get("logging.channels.{$driver}")); } /** @@ -156,16 +156,16 @@ protected function ensureDeprecationLoggerIsConfigured() */ protected function ensureNullLogDriverIsConfigured() { - with(static::$app['config'], function ($config) { - if ($config->get('logging.channels.null')) { - return; - } + $config = static::$app['config']; - $config->set('logging.channels.null', [ - 'driver' => 'monolog', - 'handler' => NullHandler::class, - ]); - }); + if ($config->get('logging.channels.null')) { + return; + } + + $config->set('logging.channels.null', [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ]); } /** diff --git a/src/Illuminate/Foundation/Console/DocsCommand.php b/src/Illuminate/Foundation/Console/DocsCommand.php index 1eea49070cc5..9edca71de61c 100644 --- a/src/Illuminate/Foundation/Console/DocsCommand.php +++ b/src/Illuminate/Foundation/Console/DocsCommand.php @@ -125,11 +125,11 @@ public function handle(Http $http, Cache $cache) */ protected function openUrl() { - with($this->url(), function ($url) { - $this->components->info("Opening the docs to: {$url}"); + $url = $this->url(); - $this->open($url); - }); + $this->components->info("Opening the docs to: {$url}"); + + $this->open($url); } /** @@ -145,9 +145,9 @@ protected function url() ]); } - return with($this->page(), function ($page) { - return trim("https://laravel.com/docs/{$this->version()}/{$page}#{$this->section($page)}", '#/'); - }); + $page = $this->page(); + + return trim("https://laravel.com/docs/{$this->version()}/{$page}#{$this->section($page)}", '#/'); } /** @@ -157,15 +157,15 @@ protected function url() */ protected function page() { - return with($this->resolvePage(), function ($page) { - if ($page === null) { - $this->components->warn('Unable to determine the page you are trying to visit.'); + $page = $this->resolvePage(); - return '/'; - } + if ($page === null) { + $this->components->warn('Unable to determine the page you are trying to visit.'); - return $page; - }); + return '/'; + } + + return $page; } /** @@ -441,11 +441,11 @@ public function docs() */ protected function refreshDocs() { - with($this->fetchDocs(), function ($response) { - if ($response->successful()) { - $this->cache->put("artisan.docs.{{$this->version()}}.index", $response->collect(), CarbonInterval::months(2)); - } - }); + $response = $this->fetchDocs(); + + if ($response->successful()) { + $this->cache->put("artisan.docs.{{$this->version()}}.index", $response->collect(), CarbonInterval::months(2)); + } } /** diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php index db89e067c160..73d5f9771b5b 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php @@ -225,22 +225,22 @@ protected function assertModelMissing($model) */ public function expectsDatabaseQueryCount($expected, $connection = null) { - with($this->getConnection($connection), function ($connectionInstance) use ($expected, $connection) { - $actual = 0; - - $connectionInstance->listen(function (QueryExecuted $event) use (&$actual, $connectionInstance, $connection) { - if (is_null($connection) || $connectionInstance === $event->connection) { - $actual++; - } - }); - - $this->beforeApplicationDestroyed(function () use (&$actual, $expected, $connectionInstance) { - $this->assertSame( - $expected, - $actual, - "Expected {$expected} database queries on the [{$connectionInstance->getName()}] connection. {$actual} occurred." - ); - }); + $connectionInstance = $this->getConnection($connection); + + $actual = 0; + + $connectionInstance->listen(function (QueryExecuted $event) use (&$actual, $connectionInstance, $connection) { + if (is_null($connection) || $connectionInstance === $event->connection) { + $actual++; + } + }); + + $this->beforeApplicationDestroyed(function () use (&$actual, $expected, $connectionInstance) { + $this->assertSame( + $expected, + $actual, + "Expected {$expected} database queries on the [{$connectionInstance->getName()}] connection. {$actual} occurred." + ); }); return $this; diff --git a/src/Illuminate/Mail/Attachment.php b/src/Illuminate/Mail/Attachment.php index 8e2e87ed5496..04b40f8e997c 100644 --- a/src/Illuminate/Mail/Attachment.php +++ b/src/Illuminate/Mail/Attachment.php @@ -206,15 +206,17 @@ function ($data) use ($mail, $options) { */ public function isEquivalent(Attachment $attachment, $options = []) { - return with([ + $newOptions = [ 'as' => $options['as'] ?? $attachment->as, 'mime' => $options['mime'] ?? $attachment->mime, - ], fn ($options) => $this->attachWith( + ]; + + return $this->attachWith( fn ($path) => [$path, ['as' => $this->as, 'mime' => $this->mime]], fn ($data) => [$data(), ['as' => $this->as, 'mime' => $this->mime]], ) === $attachment->attachWith( - fn ($path) => [$path, $options], - fn ($data) => [$data(), $options], - )); + fn ($path) => [$path, $newOptions], + fn ($data) => [$data(), $newOptions], + ); } } diff --git a/src/Illuminate/Routing/RoutingServiceProvider.php b/src/Illuminate/Routing/RoutingServiceProvider.php index f964f5b9baa9..bd4fb5d91274 100755 --- a/src/Illuminate/Routing/RoutingServiceProvider.php +++ b/src/Illuminate/Routing/RoutingServiceProvider.php @@ -134,16 +134,16 @@ protected function registerPsrRequest() { $this->app->bind(ServerRequestInterface::class, function ($app) { if (class_exists(PsrHttpFactory::class)) { - return with((new PsrHttpFactory) - ->createRequest($illuminateRequest = $app->make('request')), function (ServerRequestInterface $request) use ($illuminateRequest) { - if ($illuminateRequest->getContentTypeFormat() !== 'json' && $illuminateRequest->request->count() === 0) { - return $request; - } - - return $request->withParsedBody( - array_merge($request->getParsedBody() ?? [], $illuminateRequest->getPayload()->all()) - ); - }); + $illuminateRequest = $app->make('request'); + $request = (new PsrHttpFactory)->createRequest($illuminateRequest); + + if ($illuminateRequest->getContentTypeFormat() !== 'json' && $illuminateRequest->request->count() === 0) { + return $request; + } + + return $request->withParsedBody( + array_merge($request->getParsedBody() ?? [], $illuminateRequest->getPayload()->all()) + ); } throw new BindingResolutionException('Unable to resolve PSR request. Please install the "symfony/psr-http-message-bridge" package.'); diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 29967af83276..082f994efe8c 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -472,10 +472,9 @@ public function validateBetween($attribute, $value, $parameters) $this->requireParameterCount(2, $parameters, 'between'); try { - return with( - BigNumber::of($this->getSize($attribute, $value)), - fn ($size) => $size->isGreaterThanOrEqualTo($this->trim($parameters[0])) && $size->isLessThanOrEqualTo($this->trim($parameters[1])) - ); + $size = BigNumber::of($this->getSize($attribute, $value)); + + return $size->isGreaterThanOrEqualTo($this->trim($parameters[0])) && $size->isLessThanOrEqualTo($this->trim($parameters[1])); } catch (MathException) { return false; } diff --git a/tests/Foundation/Bootstrap/HandleExceptionsTest.php b/tests/Foundation/Bootstrap/HandleExceptionsTest.php index f900144068fc..d0f84de2cc72 100644 --- a/tests/Foundation/Bootstrap/HandleExceptionsTest.php +++ b/tests/Foundation/Bootstrap/HandleExceptionsTest.php @@ -29,9 +29,7 @@ protected function setUp(): void protected function handleExceptions() { return tap(new HandleExceptions(), function ($instance) { - with(new ReflectionClass($instance), function ($reflection) use ($instance) { - $reflection->getProperty('app')->setValue($instance, $this->app); - }); + (new ReflectionClass($instance))->getProperty('app')->setValue($instance, $this->app); }); } @@ -381,11 +379,7 @@ public function testForgetApp() { $instance = $this->handleExceptions(); - $appResolver = fn () => with(new ReflectionClass($instance), function ($reflection) use ($instance) { - $property = $reflection->getProperty('app'); - - return $property->getValue($instance); - }); + $appResolver = fn () => (new ReflectionClass($instance))->getProperty('app')->getValue($instance); $this->assertNotNull($appResolver()); @@ -398,11 +392,7 @@ public function testHandlerForgetsPreviousApp() { $instance = $this->handleExceptions(); - $appResolver = fn () => with(new ReflectionClass($instance), function ($reflection) use ($instance) { - $property = $reflection->getProperty('app'); - - return $property->getValue($instance); - }); + $appResolver = fn () => (new ReflectionClass($instance))->getProperty('app')->getValue($instance); $this->assertSame($this->app, $appResolver()); From 8323276a2b754d3c653bc6bddce59908dc77cadc Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 12 Sep 2025 14:42:29 -0500 Subject: [PATCH 052/103] handle all Enum types for default values (#57040) by using the internal `enum_value()` helper, we can handle both `UnitEnum`s and `BackedEnum`s, similar to how we do elsewhere in the framework. we check for `UnitEnum`, because it is the broader of the 2 types. --- src/Illuminate/Database/Schema/Grammars/Grammar.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index bd67b9fded29..391324b9c6d2 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -2,13 +2,15 @@ namespace Illuminate\Database\Schema\Grammars; -use BackedEnum; use Illuminate\Contracts\Database\Query\Expression; use Illuminate\Database\Concerns\CompilesJsonPaths; use Illuminate\Database\Grammar as BaseGrammar; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; use RuntimeException; +use UnitEnum; + +use function Illuminate\Support\enum_value; abstract class Grammar extends BaseGrammar { @@ -477,8 +479,8 @@ protected function getDefaultValue($value) return $this->getValue($value); } - if ($value instanceof BackedEnum) { - return "'".str_replace("'", "''", $value->value)."'"; + if ($value instanceof UnitEnum) { + return "'".str_replace("'", "''", enum_value($value))."'"; } return is_bool($value) From 99b08c180122497e8c11ea165e201b0800bb376c Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:29:02 +0300 Subject: [PATCH 053/103] Refactor chained method calls for readability (#57050) --- src/Illuminate/Database/Schema/SqliteSchemaState.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Schema/SqliteSchemaState.php b/src/Illuminate/Database/Schema/SqliteSchemaState.php index 23cf3896f41b..e539064bcf12 100644 --- a/src/Illuminate/Database/Schema/SqliteSchemaState.php +++ b/src/Illuminate/Database/Schema/SqliteSchemaState.php @@ -16,11 +16,11 @@ class SqliteSchemaState extends SchemaState */ public function dump(Connection $connection, $path) { - ($process = $this->makeProcess( - $this->baseCommand().' ".schema --indent"' - ))->setTimeout(null)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ - // - ])); + $process = $this->makeProcess($this->baseCommand().' ".schema --indent"') + ->setTimeout(null) + ->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + // + ])); $migrations = preg_replace('/CREATE TABLE sqlite_.+?\);[\r\n]+/is', '', $process->getOutput()); From 588b9c8e87ea091974946de14beb7263337c90a8 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:34:12 +0300 Subject: [PATCH 054/103] Improve docblock wording (#57056) --- src/Illuminate/Testing/TestResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index f2ad50faef32..98de591eb51d 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -246,7 +246,7 @@ public function assertRedirectBack() } /** - * Assert whether the response is redirecting back to the previous location and the session has the given errors. + * Assert whether the response is redirecting back to the previous location with the given errors in the session. * * @param string|array $keys * @param mixed $format From 92bea89f9ba7b9e3754fc14a3fac6de5204a4976 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:35:19 +0300 Subject: [PATCH 055/103] Refactor chained method calls for readability (#57054) --- src/Illuminate/Database/Seeder.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Seeder.php b/src/Illuminate/Database/Seeder.php index 67b617ec8936..cac50afb579d 100755 --- a/src/Illuminate/Database/Seeder.php +++ b/src/Illuminate/Database/Seeder.php @@ -50,10 +50,8 @@ public function call($class, $silent = false, array $parameters = []) $name = get_class($seeder); if ($silent === false && isset($this->command)) { - (new TwoColumnDetail($this->command->getOutput()))->render( - $name, - 'RUNNING' - ); + (new TwoColumnDetail($this->command->getOutput())) + ->render($name, 'RUNNING'); } $startTime = microtime(true); @@ -63,10 +61,8 @@ public function call($class, $silent = false, array $parameters = []) if ($silent === false && isset($this->command)) { $runTime = number_format((microtime(true) - $startTime) * 1000); - (new TwoColumnDetail($this->command->getOutput()))->render( - $name, - "$runTime ms DONE" - ); + (new TwoColumnDetail($this->command->getOutput())) + ->render($name, "$runTime ms DONE"); $this->command->getOutput()->writeln(''); } From 1b4fd54863516e7fa7e4db6e05da76d5e97afea8 Mon Sep 17 00:00:00 2001 From: Ryuta Hamasaki Date: Mon, 15 Sep 2025 23:54:51 +0900 Subject: [PATCH 056/103] [12.x] Update local exception page (#57036) * Configure Vite with Tailwind CSS * Register new renderer template * Base layout with static content * Use dashed border * Add separator * Replace hard-coded values * Replace hard-coded query * Add empty state * Clean up section container * Clean up * Add section-container * Use section-container in topbar * Clean up * Extract query component * Add request-header component * Add request-body component * Add routing component * Add routing-parameter component * Add overview component * Add request-url component * Add header component * Add topbar component * Add trace component skeleton * Group exception frames * Add formatted-source and file-with-line components * Add frames * Add previous frame * Show code snippet without syntax highlight * Use exceptionAsMarkdown * Remove unused props * Assign snippet to a variable * Syntax highlight code snippet * Update phiki * Syntax highlight query, body, and route parameters * Update phiki * Use LineDecoration to highlight code snippet * Unescape highlighted text * Add syntax-highlight component * Handle files with less than 5 lines * Install Alpine.js * Add layout component * Add laravel-ascii component * Update method badge * Mark frame as main * Expand/collapse frames * Use current color in SVGs * Don't show callable when the frame doesn't have a class * Copy request URL to clipboard * Truncate long source * Add tooltip * wip: tooltip * Add side prop to tooltip * Add tooltip on request url * Prevent non-vendor frame from overflowing * Allow horizontal scroll on request body * Add tooltip on request headers * Add tooltip on database query * Hide JS-dependent elements until Alpine is loaded * Paginate queries * Add database icon * Add folder icons * Add copy icon * Add globe icon * Add alert icon * Copy exception as markdown * Add badge component * Add http-method component * Expand frames on clicking parent div * Open file in editor * Use pointer cursor on pagination buttons * Replace custom tooltip component with tippy.js * Upgrade phiki/phiki * Use dark-plus theme * Use dvh instead of screen * wip: light mode * Style gutter text * Only show the first 100 queries * light mode styling * Add light theme for syntax highlighter * Uppercase keys * Style copy as markdown button * Clean up * Add hover effect on laravel ascii logo * Add shadow * Fix tooltip position for queries * Tweak padding for mobile * Allow HTML in tooltip * Show frame arguments * Syntax highlight frame source * Add markdown template * Replace renderer directory * Simplify CSS rendering * Remove unused method * Revert unintended changes * Rename array key * Rename type to operator * Set max width for tooltip * Display vendor frames in two lines * Apply fixes from StyleCI * Add empty state for routing context * Only round top corners when frame is expanded * Update dot color for non-vendor frame * Update topbar height * Adjust padding around header section * Move up request url and have it overlap the separator line * Adjust spacing * Adjust spacing for mobile * Replace shadow-sm with shadow-xs * Apply bg-white without opacity in light mode * Adjust spacing --------- Co-authored-by: StyleCI Bot --- composer.json | 1 + .../Exceptions/Renderer/Exception.php | 59 +- .../Foundation/Exceptions/Renderer/Frame.php | 87 +- .../Exceptions/Renderer/Listener.php | 2 +- .../Exceptions/Renderer/Renderer.php | 15 +- .../renderer/components/badge.blade.php | 47 + .../renderer/components/card.blade.php | 5 - .../renderer/components/context.blade.php | 148 - .../renderer/components/copy-button.blade.php | 25 - .../renderer/components/editor.blade.php | 29 - .../renderer/components/empty-state.blade.php | 5 + .../components/file-with-line.blade.php | 21 + .../components/formatted-source.blade.php | 23 + .../renderer/components/frame-code.blade.php | 15 + .../renderer/components/frame.blade.php | 55 + .../renderer/components/header.blade.php | 50 +- .../renderer/components/http-method.blade.php | 16 + .../renderer/components/icons/alert.blade.php | 10 + .../renderer/components/icons/check.blade.php | 3 + .../components/icons/chevron-down.blade.php | 3 - .../components/icons/chevron-left.blade.php | 3 + .../components/icons/chevron-right.blade.php | 3 + .../components/icons/chevron-up.blade.php | 3 - .../icons/chevrons-down-up.blade.php | 11 + .../components/icons/chevrons-left.blade.php | 4 + .../components/icons/chevrons-right.blade.php | 4 + .../icons/chevrons-up-down.blade.php | 11 + .../icons/computer-desktop.blade.php | 10 - .../renderer/components/icons/copy.blade.php | 10 + .../components/icons/database.blade.php | 3 + .../components/icons/folder-open.blade.php | 11 + .../components/icons/folder.blade.php | 5 + .../renderer/components/icons/globe.blade.php | 5 + .../renderer/components/icons/info.blade.php | 5 + .../components/icons/laravel-ascii.blade.php | 4 + .../renderer/components/icons/moon.blade.php | 10 - .../renderer/components/icons/sun.blade.php | 10 - .../laravel-ascii-spotlight.blade.php | 22 + .../renderer/components/layout.blade.php | 46 +- .../renderer/components/navigation.blade.php | 29 - .../renderer/components/overview.blade.php | 30 + .../renderer/components/query.blade.php | 182 ++ .../components/request-body.blade.php | 12 + .../components/request-header.blade.php | 18 + .../renderer/components/request-url.blade.php | 40 + .../components/routing-parameter.blade.php | 12 + .../renderer/components/routing.blade.php | 20 + .../components/section-container.blade.php | 5 + .../renderer/components/separator.blade.php | 3 + .../components/syntax-highlight.blade.php | 39 + .../components/theme-switcher.blade.php | 98 - .../renderer/components/topbar.blade.php | 46 + .../components/trace-and-editor.blade.php | 13 - .../renderer/components/trace.blade.php | 97 +- .../components/vendor-frame.blade.php | 13 + .../components/vendor-frames.blade.php | 45 + .../exceptions/renderer/dark-mode.css | 1 - .../exceptions/renderer/dist/dark-mode.css | 1 - .../exceptions/renderer/dist/light-mode.css | 10 - .../exceptions/renderer/dist/scripts.js | 8 +- .../exceptions/renderer/dist/styles.css | 2 +- .../exceptions/renderer/light-mode.css | 1 - .../exceptions/renderer/package-lock.json | 2653 +++++++---------- .../exceptions/renderer/package.json | 17 +- .../exceptions/renderer/postcss.config.js | 6 - .../resources/exceptions/renderer/scripts.js | 28 +- .../exceptions/renderer/show.blade.php | 46 +- .../resources/exceptions/renderer/styles.css | 91 +- .../exceptions/renderer/tailwind.config.js | 15 - .../exceptions/renderer/vite.config.js | 12 +- 70 files changed, 2113 insertions(+), 2279 deletions(-) create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/badge.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/card.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/context.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/copy-button.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/editor.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/empty-state.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/file-with-line.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/formatted-source.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/frame-code.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/frame.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/http-method.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/alert.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/check.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevron-down.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevron-left.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevron-right.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevron-up.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevrons-down-up.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevrons-left.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevrons-right.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/chevrons-up-down.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/computer-desktop.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/copy.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/database.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/folder-open.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/folder.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/globe.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/info.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/laravel-ascii.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/moon.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/icons/sun.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/laravel-ascii-spotlight.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/navigation.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/overview.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/query.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/request-body.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/request-header.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/request-url.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/routing-parameter.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/routing.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/section-container.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/separator.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/syntax-highlight.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/topbar.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/trace-and-editor.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/vendor-frame.blade.php create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/components/vendor-frames.blade.php delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/dark-mode.css delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/dist/dark-mode.css delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/dist/light-mode.css delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/light-mode.css delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/postcss.config.js delete mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/tailwind.config.js diff --git a/composer.json b/composer.json index 382e7dab44a9..b3fcb2959193 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "monolog/monolog": "^3.0", "nesbot/carbon": "^3.8.4", "nunomaduro/termwind": "^2.0", + "phiki/phiki": "v2.0.0", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php b/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php index eb65929f3150..aeb4ff5b5fe9 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php @@ -87,17 +87,13 @@ public function class() } /** - * Get the first "non-vendor" frame index. + * Get the exception code. * - * @return int + * @return int|string */ - public function defaultFrame() + public function code() { - $key = array_search(false, array_map(function (Frame $frame) { - return $frame->isFromVendor(); - }, $this->frames()->all())); - - return $key === false ? 0 : $key; + return $this->exception->getCode(); } /** @@ -120,9 +116,50 @@ public function frames() array_shift($trace); } - return new Collection(array_map( - fn (array $trace) => new Frame($this->exception, $classMap, $trace, $this->basePath), $trace, - )); + $frames = []; + $previousFrame = null; + + foreach (array_reverse($trace) as $frameData) { + $frame = new Frame($this->exception, $classMap, $frameData, $this->basePath, $previousFrame); + $frames[] = $frame; + $previousFrame = $frame; + } + + $frames = array_reverse($frames); + + foreach ($frames as $frame) { + if (! $frame->isFromVendor()) { + $frame->markAsMain(); + break; + } + } + + return new Collection($frames); + } + + /** + * Get the exception's frames grouped by vendor status. + * + * @return array}> + */ + public function frameGroups() + { + $groups = []; + + foreach ($this->frames() as $frame) { + $isVendor = $frame->isFromVendor(); + + if (empty($groups) || $groups[array_key_last($groups)]['is_vendor'] !== $isVendor) { + $groups[] = [ + 'is_vendor' => $isVendor, + 'frames' => [], + ]; + } + + $groups[array_key_last($groups)]['frames'][] = $frame; + } + + return $groups; } /** diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php b/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php index efd6d7a62f63..fd615b6aa636 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php @@ -37,20 +37,36 @@ class Frame */ protected $basePath; + /** + * The previous frame. + * + * @var \Illuminate\Foundation\Exceptions\Renderer\Frame|null + */ + protected $previous; + + /** + * Whether this frame is the main (first non-vendor) frame. + * + * @var bool + */ + protected $isMain = false; + /** * Create a new frame instance. * * @param \Symfony\Component\ErrorHandler\Exception\FlattenException $exception * @param array $classMap - * @param array{file: string, line: int, class?: string, type?: string, function?: string} $frame + * @param array{file: string, line: int, class?: string, type?: string, function?: string, args?: array} $frame * @param string $basePath + * @param \Illuminate\Foundation\Exceptions\Renderer\Frame|null $previous */ - public function __construct(FlattenException $exception, array $classMap, array $frame, string $basePath) + public function __construct(FlattenException $exception, array $classMap, array $frame, string $basePath, ?Frame $previous = null) { $this->exception = $exception; $this->classMap = $classMap; $this->frame = $frame; $this->basePath = $basePath; + $this->previous = $previous; } /** @@ -95,7 +111,11 @@ public function class() */ public function file() { - return str_replace($this->basePath.'/', '', $this->frame['file']); + return match (true) { + ! isset($this->frame['file']) => '[internal function]', + ! is_string($this->frame['file']) => '[unknown file]', + default => str_replace($this->basePath.'/', '', $this->frame['file']), + }; } /** @@ -114,6 +134,16 @@ public function line() return $this->frame['line'] > $maxLines ? 1 : $this->frame['line']; } + /** + * Get the frame's function operator. + * + * @return '::'|'->'|'' + */ + public function operator() + { + return $this->frame['type']; + } + /** * Get the frame's function or method. * @@ -127,6 +157,27 @@ public function callable() }; } + /** + * Get the frame's arguments. + * + * @return array + */ + public function args() + { + if (! isset($this->frame['args']) || ! is_array($this->frame['args']) || count($this->frame['args']) === 0) { + return []; + } + + return array_map(function ($argument) { + [$key, $value] = $argument; + + return match ($key) { + 'object' => "{$key}({$value})", + default => $key, + }; + }, $this->frame['args']); + } + /** * Get the frame's code snippet. * @@ -157,4 +208,34 @@ public function isFromVendor() return ! str_starts_with($this->frame['file'], $this->basePath) || str_starts_with($this->frame['file'], $this->basePath.'/vendor'); } + + /** + * Get the previous frame. + * + * @return \Illuminate\Foundation\Exceptions\Renderer\Frame|null + */ + public function previous() + { + return $this->previous; + } + + /** + * Mark this frame as the main frame. + * + * @return void + */ + public function markAsMain() + { + $this->isMain = true; + } + + /** + * Determine if this is the main frame. + * + * @return bool + */ + public function isMain() + { + return $this->isMain; + } } diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php b/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php index 3b5abe63a68d..3ab4a66680e0 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php @@ -59,7 +59,7 @@ public function queries() */ public function onQueryExecuted(QueryExecuted $event) { - if (count($this->queries) === 100) { + if (count($this->queries) === 101) { return; } diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php b/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php index ac8c03476438..cc464f3f9fbe 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Foundation\Exceptions\Renderer\Mappers\BladeMapper; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Throwable; @@ -108,19 +107,7 @@ public function render(Request $request, Throwable $throwable) */ public static function css() { - return (new Collection([ - ['styles.css', []], - ['light-mode.css', ['data-theme' => 'light']], - ['dark-mode.css', ['data-theme' => 'dark']], - ]))->map(function ($fileAndAttributes) { - [$filename, $attributes] = $fileAndAttributes; - - return ''; - })->implode(''); + return ''; } /** diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/badge.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/badge.blade.php new file mode 100644 index 000000000000..5fedfcf12535 --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/badge.blade.php @@ -0,0 +1,47 @@ +@props(['type' => 'default', 'variant' => 'soft']) + +@php +$baseClasses = 'inline-flex w-fit shrink-0 items-center justify-center gap-1 font-mono leading-3 uppercase transition-colors dark:border [&_svg]:size-2.5 h-6 min-w-5 rounded-md px-1.5 text-xs/none'; + +$types = [ + 'default' => [ + 'soft' => 'bg-black/8 text-neutral-900 dark:border-neutral-700 dark:bg-white/10 dark:text-neutral-100', + 'solid' => 'bg-neutral-600 text-neutral-100 dark:border-neutral-500 dark:bg-neutral-600', + ], + 'success' => [ + 'soft' => 'bg-emerald-200 text-emerald-900 dark:border-emerald-600 dark:bg-emerald-900/70 dark:text-emerald-400', + 'solid' => 'bg-emerald-600 dark:border-emerald-500 dark:bg-emerald-600', + ], + 'primary' => [ + 'soft' => 'bg-blue-100 text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300', + 'solid' => 'bg-blue-700 dark:border-blue-600 dark:bg-blue-700', + ], + 'error' => [ + 'soft' => 'bg-rose-200 text-rose-900 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-100 dark:[&_svg]:!text-white', + 'solid' => 'bg-rose-600 dark:border-rose-500 dark:bg-rose-600', + ], + 'alert' => [ + 'soft' => 'bg-amber-200 text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300', + 'solid' => 'bg-amber-600 dark:border-amber-500 dark:bg-amber-600', + ], + 'white' => [ + 'soft' => 'bg-white text-neutral-900 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100', + 'solid' => 'bg-black/10 text-neutral-900 dark:text-neutral-900 dark:bg-white', + ], +]; + +$variants = [ + 'soft' => '', + 'solid' => 'text-white dark:text-white [&_svg]:!text-white', +]; + +$typeClasses = $types[$type][$variant] ?? $types['default']['soft']; +$variantClasses = $variants[$variant] ?? $variants['soft']; + +$classes = implode(' ', [$baseClasses, $typeClasses, $variantClasses]); + +@endphp + +
merge(['class' => $classes]) }}> + {{ $slot }} +
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/card.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/card.blade.php deleted file mode 100644 index 14dcd4f51426..000000000000 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/card.blade.php +++ /dev/null @@ -1,5 +0,0 @@ -
merge(['class' => "@container flex flex-col p-6 sm:p-12 bg-white dark:bg-gray-900/80 text-gray-900 dark:text-gray-100 rounded-lg default:col-span-full default:lg:col-span-6 default:row-span-1 dark:ring-1 dark:ring-gray-800 shadow-xl"]) }} -> - {{ $slot }} -
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/context.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/context.blade.php deleted file mode 100644 index 13b922a865e6..000000000000 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/context.blade.php +++ /dev/null @@ -1,148 +0,0 @@ -@use('Illuminate\Support\Str') - -
- Request -
- -
- {{ $exception->request()->method() }} - {{ Str::start($exception->request()->path(), '/') }} -
- -
- Headers -
- -
- @forelse ($exception->requestHeaders() as $key => $value) -
- - {{ $key }} - - -
{{ $value }}
-
-
- @empty - -
No headers data
-
- @endforelse -
- -
- Body -
- -
-
- -
{{ $exception->requestBody() ?: 'No body data' }}
-
-
-
- -
- - -
- Application -
- -
- Routing -
- -
- @forelse ($exception->applicationRouteContext() as $name => $value) -
- {{ $name }} - -
{{ $value }}
-
-
- @empty - -
No routing data
-
- @endforelse -
- - @if ($routeParametersContext = $exception->applicationRouteParametersContext()) -
- Routing Parameters -
- -
-
- -
{{ $routeParametersContext }}
-
-
-
- @endif - -
- Database Queries - - @if (count($exception->applicationQueries()) === 100) - only the first 100 queries are displayed - @endif - -
- -
- @forelse ($exception->applicationQueries() as ['connectionName' => $connectionName, 'sql' => $sql, 'time' => $time]) -
-
- {{ $connectionName }} - -
- -
{{ $sql }}
-
-
- @empty - -
No query data
-
- @endforelse -
-
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/copy-button.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/copy-button.blade.php deleted file mode 100644 index 115872f4278e..000000000000 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/copy-button.blade.php +++ /dev/null @@ -1,25 +0,0 @@ - -
- -
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/editor.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/editor.blade.php deleted file mode 100644 index fbb8b38cb006..000000000000 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/editor.blade.php +++ /dev/null @@ -1,29 +0,0 @@ -@foreach ($exception->frames() as $frame) -
-
-
-
- @if (config('app.editor')) - - {{ $frame->file() }}:{{ $frame->line() }} - - @else - {{ $frame->file() }}:{{ $frame->line() }} - @endif -
-
-
-
-
-
-
-@endforeach diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/empty-state.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/empty-state.blade.php new file mode 100644 index 000000000000..9a41f78a8f1b --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/empty-state.blade.php @@ -0,0 +1,5 @@ +@props(['message']) + +
+ // {{ $message }} +
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/file-with-line.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/file-with-line.blade.php new file mode 100644 index 000000000000..99dd9f4a493b --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/file-with-line.blade.php @@ -0,0 +1,21 @@ +@props(['frame', 'direction' => 'ltr']) + +@php + $file = $frame->file(); + $line = $frame->line(); +@endphp + +
merge(['class' => 'truncate font-mono text-xs text-neutral-500 dark:text-neutral-400']) }} + dir="{{ $direction }}" +> + + @if (config('app.editor')) + + {{ $file }}:{{ $line }} + + @else + {{ $file }}:{{ $line }} + @endif + +
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/formatted-source.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/formatted-source.blade.php new file mode 100644 index 000000000000..06e09c692d0d --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/formatted-source.blade.php @@ -0,0 +1,23 @@ +@props(['frame']) + +@php + if ($class = $frame->class()) { + $source = $class; + + if ($previous = $frame->previous()) { + $source .= $previous->operator(); + $source .= $previous->callable(); + $source .= '('.implode(', ', $previous->args()).')'; + } + } else { + $source = $frame->source(); + } +@endphp + + diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame-code.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame-code.blade.php new file mode 100644 index 000000000000..0789d76ccffb --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame-code.blade.php @@ -0,0 +1,15 @@ +@props(['code', 'highlightedLine']) + +
+ +
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame.blade.php new file mode 100644 index 000000000000..66e9dc7a529d --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/frame.blade.php @@ -0,0 +1,55 @@ +@props(['frame']) + +
+
+ {{-- Dot --}} +
+
+
+ +
+ + +
+ +
+ +
+
+ + @if($snippet = $frame->snippet()) + + @endif +
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/header.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/header.blade.php index 4da473e71d90..2bb929a52684 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/header.blade.php +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/header.blade.php @@ -1,28 +1,32 @@ - -
-
-
- - - {{ implode(' ', array_slice(explode('\\', $exception->class()), -1)) }} - -
-
- {{ $exception->message() }} -
-
+@props(['exception']) -