From 695097c9dc11645230b97e13128efa6e2735d6ab Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Nov 2024 09:30:28 -0600 Subject: [PATCH 001/158] check if running in console --- src/Illuminate/Foundation/Application.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index f1b72f455ab2..4ff5ca055037 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -609,7 +609,9 @@ public function isProduction() */ public function detectEnvironment(Closure $callback) { - $args = $_SERVER['argv'] ?? null; + $args = $this->runningInConsole() && $_SERVER['argv'] + ? $_SERVER['argv'] + : null; return $this['env'] = (new EnvironmentDetector)->detect($callback, $args); } From eded6bdfc05af9b5437d107b4d092558fe46292c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Nov 2024 09:30:55 -0600 Subject: [PATCH 002/158] check if running in console --- src/Illuminate/Foundation/Application.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0aa6a98e0c15..643f85663642 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -602,7 +602,9 @@ public function isProduction() */ public function detectEnvironment(Closure $callback) { - $args = $_SERVER['argv'] ?? null; + $args = $this->runningInConsole() && $_SERVER['argv'] + ? $_SERVER['argv'] + : null; return $this['env'] = (new EnvironmentDetector)->detect($callback, $args); } From a069cf17e4943fb88d2a91c92690004fb3236dab Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Nov 2024 09:39:14 -0600 Subject: [PATCH 003/158] version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 4ff5ca055037..779c35edd82a 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -38,7 +38,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '9.52.16'; + const VERSION = '9.52.17'; /** * The base path for the Laravel installation. From 83e64679d0688ee83b9224b60831dcf4b1a812f7 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Nov 2024 09:41:16 -0600 Subject: [PATCH 004/158] version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 643f85663642..8c6a32e98751 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '8.83.27'; + const VERSION = '8.83.28'; /** * The base path for the Laravel installation. From f1877e7f554625e9cf85202ffed025d482aa6c14 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Nov 2024 16:19:26 -0600 Subject: [PATCH 005/158] use isset --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 779c35edd82a..c28a1d07481f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -609,7 +609,7 @@ public function isProduction() */ public function detectEnvironment(Closure $callback) { - $args = $this->runningInConsole() && $_SERVER['argv'] + $args = $this->runningInConsole() && isset($_SERVER['argv']) ? $_SERVER['argv'] : null; From d60e9f38f2b918c53e725a054a0834df9ea56fef Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 20 Nov 2024 09:55:06 -0600 Subject: [PATCH 006/158] cloud env --- src/Illuminate/Http/Middleware/TrustProxies.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index faf5daf8db3c..bc756d4f829b 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -49,6 +49,12 @@ protected function setTrustedProxyIpAddresses(Request $request) { $trustedIps = $this->proxies() ?: config('trustedproxy.proxies'); + if (is_null($trustedIps) && + (($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || + ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1')) { + $trustedIps = '*'; + } + if ($trustedIps === '*' || $trustedIps === '**') { return $this->setTrustedProxyIpAddressesToTheCallingIp($request); } From 77a9ac53050c5128ddaa5bf0ec110e04dfc51166 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 20 Nov 2024 09:55:32 -0600 Subject: [PATCH 007/158] cloud env --- src/Illuminate/Http/Middleware/TrustProxies.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 872fd3ca2e71..3da466e6d6df 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -49,6 +49,12 @@ protected function setTrustedProxyIpAddresses(Request $request) { $trustedIps = $this->proxies() ?: config('trustedproxy.proxies'); + if (is_null($trustedIps) && + (($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || + ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1')) { + $trustedIps = '*'; + } + if ($trustedIps === '*' || $trustedIps === '**') { return $this->setTrustedProxyIpAddressesToTheCallingIp($request); } From d841a226a50c715431952a10260ba4fac9e91cc4 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 20 Nov 2024 09:55:41 -0600 Subject: [PATCH 008/158] version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 8c6a32e98751..477efa4c8f62 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '8.83.28'; + const VERSION = '8.83.29'; /** * The base path for the Laravel installation. From 41c812bf83e00d0d3f4b6963b0d475b26cb6fbf7 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 20 Nov 2024 09:56:00 -0600 Subject: [PATCH 009/158] version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index c28a1d07481f..3b311bdfcd21 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -38,7 +38,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '9.52.17'; + const VERSION = '9.52.18'; /** * The base path for the Laravel installation. From f132b23b13909cc22c615c01b0c5640541c3da0c Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 26 Nov 2024 15:32:57 +0000 Subject: [PATCH 010/158] Update version to v10.48.25 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index de5866760ab1..c1f2296fc84f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.24'; + const VERSION = '10.48.25'; /** * The base path for the Laravel installation. From 3dcbda210ddd755af269f26fab37ff14ff6e9e09 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 26 Nov 2024 15:34:40 +0000 Subject: [PATCH 011/158] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ba537c6a8f..4f46413a414d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.24...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.25...10.x) + +## [v10.48.25](https://github.com/laravel/framework/compare/v10.48.24...v10.48.25) - 2024-11-26 + +* [10.x] PHP 8.4 Code Compatibility by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53612 ## [v10.48.24](https://github.com/laravel/framework/compare/v10.48.23...v10.48.24) - 2024-11-20 From bd0e7cc096189e4fe37c827740b24bf5aa569898 Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Sun, 8 Dec 2024 16:36:59 +0100 Subject: [PATCH 012/158] Refine error messages for detecting lost connections (#53794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last year I added those two via https://github.com/laravel/framework/pull/47398 Please see details and reasoning there, it still applies; TL;DR: > Ever since I moved from AWS Aurora to using AWS RDS Proxy, which > (similar to pgbouncer) is essential to not overload the primary database > with connection, I sporadically see these messages. This affects > scheduled tasks, worker, HTTP requests; it's across the whole board. What I didn't knew back then was that the order/format of those messages very much dependent on the underlying libraries (libpq and or libssl presumably) and once we started to upgrade from Debian bullseye to bookworm, they changed and now are randomly interrupting our service again. The new (complete) messages look like this: > `SQLSTATE[08006] [7] connection to server at "…" (…), port 5… failed: SSL error: sslv3 alert unexpected message (Connection: main, SQL: …` That is, it still contains "SSL error: sslv3 alert unexpected message" but there's now connection specific information before the SQLSTATE prefix. Since lost connection detection is based on exact string matching and not regex, we're just removing the magic numbers in front and still keep backwards compatibility. I opened this PR against Laravel 10.x because we're still using and it requires the fix there (we're in the progress moving to L11, but it still takes time). Thanks --- src/Illuminate/Database/DetectsLostConnections.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 8cb1187a8bb7..445ee9dd66d3 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -64,8 +64,8 @@ protected function causedByLostConnection(Throwable $e) 'Reason: Server is in script upgrade mode. Only administrator can connect at this time.', 'Unknown $curl_error_code: 77', 'SSL: Handshake timed out', - 'SQLSTATE[08006] [7] SSL error: sslv3 alert unexpected message', - 'SQLSTATE[08006] [7] unrecognized SSL error code:', + 'SSL error: sslv3 alert unexpected message', + 'unrecognized SSL error code:', 'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it', 'SQLSTATE[HY000] [2002] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond', 'SQLSTATE[HY000] [2002] Network is unreachable', From d37ac782c6add5e6e124a0a80ac02e752f2c3154 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 9 Dec 2024 10:17:12 -0600 Subject: [PATCH 013/158] updates for cloud --- .../Console/Scheduling/CommandBuilder.php | 6 +++--- src/Illuminate/Console/Scheduling/Event.php | 6 +++++- src/Illuminate/Http/Middleware/TrustProxies.php | 4 +--- src/Illuminate/Support/helpers.php | 13 +++++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/CommandBuilder.php b/src/Illuminate/Console/Scheduling/CommandBuilder.php index ee13c5ee372d..17ad5522da92 100644 --- a/src/Illuminate/Console/Scheduling/CommandBuilder.php +++ b/src/Illuminate/Console/Scheduling/CommandBuilder.php @@ -32,9 +32,9 @@ protected function buildForegroundCommand(Event $event) { $output = ProcessUtils::escapeArgument($event->output); - return $this->ensureCorrectUser( - $event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1' - ); + return laravel_cloud() + ? $this->ensureCorrectUser($event, $event->command.' 2>&1 | tee '.($event->shouldAppendOutput ? '-a ' : '').$output) + : $this->ensureCorrectUser($event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1'); } /** diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 98fff2fec158..60b8b0521d41 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -289,7 +289,11 @@ protected function execute($container) { return Process::fromShellCommandline( $this->buildCommand(), base_path(), null, null, null - )->run(); + )->run( + laravel_cloud() + ? fn ($type, $line) => fwrite($type === 'out' ? STDOUT : STDERR, $line) + : fn () => true + ); } /** diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index cd5c63ecbd0e..c5a1d5dc1910 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -49,9 +49,7 @@ protected function setTrustedProxyIpAddresses(Request $request) { $trustedIps = $this->proxies() ?: config('trustedproxy.proxies'); - if (is_null($trustedIps) && - (($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || - ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1')) { + if (is_null($trustedIps) && laravel_cloud()) { $trustedIps = '*'; } diff --git a/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index cdcf52f1546a..bb53c8e5f302 100755 --- a/src/Illuminate/Support/helpers.php +++ b/src/Illuminate/Support/helpers.php @@ -152,6 +152,19 @@ function filled($value) } } +if (! function_exists('laravel_cloud')) { + /** + * Determine if the application is running on Laravel Cloud. + * + * @return bool + */ + function laravel_cloud() + { + return ($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || + ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1'; + } +} + if (! function_exists('object_get')) { /** * Get an item from an object using "dot" notation. From 3b8ed63a564773e3c337fbb140c377715b334013 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 9 Dec 2024 10:18:32 -0600 Subject: [PATCH 014/158] configure logging for cloud --- .../Foundation/Bootstrap/HandleExceptions.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 31ac7e49413b..59d77c0ca802 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -8,7 +8,9 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; use Illuminate\Support\Env; +use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; +use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; @@ -52,6 +54,10 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } + + if (laravel_cloud()) { + $this->configureCloudLogging($app); + } } /** @@ -244,6 +250,34 @@ protected function fatalErrorFromPhpError(array $error, $traceOffset = null) return new FatalError($error['message'], 0, $error, $traceOffset); } + /** + * Configure the Laravel Cloud log channels. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + protected function configureCloudLogging(Application $app) + { + $app['config']->set('logging.channels.stderr.formatter_with', [ + 'includeStacktraces' => true, + ]); + + $app['config']->set('logging.channels.laravel-cloud-socket', [ + 'driver' => 'monolog', + 'handler' => SocketHandler::class, + 'formatter' => JsonFormatter::class, + 'formatter_with' => [ + 'includeStacktraces' => true, + ], + 'with' => [ + 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? + $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? + 'unix:///tmp/cloud-init.sock', + 'persistent' => true, + ], + ]); + } + /** * Forward a method call to the given method if an application instance exists. * From 7bc897a38bac08cf008b3d234917ce6759828c53 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 9 Dec 2024 10:20:24 -0600 Subject: [PATCH 015/158] backport cloud fixes --- .../Console/Scheduling/CommandBuilder.php | 6 ++-- src/Illuminate/Console/Scheduling/Event.php | 6 +++- .../Foundation/Bootstrap/HandleExceptions.php | 34 +++++++++++++++++++ .../Http/Middleware/TrustProxies.php | 4 +-- src/Illuminate/Support/helpers.php | 13 +++++++ 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/CommandBuilder.php b/src/Illuminate/Console/Scheduling/CommandBuilder.php index ee13c5ee372d..17ad5522da92 100644 --- a/src/Illuminate/Console/Scheduling/CommandBuilder.php +++ b/src/Illuminate/Console/Scheduling/CommandBuilder.php @@ -32,9 +32,9 @@ protected function buildForegroundCommand(Event $event) { $output = ProcessUtils::escapeArgument($event->output); - return $this->ensureCorrectUser( - $event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1' - ); + return laravel_cloud() + ? $this->ensureCorrectUser($event, $event->command.' 2>&1 | tee '.($event->shouldAppendOutput ? '-a ' : '').$output) + : $this->ensureCorrectUser($event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1'); } /** diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 0ff10188b251..98a9641a3346 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -252,7 +252,11 @@ protected function execute($container) { return Process::fromShellCommandline( $this->buildCommand(), base_path(), null, null, null - )->run(); + )->run( + laravel_cloud() + ? fn ($type, $line) => fwrite($type === 'out' ? STDOUT : STDERR, $line) + : fn () => true + ); } /** diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 393be5c17866..3d9ca5a3c4df 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -7,7 +7,9 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; +use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; +use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; @@ -51,6 +53,10 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } + + if (laravel_cloud()) { + $this->configureCloudLogging($app); + } } /** @@ -259,6 +265,34 @@ protected function fatalErrorFromPhpError(array $error, $traceOffset = null) return new FatalError($error['message'], 0, $error, $traceOffset); } + /** + * Configure the Laravel Cloud log channels. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + protected function configureCloudLogging(Application $app) + { + $app['config']->set('logging.channels.stderr.formatter_with', [ + 'includeStacktraces' => true, + ]); + + $app['config']->set('logging.channels.laravel-cloud-socket', [ + 'driver' => 'monolog', + 'handler' => SocketHandler::class, + 'formatter' => JsonFormatter::class, + 'formatter_with' => [ + 'includeStacktraces' => true, + ], + 'with' => [ + 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? + $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? + 'unix:///tmp/cloud-init.sock', + 'persistent' => true, + ], + ]); + } + /** * Forward a method call to the given method if an application instance exists. * diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index bc756d4f829b..1fb4dd36cb87 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -49,9 +49,7 @@ protected function setTrustedProxyIpAddresses(Request $request) { $trustedIps = $this->proxies() ?: config('trustedproxy.proxies'); - if (is_null($trustedIps) && - (($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || - ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1')) { + if (is_null($trustedIps) && laravel_cloud()) { $trustedIps = '*'; } diff --git a/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index bb8d178f4889..c26e41726077 100755 --- a/src/Illuminate/Support/helpers.php +++ b/src/Illuminate/Support/helpers.php @@ -151,6 +151,19 @@ function filled($value) } } +if (! function_exists('laravel_cloud')) { + /** + * Determine if the application is running on Laravel Cloud. + * + * @return bool + */ + function laravel_cloud() + { + return ($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || + ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1'; + } +} + if (! function_exists('object_get')) { /** * Get an item from an object using "dot" notation. From eab1e939474458e7faa1f3c2f96a37a9b92b4d1e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 9 Dec 2024 10:27:06 -0600 Subject: [PATCH 016/158] backport cloud support --- .../Console/Scheduling/CommandBuilder.php | 6 ++-- src/Illuminate/Console/Scheduling/Event.php | 6 +++- .../Foundation/Bootstrap/HandleExceptions.php | 34 +++++++++++++++++++ .../Http/Middleware/TrustProxies.php | 4 +-- src/Illuminate/Support/helpers.php | 13 +++++++ 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/CommandBuilder.php b/src/Illuminate/Console/Scheduling/CommandBuilder.php index ee13c5ee372d..17ad5522da92 100644 --- a/src/Illuminate/Console/Scheduling/CommandBuilder.php +++ b/src/Illuminate/Console/Scheduling/CommandBuilder.php @@ -32,9 +32,9 @@ protected function buildForegroundCommand(Event $event) { $output = ProcessUtils::escapeArgument($event->output); - return $this->ensureCorrectUser( - $event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1' - ); + return laravel_cloud() + ? $this->ensureCorrectUser($event, $event->command.' 2>&1 | tee '.($event->shouldAppendOutput ? '-a ' : '').$output) + : $this->ensureCorrectUser($event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1'); } /** diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 4de88f163dbf..34e5a8524651 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -224,7 +224,11 @@ protected function runCommandInForeground(Container $container) $this->exitCode = Process::fromShellCommandline( $this->buildCommand(), base_path(), null, null, null - )->run(); + )->run( + laravel_cloud() + ? fn ($type, $line) => fwrite($type === 'out' ? STDOUT : STDERR, $line) + : fn () => true + ); $this->callAfterCallbacks($container); } finally { diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 286c2fec3510..24ab3e7416bb 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -7,7 +7,9 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; +use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; +use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; @@ -51,6 +53,10 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } + + if (laravel_cloud()) { + $this->configureCloudLogging($app); + } } /** @@ -244,6 +250,34 @@ protected function isFatal($type) return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]); } + /** + * Configure the Laravel Cloud log channels. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + protected function configureCloudLogging(Application $app) + { + $app['config']->set('logging.channels.stderr.formatter_with', [ + 'includeStacktraces' => true, + ]); + + $app['config']->set('logging.channels.laravel-cloud-socket', [ + 'driver' => 'monolog', + 'handler' => SocketHandler::class, + 'formatter' => JsonFormatter::class, + 'formatter_with' => [ + 'includeStacktraces' => true, + ], + 'with' => [ + 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? + $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? + 'unix:///tmp/cloud-init.sock', + 'persistent' => true, + ], + ]); + } + /** * Get an instance of the exception handler. * diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 3da466e6d6df..f1de7422b0a3 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -49,9 +49,7 @@ protected function setTrustedProxyIpAddresses(Request $request) { $trustedIps = $this->proxies() ?: config('trustedproxy.proxies'); - if (is_null($trustedIps) && - (($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || - ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1')) { + if (is_null($trustedIps) && laravel_cloud()) { $trustedIps = '*'; } diff --git a/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index 0b82fe76939d..a85aee2b1b62 100755 --- a/src/Illuminate/Support/helpers.php +++ b/src/Illuminate/Support/helpers.php @@ -146,6 +146,19 @@ function filled($value) } } +if (! function_exists('laravel_cloud')) { + /** + * Determine if the application is running on Laravel Cloud. + * + * @return bool + */ + function laravel_cloud() + { + return ($_ENV['LARAVEL_CLOUD'] ?? false) === '1' || + ($_SERVER['LARAVEL_CLOUD'] ?? false) === '1'; + } +} + if (! function_exists('object_get')) { /** * Get an item from an object using "dot" notation. From 9cc32b5de7c3a4a3a8e35f6ecf209c3ae9a58333 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 10 Dec 2024 22:47:55 +0800 Subject: [PATCH 017/158] [10.x] Bump minimum `league/commonmark` (#53829) Security fixes: https://github.com/advisories/GHSA-c2pc-g5qf-rfrf Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Mail/composer.json | 2 +- src/Illuminate/Support/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index a31bfc82feca..387c4f880e2a 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -20,7 +20,7 @@ "illuminate/contracts": "^10.0", "illuminate/macroable": "^10.0", "illuminate/support": "^10.0", - "league/commonmark": "^2.2", + "league/commonmark": "^2.6", "psr/log": "^1.0|^2.0|^3.0", "symfony/mailer": "^6.2", "tijsverkoyen/css-to-inline-styles": "^2.2.5" diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 57c92e366238..9d04374b9f97 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -44,7 +44,7 @@ }, "suggest": { "illuminate/filesystem": "Required to use the composer class (^10.0).", - "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", "symfony/process": "Required to use the composer class (^6.2).", "symfony/uid": "Required to use Str::ulid() (^6.2).", From eacb494cd16ad8f4b04387d716c1d78cfd1f20fb Mon Sep 17 00:00:00 2001 From: Tristan Payne Date: Mon, 6 Jan 2025 09:06:44 -0600 Subject: [PATCH 018/158] [10.x] Backport 11.x PHP 8.4 fix for str_getcsv deprecation (#54074) * Backport 11.x PHP 8.4 compat fix to str_getcsv * Fix indent --- src/Illuminate/Validation/ValidationRuleParser.php | 2 +- tests/Validation/ValidationUniqueRuleTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Validation/ValidationRuleParser.php b/src/Illuminate/Validation/ValidationRuleParser.php index 0469e0daa758..519a1f37640e 100644 --- a/src/Illuminate/Validation/ValidationRuleParser.php +++ b/src/Illuminate/Validation/ValidationRuleParser.php @@ -280,7 +280,7 @@ protected static function parseStringRule($rule) */ protected static function parseParameters($rule, $parameter) { - return static::ruleIsRegex($rule) ? [$parameter] : str_getcsv($parameter); + return static::ruleIsRegex($rule) ? [$parameter] : str_getcsv($parameter, escape: '\\'); } /** diff --git a/tests/Validation/ValidationUniqueRuleTest.php b/tests/Validation/ValidationUniqueRuleTest.php index 69212ab4063e..8b4c85054ca5 100644 --- a/tests/Validation/ValidationUniqueRuleTest.php +++ b/tests/Validation/ValidationUniqueRuleTest.php @@ -57,8 +57,8 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule->ignore('Taylor, Otwell"\'..-"', 'id_column'); $rule->where('foo', 'bar'); $this->assertSame('unique:table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"', (string) $rule); - $this->assertSame('Taylor, Otwell"\'..-"', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"')[2])); - $this->assertSame('id_column', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"')[3])); + $this->assertSame('Taylor, Otwell"\'..-"', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"', escape: '\\')[2])); + $this->assertSame('id_column', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"', escape: '\\')[3])); $rule = new Unique('table', 'column'); $rule->ignore(null, 'id_column'); From d1fc4b60c57184d3daf50c9715a46b5f2589babf Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 9 Jan 2025 16:56:56 -0800 Subject: [PATCH 019/158] only clear interrupt signal if we have repeatable commands --- src/Illuminate/Console/Scheduling/ScheduleRunCommand.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php index 917ff9a183b1..b914229af51b 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -112,12 +112,14 @@ public function handle(Schedule $schedule, Dispatcher $dispatcher, Cache $cache, $this->handler = $handler; $this->phpBinary = Application::phpBinary(); - $this->clearInterruptSignal(); - $this->newLine(); $events = $this->schedule->dueEvents($this->laravel); + if ($events->contains->isRepeatable()) { + $this->clearInterruptSignal(); + } + foreach ($events as $event) { if (! $event->filtersPass($this->laravel)) { $this->dispatcher->dispatch(new ScheduledTaskSkipped($event)); From 0948ecc2c47fdfb940a3d655b62d246ed1c6ad2a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 21 Jan 2025 10:06:22 -0600 Subject: [PATCH 020/158] update predis connector --- src/Illuminate/Redis/Connectors/PredisConnector.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Redis/Connectors/PredisConnector.php b/src/Illuminate/Redis/Connectors/PredisConnector.php index 8769fc53f535..50fc39462ce0 100644 --- a/src/Illuminate/Redis/Connectors/PredisConnector.php +++ b/src/Illuminate/Redis/Connectors/PredisConnector.php @@ -6,6 +6,7 @@ use Illuminate\Redis\Connections\PredisClusterConnection; use Illuminate\Redis\Connections\PredisConnection; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Predis\Client; class PredisConnector implements Connector @@ -27,6 +28,11 @@ public function connect(array $config, array $options) $formattedOptions['prefix'] = $config['prefix']; } + if (isset($config['host']) && str_starts_with($config['host'], 'tls://')) { + $config['scheme'] = 'tls'; + $config['host'] = Str::after($config['host'], 'tls://'); + } + return new PredisConnection(new Client($config, $formattedOptions)); } From befba4ae9d5185407f5bece9e30be1141962eb3c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 21 Jan 2025 10:06:45 -0600 Subject: [PATCH 021/158] update predis connector --- src/Illuminate/Redis/Connectors/PredisConnector.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Redis/Connectors/PredisConnector.php b/src/Illuminate/Redis/Connectors/PredisConnector.php index 6222a4b8e977..0252ccbdb9ce 100644 --- a/src/Illuminate/Redis/Connectors/PredisConnector.php +++ b/src/Illuminate/Redis/Connectors/PredisConnector.php @@ -6,6 +6,7 @@ use Illuminate\Redis\Connections\PredisClusterConnection; use Illuminate\Redis\Connections\PredisConnection; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Predis\Client; class PredisConnector implements Connector @@ -27,6 +28,11 @@ public function connect(array $config, array $options) $formattedOptions['prefix'] = $config['prefix']; } + if (isset($config['host']) && str_starts_with($config['host'], 'tls://')) { + $config['scheme'] = 'tls'; + $config['host'] = Str::after($config['host'], 'tls://'); + } + return new PredisConnection(new Client($config, $formattedOptions)); } From 0d244f0e7ef871b6d4efcacb396e155e6a13912c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 21 Jan 2025 10:07:36 -0600 Subject: [PATCH 022/158] update version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 3b311bdfcd21..3a0ccf14fdc4 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -38,7 +38,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '9.52.18'; + const VERSION = '9.52.19'; /** * The base path for the Laravel installation. From 4c04a91442cad2433cddb1d376e40d0a43ec9571 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:07:37 +0000 Subject: [PATCH 023/158] Update version to v10.48.26 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index c1f2296fc84f..d5e2b72e6cf1 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.25'; + const VERSION = '10.48.26'; /** * The base path for the Laravel installation. From 1fa6b1f18c981d3f45dc88ec2aea26148a2168b9 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:09:18 +0000 Subject: [PATCH 024/158] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f46413a414d..a7ef640c6f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.25...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.26...10.x) + +## [v10.48.26](https://github.com/laravel/framework/compare/v10.48.25...v10.48.26) - 2025-01-21 + +* [10.x] Refine error messages for detecting lost connections (Debian bookworm compatibility) by [@mfn](https://github.com/mfn) in https://github.com/laravel/framework/pull/53794 +* [10.x] Bump minimum `league/commonmark` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53829 +* [10.x] Backport 11.x PHP 8.4 fix for str_getcsv deprecation by [@aka-tpayne](https://github.com/aka-tpayne) in https://github.com/laravel/framework/pull/54074 ## [v10.48.25](https://github.com/laravel/framework/compare/v10.48.24...v10.48.25) - 2024-11-26 From c9c8a5a83ae3c8ad1c94702c6eb61fee8a13cb4f Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 24 Jan 2025 10:09:31 -0600 Subject: [PATCH 025/158] r2 backport --- src/Illuminate/Filesystem/FilesystemManager.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index d81b31ff6e91..1604cad8fd7b 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -316,6 +316,10 @@ protected function createFlysystem(FlysystemAdapter $adapter, array $config) $adapter = new PathPrefixedAdapter($adapter, $config['prefix']); } + if (str_contains($config['endpoint'] ?? '', 'r2.cloudflarestorage.com')) { + $config['retain_visibility'] = false; + } + return new Flysystem($adapter, Arr::only($config, [ 'directory_visibility', 'disable_asserts', From eb0be33e4b806b92f396357b99ffcb2d3ef67957 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:17:36 +0000 Subject: [PATCH 026/158] Update version to v10.48.27 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index d5e2b72e6cf1..a29a260d20a9 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.26'; + const VERSION = '10.48.27'; /** * The base path for the Laravel installation. From a39f4db06010683a46252677c3caa36b8f6dc707 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 31 Jan 2025 10:58:14 +0100 Subject: [PATCH 027/158] add cloud class --- src/Illuminate/Foundation/Cloud.php | 130 ++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/Illuminate/Foundation/Cloud.php diff --git a/src/Illuminate/Foundation/Cloud.php b/src/Illuminate/Foundation/Cloud.php new file mode 100644 index 000000000000..2bd1dffafc31 --- /dev/null +++ b/src/Illuminate/Foundation/Cloud.php @@ -0,0 +1,130 @@ + function () use ($app) { + static::configureDisks($app); + static::configureUnpooledPostgresConnection($app); + static::ensureMigrationsUseUnpooledConnection($app); + }, + HandleExceptions::class => function () use ($app) { + static::configureCloudLogging($app); + }, + default => fn () => true, + })(); + } + + /** + * Configure the Laravel Cloud disks if applicable. + */ + public static function configureDisks(Application $app): void + { + if (! isset($_SERVER['LARAVEL_CLOUD_DISK_CONFIG'])) { + return; + } + + $disks = json_decode($_SERVER['LARAVEL_CLOUD_DISK_CONFIG'], true); + + foreach ($disks as $disk) { + $app['config']->set('filesystems.disks.'.$disk['disk'], [ + 'driver' => 's3', + 'key' => $disk['access_key_id'], + 'secret' => $disk['access_key_secret'], + 'bucket' => $disk['bucket'], + 'url' => $disk['url'], + 'endpoint' => $disk['endpoint'], + 'region' => 'auto', + 'use_path_style_endpoint' => false, + 'throw' => false, + 'report' => false, + ]); + + if ($disk['is_default'] ?? false) { + $app['config']->set('filesystems.default', $disk['disk']); + } + } + } + + /** + * Configure the unpooled Laravel Postgres connection if applicable. + */ + public static function configureUnpooledPostgresConnection(Application $app): void + { + $host = $app['config']->get('database.connections.pgsql.host', ''); + + if (str_contains($host, 'pg.laravel.cloud') && + str_contains($host, '-pooler')) { + $app['config']->set( + 'database.connections.pgsql-unpooled', + array_merge($app['config']->get('database.connections.pgsql'), [ + 'host' => str_replace('-pooler', '', $host), + ]) + ); + } + } + + /** + * Ensure that migrations use the unpooled Postgres connection if applicable. + */ + public static function ensureMigrationsUseUnpooledConnection(Application $app): void + { + if (! is_array($app['config']->get('database.connections.pgsql-unpooled'))) { + return; + } + + Migrator::resolveConnectionsUsing(function ($resolver, $connection) use ($app) { + $connection = $connection ?? $app['config']->get('database.default'); + + return $resolver->connection( + $connection === 'pgsql' ? 'pgsql-unpooled' : $connection + ); + }); + } + + /** + * Configure the Laravel Cloud log channels. + */ + public static function configureCloudLogging(Application $app): void + { + $app['config']->set('logging.channels.stderr.formatter_with', [ + 'includeStacktraces' => true, + ]); + + $app['config']->set('logging.channels.laravel-cloud-socket', [ + 'driver' => 'monolog', + 'handler' => SocketHandler::class, + 'formatter' => JsonFormatter::class, + 'formatter_with' => [ + 'includeStacktraces' => true, + ], + 'with' => [ + 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? + $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? + 'unix:///tmp/cloud-init.sock', + 'persistent' => true, + ], + ]); + } +} From 7f427c4f34749ccaa2fded11ea42c0ba3c8f5436 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 31 Jan 2025 11:03:05 +0100 Subject: [PATCH 028/158] backport cloud support --- .../Database/Migrations/Migrator.php | 29 ++++++++++++++++- src/Illuminate/Foundation/Application.php | 23 +++++++++++++ .../Foundation/Bootstrap/HandleExceptions.php | 32 ------------------- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index ff18a26d60b5..6622d45fe0ee 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Migrations; +use Closure; use Doctrine\DBAL\Schema\SchemaException; use Illuminate\Console\View\Components\BulletList; use Illuminate\Console\View\Components\Error; @@ -52,6 +53,13 @@ class Migrator */ protected $resolver; + /** + * The custom connection resolver callback. + * + * @var \Closure|null + */ + protected static $connectionResolverCallback; + /** * The name of the default connection. * @@ -660,7 +668,26 @@ public function setConnection($name) */ public function resolveConnection($connection) { - return $this->resolver->connection($connection ?: $this->connection); + if (static::$connectionResolverCallback) { + return call_user_func( + static::$connectionResolverCallback, + $this->resolver, + $connection ?: $this->connection + ); + } else { + return $this->resolver->connection($connection ?: $this->connection); + } + } + + /** + * Set a connection resolver callback. + * + * @param \Closure $callback + * @return void + */ + public static function resolveConnectionsUsing(Closure $callback) + { + static::$connectionResolverCallback = $callback; } /** diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index a29a260d20a9..518494df9d5f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -204,6 +204,7 @@ public function __construct($basePath = null) $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); + $this->registerLaravelCloudServices(); } /** @@ -247,6 +248,28 @@ protected function registerBaseServiceProviders() $this->register(new RoutingServiceProvider($this)); } + /** + * Register any services needed for Laravel Cloud. + * + * @return void + */ + protected function registerLaravelCloudServices() + { + if (! laravel_cloud()) { + return; + } + + $this['events']->listen( + 'bootstrapping: *', + fn ($bootstrapper) => Cloud::bootstrapperBootstrapping($this, Str::after($bootstrapper, 'bootstrapping: ')) + ); + + $this['events']->listen( + 'bootstrapped: *', + fn ($bootstrapper) => Cloud::bootstrapperBootstrapped($this, Str::after($bootstrapper, 'bootstrapped: ')) + ); + } + /** * Run the given array of bootstrap classes. * diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 59d77c0ca802..2898ec584b91 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -54,10 +54,6 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } - - if (laravel_cloud()) { - $this->configureCloudLogging($app); - } } /** @@ -250,34 +246,6 @@ protected function fatalErrorFromPhpError(array $error, $traceOffset = null) return new FatalError($error['message'], 0, $error, $traceOffset); } - /** - * Configure the Laravel Cloud log channels. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @return void - */ - protected function configureCloudLogging(Application $app) - { - $app['config']->set('logging.channels.stderr.formatter_with', [ - 'includeStacktraces' => true, - ]); - - $app['config']->set('logging.channels.laravel-cloud-socket', [ - 'driver' => 'monolog', - 'handler' => SocketHandler::class, - 'formatter' => JsonFormatter::class, - 'formatter_with' => [ - 'includeStacktraces' => true, - ], - 'with' => [ - 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? - $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? - 'unix:///tmp/cloud-init.sock', - 'persistent' => true, - ], - ]); - } - /** * Forward a method call to the given method if an application instance exists. * From 62cb852a08e2a4c2c849291ea2063962f9a85abf Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 31 Jan 2025 10:03:32 +0000 Subject: [PATCH 029/158] Apply fixes from StyleCI --- src/Illuminate/Foundation/Bootstrap/HandleExceptions.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 2898ec584b91..31ac7e49413b 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -8,9 +8,7 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; use Illuminate\Support\Env; -use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; -use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; From e714e7e0c1ae51bf747e3df5b10fa60c54e3e0e1 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:04:17 +0000 Subject: [PATCH 030/158] Update version to v10.48.28 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 518494df9d5f..cfa7de7531ff 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.27'; + const VERSION = '10.48.28'; /** * The base path for the Laravel installation. From dd5c90d39a7a0bbb1e9a5fdb8931806d2fef4e73 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:05:57 +0000 Subject: [PATCH 031/158] Update CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ef640c6f7c..d2504871aece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.26...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.28...10.x) + +## [v10.48.28](https://github.com/laravel/framework/compare/v10.48.26...v10.48.28) - 2025-01-31 ## [v10.48.26](https://github.com/laravel/framework/compare/v10.48.25...v10.48.26) - 2025-01-21 From 2bb6835af73fcf0d1d0bfb84af71cef236cb8609 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 31 Jan 2025 11:09:38 +0100 Subject: [PATCH 032/158] backport cloud stuff --- .../Database/Migrations/Migrator.php | 29 +++- src/Illuminate/Foundation/Application.php | 25 +++- .../Foundation/Bootstrap/HandleExceptions.php | 32 ----- src/Illuminate/Foundation/Cloud.php | 130 ++++++++++++++++++ 4 files changed, 182 insertions(+), 34 deletions(-) create mode 100644 src/Illuminate/Foundation/Cloud.php diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index 307fc2ff0b28..eb68437731eb 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Migrations; +use Closure; use Doctrine\DBAL\Schema\SchemaException; use Illuminate\Console\View\Components\BulletList; use Illuminate\Console\View\Components\Error; @@ -52,6 +53,13 @@ class Migrator */ protected $resolver; + /** + * The custom connection resolver callback. + * + * @var \Closure|null + */ + protected static $connectionResolverCallback; + /** * The name of the default connection. * @@ -655,7 +663,26 @@ public function setConnection($name) */ public function resolveConnection($connection) { - return $this->resolver->connection($connection ?: $this->connection); + if (static::$connectionResolverCallback) { + return call_user_func( + static::$connectionResolverCallback, + $this->resolver, + $connection ?: $this->connection + ); + } else { + return $this->resolver->connection($connection ?: $this->connection); + } + } + + /** + * Set a connection resolver callback. + * + * @param \Closure $callback + * @return void + */ + public static function resolveConnectionsUsing(Closure $callback) + { + static::$connectionResolverCallback = $callback; } /** diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 3a0ccf14fdc4..8c28974ab5ea 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -38,7 +38,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '9.52.19'; + const VERSION = '9.52.20'; /** * The base path for the Laravel installation. @@ -181,6 +181,7 @@ public function __construct($basePath = null) $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); + $this->registerLaravelCloudServices(); } /** @@ -226,6 +227,28 @@ protected function registerBaseServiceProviders() $this->register(new RoutingServiceProvider($this)); } + /** + * Register any services needed for Laravel Cloud. + * + * @return void + */ + protected function registerLaravelCloudServices() + { + if (! laravel_cloud()) { + return; + } + + $this['events']->listen( + 'bootstrapping: *', + fn ($bootstrapper) => Cloud::bootstrapperBootstrapping($this, Str::after($bootstrapper, 'bootstrapping: ')) + ); + + $this['events']->listen( + 'bootstrapped: *', + fn ($bootstrapper) => Cloud::bootstrapperBootstrapped($this, Str::after($bootstrapper, 'bootstrapped: ')) + ); + } + /** * Run the given array of bootstrap classes. * diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 3d9ca5a3c4df..7021cbb703d7 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -53,10 +53,6 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } - - if (laravel_cloud()) { - $this->configureCloudLogging($app); - } } /** @@ -265,34 +261,6 @@ protected function fatalErrorFromPhpError(array $error, $traceOffset = null) return new FatalError($error['message'], 0, $error, $traceOffset); } - /** - * Configure the Laravel Cloud log channels. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @return void - */ - protected function configureCloudLogging(Application $app) - { - $app['config']->set('logging.channels.stderr.formatter_with', [ - 'includeStacktraces' => true, - ]); - - $app['config']->set('logging.channels.laravel-cloud-socket', [ - 'driver' => 'monolog', - 'handler' => SocketHandler::class, - 'formatter' => JsonFormatter::class, - 'formatter_with' => [ - 'includeStacktraces' => true, - ], - 'with' => [ - 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? - $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? - 'unix:///tmp/cloud-init.sock', - 'persistent' => true, - ], - ]); - } - /** * Forward a method call to the given method if an application instance exists. * diff --git a/src/Illuminate/Foundation/Cloud.php b/src/Illuminate/Foundation/Cloud.php new file mode 100644 index 000000000000..2bd1dffafc31 --- /dev/null +++ b/src/Illuminate/Foundation/Cloud.php @@ -0,0 +1,130 @@ + function () use ($app) { + static::configureDisks($app); + static::configureUnpooledPostgresConnection($app); + static::ensureMigrationsUseUnpooledConnection($app); + }, + HandleExceptions::class => function () use ($app) { + static::configureCloudLogging($app); + }, + default => fn () => true, + })(); + } + + /** + * Configure the Laravel Cloud disks if applicable. + */ + public static function configureDisks(Application $app): void + { + if (! isset($_SERVER['LARAVEL_CLOUD_DISK_CONFIG'])) { + return; + } + + $disks = json_decode($_SERVER['LARAVEL_CLOUD_DISK_CONFIG'], true); + + foreach ($disks as $disk) { + $app['config']->set('filesystems.disks.'.$disk['disk'], [ + 'driver' => 's3', + 'key' => $disk['access_key_id'], + 'secret' => $disk['access_key_secret'], + 'bucket' => $disk['bucket'], + 'url' => $disk['url'], + 'endpoint' => $disk['endpoint'], + 'region' => 'auto', + 'use_path_style_endpoint' => false, + 'throw' => false, + 'report' => false, + ]); + + if ($disk['is_default'] ?? false) { + $app['config']->set('filesystems.default', $disk['disk']); + } + } + } + + /** + * Configure the unpooled Laravel Postgres connection if applicable. + */ + public static function configureUnpooledPostgresConnection(Application $app): void + { + $host = $app['config']->get('database.connections.pgsql.host', ''); + + if (str_contains($host, 'pg.laravel.cloud') && + str_contains($host, '-pooler')) { + $app['config']->set( + 'database.connections.pgsql-unpooled', + array_merge($app['config']->get('database.connections.pgsql'), [ + 'host' => str_replace('-pooler', '', $host), + ]) + ); + } + } + + /** + * Ensure that migrations use the unpooled Postgres connection if applicable. + */ + public static function ensureMigrationsUseUnpooledConnection(Application $app): void + { + if (! is_array($app['config']->get('database.connections.pgsql-unpooled'))) { + return; + } + + Migrator::resolveConnectionsUsing(function ($resolver, $connection) use ($app) { + $connection = $connection ?? $app['config']->get('database.default'); + + return $resolver->connection( + $connection === 'pgsql' ? 'pgsql-unpooled' : $connection + ); + }); + } + + /** + * Configure the Laravel Cloud log channels. + */ + public static function configureCloudLogging(Application $app): void + { + $app['config']->set('logging.channels.stderr.formatter_with', [ + 'includeStacktraces' => true, + ]); + + $app['config']->set('logging.channels.laravel-cloud-socket', [ + 'driver' => 'monolog', + 'handler' => SocketHandler::class, + 'formatter' => JsonFormatter::class, + 'formatter_with' => [ + 'includeStacktraces' => true, + ], + 'with' => [ + 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? + $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? + 'unix:///tmp/cloud-init.sock', + 'persistent' => true, + ], + ]); + } +} From c237907ad870e3a3804030e8080098b66b2f1b75 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 31 Jan 2025 10:09:57 +0000 Subject: [PATCH 033/158] Apply fixes from StyleCI --- src/Illuminate/Foundation/Bootstrap/HandleExceptions.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 7021cbb703d7..393be5c17866 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -7,9 +7,7 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; -use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; -use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; From fc47dcac927dc76eac2f4cab304fedb00a2dbe50 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 11 Feb 2025 16:14:41 -0600 Subject: [PATCH 034/158] backport emulate prepares --- src/Illuminate/Foundation/Cloud.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Illuminate/Foundation/Cloud.php b/src/Illuminate/Foundation/Cloud.php index 2bd1dffafc31..046025fabe22 100644 --- a/src/Illuminate/Foundation/Cloud.php +++ b/src/Illuminate/Foundation/Cloud.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Bootstrap\LoadConfiguration; use Monolog\Formatter\JsonFormatter; use Monolog\Handler\SocketHandler; +use PDO; class Cloud { @@ -82,6 +83,14 @@ public static function configureUnpooledPostgresConnection(Application $app): vo 'host' => str_replace('-pooler', '', $host), ]) ); + + $app['config']->set( + 'database.connections.pgsql.options', + array_merge( + $app['config']->get('database.connections.pgsql.options', []), + [PDO::ATTR_EMULATE_PREPARES => true], + ), + ); } } From 8046e4ee017a4cdeb51f304ea30029f0ca263190 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 11 Feb 2025 16:15:00 -0600 Subject: [PATCH 035/158] backport emulate prepares --- src/Illuminate/Foundation/Cloud.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Illuminate/Foundation/Cloud.php b/src/Illuminate/Foundation/Cloud.php index 2bd1dffafc31..046025fabe22 100644 --- a/src/Illuminate/Foundation/Cloud.php +++ b/src/Illuminate/Foundation/Cloud.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Bootstrap\LoadConfiguration; use Monolog\Formatter\JsonFormatter; use Monolog\Handler\SocketHandler; +use PDO; class Cloud { @@ -82,6 +83,14 @@ public static function configureUnpooledPostgresConnection(Application $app): vo 'host' => str_replace('-pooler', '', $host), ]) ); + + $app['config']->set( + 'database.connections.pgsql.options', + array_merge( + $app['config']->get('database.connections.pgsql.options', []), + [PDO::ATTR_EMULATE_PREPARES => true], + ), + ); } } From a4f7a8f9b83e21882abeef78c3174c66b0f4a26b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 11 Mar 2025 01:30:26 +0800 Subject: [PATCH 036/158] [10.x] Fix attribute name used on `Validator` instance within certain rule classes (#54943) Backport PR #54845 to Laravel 10 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Validation/Validator.php | 38 ++++++++----- .../Validation/Rules/FileValidationTest.php | 54 +++++++++++++++++++ .../Rules/PasswordValidationTest.php | 49 +++++++++++++++++ 3 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 tests/Integration/Validation/Rules/FileValidationTest.php create mode 100644 tests/Integration/Validation/Rules/PasswordValidationTest.php diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 91d3b6a2e67c..8e9d7333de03 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -302,11 +302,11 @@ class Validator implements ValidatorContract protected $defaultNumericRules = ['Numeric', 'Integer', 'Decimal']; /** - * The current placeholder for dots in rule keys. + * The current random hash for the validator. * * @var string */ - protected $dotPlaceholder; + protected static $placeholderHash; /** * The exception to throw upon failure. @@ -335,7 +335,9 @@ class Validator implements ValidatorContract public function __construct(Translator $translator, array $data, array $rules, array $messages = [], array $attributes = []) { - $this->dotPlaceholder = Str::random(); + if (! isset(static::$placeholderHash)) { + static::$placeholderHash = Str::random(); + } $this->initialRules = $rules; $this->translator = $translator; @@ -363,7 +365,7 @@ public function parseData(array $data) $key = str_replace( ['.', '*'], - [$this->dotPlaceholder, '__asterisk__'], + ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash], $key ); @@ -401,7 +403,7 @@ protected function replacePlaceholders($data) protected function replacePlaceholderInString(string $value) { return str_replace( - [$this->dotPlaceholder, '__asterisk__'], + ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash], ['.', '*'], $value ); @@ -720,7 +722,7 @@ protected function getPrimaryAttribute($attribute) protected function replaceDotInParameters(array $parameters) { return array_map(function ($field) { - return str_replace('\.', $this->dotPlaceholder, $field); + return str_replace('\.', '__dot__'.static::$placeholderHash, $field); }, $parameters); } @@ -846,11 +848,23 @@ protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute) */ protected function validateUsingCustomRule($attribute, $value, $rule) { - $attribute = $this->replacePlaceholderInString($attribute); + $originalAttribute = $this->replacePlaceholderInString($attribute); + + $attribute = match (true) { + $rule instanceof Rules\File => $attribute, + $rule instanceof Rules\Password => $attribute, + default => $originalAttribute, + }; $value = is_array($value) ? $this->replacePlaceholders($value) : $value; if ($rule instanceof ValidatorAwareRule) { + if ($attribute !== $originalAttribute) { + $this->addCustomAttributes([ + $attribute => $this->customAttributes[$originalAttribute] ?? $originalAttribute, + ]); + } + $rule->setValidator($this); } @@ -863,14 +877,14 @@ protected function validateUsingCustomRule($attribute, $value, $rule) get_class($rule->invokable()) : get_class($rule); - $this->failedRules[$attribute][$ruleClass] = []; + $this->failedRules[$originalAttribute][$ruleClass] = []; - $messages = $this->getFromLocalArray($attribute, $ruleClass) ?? $rule->message(); + $messages = $this->getFromLocalArray($originalAttribute, $ruleClass) ?? $rule->message(); $messages = $messages ? (array) $messages : [$ruleClass]; foreach ($messages as $key => $message) { - $key = is_string($key) ? $key : $attribute; + $key = is_string($key) ? $key : $originalAttribute; $this->messages->add($key, $this->makeReplacements( $message, $key, $ruleClass, [] @@ -1159,7 +1173,7 @@ public function getRulesWithoutPlaceholders() { return collect($this->rules) ->mapWithKeys(fn ($value, $key) => [ - str_replace($this->dotPlaceholder, '\\.', $key) => $value, + str_replace('__dot__'.static::$placeholderHash, '\\.', $key) => $value, ]) ->all(); } @@ -1173,7 +1187,7 @@ public function getRulesWithoutPlaceholders() public function setRules(array $rules) { $rules = collect($rules)->mapWithKeys(function ($value, $key) { - return [str_replace('\.', $this->dotPlaceholder, $key) => $value]; + return [str_replace('\.', '__dot__'.static::$placeholderHash, $key) => $value]; })->toArray(); $this->initialRules = $rules; diff --git a/tests/Integration/Validation/Rules/FileValidationTest.php b/tests/Integration/Validation/Rules/FileValidationTest.php new file mode 100644 index 000000000000..ba0d54920e46 --- /dev/null +++ b/tests/Integration/Validation/Rules/FileValidationTest.php @@ -0,0 +1,54 @@ +create('laravel.png', 1, 'image/png'); + + $validator = Validator::make([ + 'files' => [ + $attribute => $file, + ], + ], [ + 'files.*' => ['required', File::types(['image/png', 'image/jpeg'])], + ]); + + $this->assertTrue($validator->passes()); + } + + #[TestWith(['0'])] + #[TestWith(['.'])] + #[TestWith(['*'])] + #[TestWith(['__asterisk__'])] + public function test_it_can_validate_attribute_as_array_when_validation_should_fails(string $attribute) + { + $file = UploadedFile::fake()->create('laravel.php', 1, 'image/php'); + + $validator = Validator::make([ + 'files' => [ + $attribute => $file, + ], + ], [ + 'files.*' => ['required', File::types($mimes = ['image/png', 'image/jpeg'])], + ]); + + $this->assertFalse($validator->passes()); + + $this->assertSame([ + 0 => __('validation.mimetypes', ['attribute' => sprintf('files.%s', str_replace('_', ' ', $attribute)), 'values' => implode(', ', $mimes)]), + ], $validator->messages()->all()); + } +} diff --git a/tests/Integration/Validation/Rules/PasswordValidationTest.php b/tests/Integration/Validation/Rules/PasswordValidationTest.php new file mode 100644 index 000000000000..e1f7672ac89b --- /dev/null +++ b/tests/Integration/Validation/Rules/PasswordValidationTest.php @@ -0,0 +1,49 @@ + [ + $attribute => 'secret', + ], + ], [ + 'passwords.*' => ['required', Password::default()->min(6)], + ]); + + $this->assertTrue($validator->passes()); + } + + #[TestWith(['0'])] + #[TestWith(['.'])] + #[TestWith(['*'])] + #[TestWith(['__asterisk__'])] + public function test_it_can_validate_attribute_as_array_when_validation_should_fails(string $attribute) + { + $validator = Validator::make([ + 'passwords' => [ + $attribute => 'secret', + ], + ], [ + 'passwords.*' => ['required', Password::default()->min(8)], + ]); + + $this->assertFalse($validator->passes()); + + $this->assertSame([ + 0 => sprintf('The passwords.%s field must be at least 8 characters.', str_replace('_', ' ', $attribute)), + ], $validator->messages()->all()); + } +} From f85216c82cbd38b66d67ebd20ea762cb3751a4b4 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:34:30 +0000 Subject: [PATCH 037/158] Update version to v11.44.2 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 7dbfa672041e..be7345c8ef28 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.1'; + const VERSION = '11.44.2'; /** * The base path for the Laravel installation. From 88bdf4763998eb4d0ceddb85b41c2ed5eeb348c9 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:36:14 +0000 Subject: [PATCH 038/158] Update CHANGELOG --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4c7a4c7be4..2bfd4dc1e9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.1...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.2...11.x) + +## [v11.44.2](https://github.com/laravel/framework/compare/v11.44.1...v11.44.2) - 2025-03-12 + +* [11.x] Fix double negative in `whereNotMorphedTo()` query by [@owenvoke](https://github.com/owenvoke) in https://github.com/laravel/framework/pull/54902 +* [11.x] Backport "Change `paginate()` method return types to `\Illuminate\Pagination\LengthAwarePaginator`" by [@carestad](https://github.com/carestad) in https://github.com/laravel/framework/pull/54917 +* [11.x] Revert faulty change to `EnumeratesValues::ensure()` doc block by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/54919 +* Ensure ValidationEmailRuleTest skips tests requiring the intl extension when unavailable by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54918 +* [11.x] Backport "Fix issue with using `RedisCluster` with compression or serialization" by [@rzv-me](https://github.com/rzv-me) in https://github.com/laravel/framework/pull/54935 +* [11.x] Fix callOnce in Seeder so it handles arrays properly by [@lbovit](https://github.com/lbovit) in https://github.com/laravel/framework/pull/54985 ## [v11.44.1](https://github.com/laravel/framework/compare/v11.44.0...v11.44.1) - 2025-03-05 From 8f7f9247cb8aad1a769d6b9815a6623d89b46b47 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 12 Mar 2025 09:42:01 -0500 Subject: [PATCH 039/158] version --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index cfa7de7531ff..2d925a6eb43e 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.28'; + const VERSION = '10.48.29'; /** * The base path for the Laravel installation. From d7434ea1024241a80aa29e0f0188da193a5814be Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 19 Mar 2025 02:13:00 +0800 Subject: [PATCH 040/158] Add `Illuminate\Support\EncodedHtmlString` (#54737) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update EncodedHtmlString.php * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * Update EncodedHtmlString.php * Update Markdown.php * Update src/Illuminate/Mail/Markdown.php Co-authored-by: Sebastian Hädrich <11225821+shaedrich@users.noreply.github.com> * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * formatting * formatting " --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Sebastian Hädrich <11225821+shaedrich@users.noreply.github.com> Co-authored-by: Taylor Otwell --- .../InteractsWithTestCaseLifecycle.php | 2 + src/Illuminate/Mail/Mailable.php | 9 ++- src/Illuminate/Mail/Markdown.php | 59 ++++++++++++-- src/Illuminate/Support/EncodedHtmlString.php | 76 +++++++++++++++++++ .../View/Compilers/BladeCompiler.php | 22 ++++++ tests/Integration/Mail/MailableTest.php | 72 ++++++++++++++++++ tests/Integration/Mail/MarkdownParserTest.php | 74 ++++++++++++++++++ tests/Mail/MailMarkdownTest.php | 26 +++++++ 8 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 src/Illuminate/Support/EncodedHtmlString.php create mode 100644 tests/Integration/Mail/MailableTest.php create mode 100644 tests/Integration/Mail/MarkdownParserTest.php diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php index 4be085daa39c..4b81d3c4d45d 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -26,6 +26,7 @@ use Illuminate\Queue\Console\WorkCommand; use Illuminate\Queue\Queue; use Illuminate\Support\Carbon; +use Illuminate\Support\EncodedHtmlString; use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\ParallelTesting; use Illuminate\Support\Once; @@ -171,6 +172,7 @@ protected function tearDownTheTestEnvironment(): void Component::forgetFactory(); ConvertEmptyStringsToNull::flushState(); Factory::flushState(); + EncodedHtmlString::flushState(); EncryptCookies::flushState(); HandleExceptions::flushState(); Migrator::withoutMigrations([]); diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index f517b803dce6..1896dc0b0252 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -13,6 +13,7 @@ use Illuminate\Contracts\Support\Renderable; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Support\Collection; +use Illuminate\Support\EncodedHtmlString; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; use Illuminate\Support\Traits\Conditionable; @@ -1371,7 +1372,7 @@ public function assertHasSubject($subject) */ public function assertSeeInHtml($string, $escape = true) { - $string = $escape ? e($string) : $string; + $string = $escape ? EncodedHtmlString::convert($string, withQuote: isset($this->markdown)) : $string; [$html, $text] = $this->renderForAssertions(); @@ -1393,7 +1394,7 @@ public function assertSeeInHtml($string, $escape = true) */ public function assertDontSeeInHtml($string, $escape = true) { - $string = $escape ? e($string) : $string; + $string = $escape ? EncodedHtmlString::convert($string, withQuote: isset($this->markdown)) : $string; [$html, $text] = $this->renderForAssertions(); @@ -1415,7 +1416,9 @@ public function assertDontSeeInHtml($string, $escape = true) */ public function assertSeeInOrderInHtml($strings, $escape = true) { - $strings = $escape ? array_map('e', $strings) : $strings; + $strings = $escape ? array_map(function ($string) { + return EncodedHtmlString::convert($string, withQuote: isset($this->markdown)); + }, $strings) : $strings; [$html, $text] = $this->renderForAssertions(); diff --git a/src/Illuminate/Mail/Markdown.php b/src/Illuminate/Mail/Markdown.php index 8faf739eb393..e8ec2defc4c4 100644 --- a/src/Illuminate/Mail/Markdown.php +++ b/src/Illuminate/Mail/Markdown.php @@ -3,6 +3,7 @@ namespace Illuminate\Mail; use Illuminate\Contracts\View\Factory as ViewFactory; +use Illuminate\Support\EncodedHtmlString; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; use League\CommonMark\Environment\Environment; @@ -60,9 +61,19 @@ public function render($view, array $data = [], $inliner = null) { $this->view->flushFinderCache(); - $contents = $this->view->replaceNamespace( - 'mail', $this->htmlComponentPaths() - )->make($view, $data)->render(); + $bladeCompiler = $this->view + ->getEngineResolver() + ->resolve('blade') + ->getCompiler(); + + $contents = $bladeCompiler->usingEchoFormat( + 'new \Illuminate\Support\EncodedHtmlString(%s)', + function () use ($view, $data) { + return $this->view->replaceNamespace( + 'mail', $this->htmlComponentPaths() + )->make($view, $data)->render(); + } + ); if ($this->view->exists($customTheme = Str::start($this->theme, 'mail.'))) { $theme = $customTheme; @@ -105,16 +116,48 @@ public function renderText($view, array $data = []) */ public static function parse($text) { - $environment = new Environment([ + EncodedHtmlString::encodeUsing(function ($value) { + $replacements = [ + '[' => '\[', + '<' => '\<', + ]; + + $html = str_replace(array_keys($replacements), array_values($replacements), $value); + + return static::converter([ + 'html_input' => 'escape', + ])->convert($html)->getContent(); + }); + + $html = ''; + + try { + $html = static::converter()->convert($text)->getContent(); + } finally { + EncodedHtmlString::flushState(); + } + + return new HtmlString($html); + } + + /** + * Get a Markdown converter instance. + * + * @internal + * + * @param array $config + * @return \League\CommonMark\MarkdownConverter + */ + public static function converter(array $config = []) + { + $environment = new Environment(array_merge([ 'allow_unsafe_links' => false, - ]); + ], $config)); $environment->addExtension(new CommonMarkCoreExtension); $environment->addExtension(new TableExtension); - $converter = new MarkdownConverter($environment); - - return new HtmlString($converter->convert($text)->getContent()); + return new MarkdownConverter($environment); } /** diff --git a/src/Illuminate/Support/EncodedHtmlString.php b/src/Illuminate/Support/EncodedHtmlString.php new file mode 100644 index 000000000000..18928e75b633 --- /dev/null +++ b/src/Illuminate/Support/EncodedHtmlString.php @@ -0,0 +1,76 @@ +html, $this->doubleEncode); + } + + /** + * Set the callable that will be used to encode the HTML strings. + * + * @param callable|null $factory + * @return void + */ + public static function encodeUsing(?callable $factory = null) + { + static::$encodeUsingFactory = $factory; + } + + /** + * Flush the class's global state. + * + * @return void + */ + public static function flushState() + { + static::$encodeUsingFactory = null; + } +} diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php index bd739b90a5d9..ca46c5a4158d 100644 --- a/src/Illuminate/View/Compilers/BladeCompiler.php +++ b/src/Illuminate/View/Compilers/BladeCompiler.php @@ -1003,6 +1003,28 @@ public function precompiler(callable $precompiler) $this->precompilers[] = $precompiler; } + /** + * Execute the given callback using a custom echo format. + * + * @param string $format + * @param callable $callback + * @return string + */ + public function usingEchoFormat($format, callable $callback) + { + $originalEchoFormat = $this->echoFormat; + + $this->setEchoFormat($format); + + try { + $output = call_user_func($callback); + } finally { + $this->setEchoFormat($originalEchoFormat); + } + + return $output; + } + /** * Set the echo format to be used by the compiler. * diff --git a/tests/Integration/Mail/MailableTest.php b/tests/Integration/Mail/MailableTest.php new file mode 100644 index 000000000000..339ebb2422d7 --- /dev/null +++ b/tests/Integration/Mail/MailableTest.php @@ -0,0 +1,72 @@ +addLocation(__DIR__.'/Fixtures'); + } + + #[DataProvider('markdownEncodedDataProvider')] + public function testItCanAssertMarkdownEncodedString($given, $expected) + { + $mailable = new class($given) extends Mailable + { + public function __construct(public string $message) + { + // + } + + public function envelope() + { + return new Envelope( + subject: 'My basic title', + ); + } + + public function content() + { + return new Content( + markdown: 'message', + ); + } + }; + + $mailable->assertSeeInHtml($expected, false); + } + + public static function markdownEncodedDataProvider() + { + yield ['[Laravel](https://laravel.com)', 'My message is: [Laravel](https://laravel.com)']; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + 'My message is: ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit https://laravel.com/docs to browse the documentation', + ]; + + yield [ + 'Visit to browse the documentation', + 'My message is: Visit <https://laravel.com/docs> to browse the documentation', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', + ]; + } +} diff --git a/tests/Integration/Mail/MarkdownParserTest.php b/tests/Integration/Mail/MarkdownParserTest.php new file mode 100644 index 000000000000..d21602c9ad00 --- /dev/null +++ b/tests/Integration/Mail/MarkdownParserTest.php @@ -0,0 +1,74 @@ +assertInstanceOf(HtmlString::class, $html); + + $this->assertStringEqualsStringIgnoringLineEndings($expected.PHP_EOL, (string) $html); + $this->assertSame((string) $html, (string) $html->toHtml()); + }); + } + + #[DataProvider('markdownEncodedDataProvider')] + public function testItCanParseMarkdownEncodedString($given, $expected) + { + tap(Markdown::parse($given), function ($html) use ($expected) { + $this->assertInstanceOf(HtmlString::class, $html); + + $this->assertStringEqualsStringIgnoringLineEndings($expected.PHP_EOL, (string) $html); + }); + } + + public static function markdownDataProvider() + { + yield ['[Laravel](https://laravel.com)', '

Laravel

']; + yield ['\[Laravel](https://laravel.com)', '

[Laravel](https://laravel.com)

']; + yield ['![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', '

Welcome to Laravel

']; + yield ['!\[Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', '

![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)

']; + yield ['Visit https://laravel.com/docs to browse the documentation', '

Visit https://laravel.com/docs to browse the documentation

']; + yield ['Visit to browse the documentation', '

Visit https://laravel.com/docs to browse the documentation

']; + yield ['Visit https://laravel.com/docs to browse the documentation', '

Visit https://laravel.com/docs to browse the documentation

']; + } + + public static function markdownEncodedDataProvider() + { + yield [new EncodedHtmlString('[Laravel](https://laravel.com)'), '

[Laravel](https://laravel.com)

']; + + yield [ + new EncodedHtmlString('![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)'), + '

![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)

', + ]; + + yield [ + new EncodedHtmlString('Visit https://laravel.com/docs to browse the documentation'), + '

Visit https://laravel.com/docs to browse the documentation

', + ]; + + yield [ + new EncodedHtmlString('Visit to browse the documentation'), + '

Visit <https://laravel.com/docs> to browse the documentation

', + ]; + + yield [ + new EncodedHtmlString('Visit https://laravel.com/docs to browse the documentation'), + '

Visit <span>https://laravel.com/docs</span> to browse the documentation

', + ]; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)
'.new EncodedHtmlString('Visit https://laravel.com/docs to browse the documentation'), + '

Welcome to Laravel
Visit <span>https://laravel.com/docs</span> to browse the documentation

', + ]; + } +} diff --git a/tests/Mail/MailMarkdownTest.php b/tests/Mail/MailMarkdownTest.php index 88ebf67893b3..cc4137d12dcd 100644 --- a/tests/Mail/MailMarkdownTest.php +++ b/tests/Mail/MailMarkdownTest.php @@ -3,6 +3,8 @@ namespace Illuminate\Tests\Mail; use Illuminate\Mail\Markdown; +use Illuminate\View\Compilers\BladeCompiler; +use Illuminate\View\Engines\EngineResolver; use Illuminate\View\Factory; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -17,6 +19,14 @@ protected function tearDown(): void public function testRenderFunctionReturnsHtml() { $viewFactory = m::mock(Factory::class); + $engineResolver = m::mock(EngineResolver::class); + $bladeCompiler = m::mock(BladeCompiler::class); + $viewFactory->shouldReceive('getEngineResolver')->andReturn($engineResolver); + $engineResolver->shouldReceive('resolve->getCompiler')->andReturn($bladeCompiler); + $bladeCompiler->shouldReceive('usingEchoFormat') + ->with('new \Illuminate\Support\EncodedHtmlString(%s)', m::type('Closure')) + ->andReturnUsing(fn ($echoFormat, $callback) => $callback()); + $markdown = new Markdown($viewFactory); $viewFactory->shouldReceive('flushFinderCache')->once(); $viewFactory->shouldReceive('replaceNamespace')->once()->with('mail', $markdown->htmlComponentPaths())->andReturnSelf(); @@ -33,6 +43,14 @@ public function testRenderFunctionReturnsHtml() public function testRenderFunctionReturnsHtmlWithCustomTheme() { $viewFactory = m::mock(Factory::class); + $engineResolver = m::mock(EngineResolver::class); + $bladeCompiler = m::mock(BladeCompiler::class); + $viewFactory->shouldReceive('getEngineResolver')->andReturn($engineResolver); + $engineResolver->shouldReceive('resolve->getCompiler')->andReturn($bladeCompiler); + $bladeCompiler->shouldReceive('usingEchoFormat') + ->with('new \Illuminate\Support\EncodedHtmlString(%s)', m::type('Closure')) + ->andReturnUsing(fn ($echoFormat, $callback) => $callback()); + $markdown = new Markdown($viewFactory); $markdown->theme('yaz'); $viewFactory->shouldReceive('flushFinderCache')->once(); @@ -50,6 +68,14 @@ public function testRenderFunctionReturnsHtmlWithCustomTheme() public function testRenderFunctionReturnsHtmlWithCustomThemeWithMailPrefix() { $viewFactory = m::mock(Factory::class); + $engineResolver = m::mock(EngineResolver::class); + $bladeCompiler = m::mock(BladeCompiler::class); + $viewFactory->shouldReceive('getEngineResolver')->andReturn($engineResolver); + $engineResolver->shouldReceive('resolve->getCompiler')->andReturn($bladeCompiler); + $bladeCompiler->shouldReceive('usingEchoFormat') + ->with('new \Illuminate\Support\EncodedHtmlString(%s)', m::type('Closure')) + ->andReturnUsing(fn ($echoFormat, $callback) => $callback()); + $markdown = new Markdown($viewFactory); $markdown->theme('mail.yaz'); $viewFactory->shouldReceive('flushFinderCache')->once(); From 32bbb68aface162f5e827790bd3c675fbd15a686 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 18 Mar 2025 18:13:30 +0000 Subject: [PATCH 041/158] Update facade docblocks --- src/Illuminate/Support/Facades/Blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Blade.php b/src/Illuminate/Support/Facades/Blade.php index 01dc7ae76723..8ca0ca8c0249 100755 --- a/src/Illuminate/Support/Facades/Blade.php +++ b/src/Illuminate/Support/Facades/Blade.php @@ -31,6 +31,7 @@ * @method static array getCustomDirectives() * @method static \Illuminate\View\Compilers\BladeCompiler prepareStringsForCompilationUsing(callable $callback) * @method static void precompiler(callable $precompiler) + * @method static string usingEchoFormat(string $format, callable $callback) * @method static void setEchoFormat(string $format) * @method static void withDoubleEncoding() * @method static void withoutDoubleEncoding() From b8a8f8383cbf0628636dd0a98bcd2925ddee5c01 Mon Sep 17 00:00:00 2001 From: Martin Saldinger <51637671+LeTamanoir@users.noreply.github.com> Date: Sat, 22 Mar 2025 02:27:13 +0100 Subject: [PATCH 042/158] fix missing return (#55099) --- src/Illuminate/Testing/TestResponse.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 4851b175c885..c0a468a7d0f0 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -994,6 +994,8 @@ public function assertOnlyJsonValidationErrors($errors, $responseKey = 'errors') $unexpectedErrorKeys = Arr::except($jsonErrors, $expectedErrorKeys); PHPUnit::withResponse($this)->assertTrue(count($unexpectedErrorKeys) === 0, 'Response has unexpected validation errors: '.collect($unexpectedErrorKeys)->keys()->map(fn ($key) => "'{$key}'")->join(', ')); + + return $this; } /** From 863d9289a5150e19ee8f6a09631a6ce441f701bc Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 24 Mar 2025 11:25:05 +0000 Subject: [PATCH 043/158] StyleCI fixes --- .../Auth/Access/AuthorizationException.php | 2 +- src/Illuminate/Auth/Access/Gate.php | 4 ++-- .../Auth/Passwords/PasswordBroker.php | 2 +- src/Illuminate/Auth/RequestGuard.php | 2 +- src/Illuminate/Auth/SessionGuard.php | 4 ++-- .../Broadcasting/BroadcastManager.php | 2 +- src/Illuminate/Bus/Batch.php | 2 +- src/Illuminate/Bus/Dispatcher.php | 2 +- src/Illuminate/Collections/Arr.php | 4 ++-- src/Illuminate/Collections/Collection.php | 6 ++--- src/Illuminate/Collections/Enumerable.php | 20 ++++++++-------- src/Illuminate/Collections/LazyCollection.php | 6 ++--- .../Collections/Traits/EnumeratesValues.php | 14 +++++------ src/Illuminate/Console/Application.php | 2 +- src/Illuminate/Container/Container.php | 8 +++---- src/Illuminate/Contracts/Auth/Access/Gate.php | 2 +- .../Contracts/Auth/PasswordBroker.php | 2 +- .../Contracts/Container/Container.php | 4 ++-- src/Illuminate/Database/Capsule/Manager.php | 2 +- src/Illuminate/Database/Eloquent/Builder.php | 4 ++-- .../Database/Eloquent/Casts/Attribute.php | 2 +- .../Eloquent/Concerns/HasGlobalScopes.php | 2 +- .../Concerns/QueriesRelationships.php | 24 +++++++++---------- src/Illuminate/Database/Eloquent/Model.php | 2 +- .../Eloquent/Relations/BelongsToMany.php | 2 +- .../Eloquent/Relations/HasManyThrough.php | 2 +- .../Database/Eloquent/Relations/Relation.php | 4 ++-- .../Database/Migrations/Migrator.php | 2 +- src/Illuminate/Database/MySqlConnection.php | 2 +- .../Database/PostgresConnection.php | 2 +- src/Illuminate/Database/Query/Builder.php | 4 ++-- src/Illuminate/Database/SQLiteConnection.php | 2 +- src/Illuminate/Database/Schema/Blueprint.php | 2 +- src/Illuminate/Database/Schema/Builder.php | 2 +- .../Database/Schema/SchemaState.php | 2 +- .../Database/SqlServerConnection.php | 2 +- src/Illuminate/Events/Dispatcher.php | 2 +- .../Exceptions/MaintenanceModeException.php | 2 +- .../Foundation/Http/FormRequest.php | 2 +- .../Concerns/InteractsWithContainer.php | 6 ++--- .../Validation/ValidatesRequests.php | 2 +- src/Illuminate/Http/Client/Factory.php | 2 +- src/Illuminate/Http/Client/PendingRequest.php | 2 +- src/Illuminate/Http/Client/Pool.php | 2 +- .../Http/Concerns/InteractsWithInput.php | 4 ++-- .../Http/Exceptions/PostTooLargeException.php | 2 +- .../Exceptions/ThrottleRequestsException.php | 2 +- src/Illuminate/Http/RedirectResponse.php | 2 +- src/Illuminate/Http/Request.php | 2 +- src/Illuminate/Log/Logger.php | 2 +- src/Illuminate/Mail/Mailer.php | 2 +- .../Notifications/ChannelManager.php | 2 +- .../Notifications/NotificationSender.php | 2 +- .../Notifications/RoutesNotifications.php | 2 +- .../Notifications/SendQueuedNotifications.php | 2 +- src/Illuminate/Pipeline/Hub.php | 2 +- src/Illuminate/Pipeline/Pipeline.php | 2 +- src/Illuminate/Queue/Capsule/Manager.php | 2 +- src/Illuminate/Queue/Worker.php | 2 +- .../Redis/Connections/PhpRedisConnection.php | 6 ++--- .../Limiters/ConcurrencyLimiterBuilder.php | 2 +- .../Redis/Limiters/DurationLimiterBuilder.php | 2 +- src/Illuminate/Routing/Router.php | 4 ++-- .../Session/DatabaseSessionHandler.php | 2 +- .../Session/Middleware/StartSession.php | 4 ++-- src/Illuminate/Support/Str.php | 2 +- .../Testing/Fakes/NotificationFake.php | 2 +- src/Illuminate/Support/helpers.php | 4 ++-- .../Testing/AssertableJsonString.php | 2 +- .../Testing/Fluent/AssertableJson.php | 4 ++-- .../Testing/Fluent/Concerns/Debugging.php | 6 ++--- .../Testing/Fluent/Concerns/Has.php | 6 ++--- .../Testing/Fluent/Concerns/Interaction.php | 2 +- .../Testing/Fluent/Concerns/Matching.php | 4 ++-- src/Illuminate/Testing/TestResponse.php | 2 +- src/Illuminate/Validation/Factory.php | 2 +- src/Illuminate/Validation/Validator.php | 4 ++-- .../View/Engines/CompilerEngine.php | 2 +- src/Illuminate/View/FileViewFinder.php | 2 +- src/Illuminate/View/View.php | 2 +- tests/Auth/AuthAccessGateTest.php | 8 +++---- .../ContainerResolveNonInstantiableTest.php | 2 +- tests/Container/ContextualBindingTest.php | 2 +- .../NotificationDatabaseChannelTest.php | 8 +++---- tests/Routing/RoutingRouteTest.php | 10 ++++---- tests/Support/SupportCarbonTest.php | 2 +- tests/Support/SupportReflectsClosuresTest.php | 2 +- tests/Validation/ValidationValidatorTest.php | 20 ++++++++-------- 88 files changed, 162 insertions(+), 162 deletions(-) diff --git a/src/Illuminate/Auth/Access/AuthorizationException.php b/src/Illuminate/Auth/Access/AuthorizationException.php index 7fe6ceba9581..0a993bca3246 100644 --- a/src/Illuminate/Auth/Access/AuthorizationException.php +++ b/src/Illuminate/Auth/Access/AuthorizationException.php @@ -22,7 +22,7 @@ class AuthorizationException extends Exception * @param \Throwable|null $previous * @return void */ - public function __construct($message = null, $code = null, Throwable $previous = null) + public function __construct($message = null, $code = null, ?Throwable $previous = null) { parent::__construct($message ?? 'This action is unauthorized.', 0, $previous); diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index fe8d93fcb4ec..5ead15cf4714 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -88,7 +88,7 @@ class Gate implements GateContract */ public function __construct(Container $container, callable $userResolver, array $abilities = [], array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [], - callable $guessPolicyNamesUsingCallback = null) + ?callable $guessPolicyNamesUsingCallback = null) { $this->policies = $policies; $this->container = $container; @@ -212,7 +212,7 @@ public function define($ability, $callback) * @param array|null $abilities * @return $this */ - public function resource($name, $class, array $abilities = null) + public function resource($name, $class, ?array $abilities = null) { $abilities = $abilities ?: [ 'viewAny' => 'viewAny', diff --git a/src/Illuminate/Auth/Passwords/PasswordBroker.php b/src/Illuminate/Auth/Passwords/PasswordBroker.php index cbbc897abd85..5d212c503bd5 100755 --- a/src/Illuminate/Auth/Passwords/PasswordBroker.php +++ b/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -45,7 +45,7 @@ public function __construct(TokenRepositoryInterface $tokens, UserProvider $user * @param \Closure|null $callback * @return string */ - public function sendResetLink(array $credentials, Closure $callback = null) + public function sendResetLink(array $credentials, ?Closure $callback = null) { // First we will check to see if we found a user at the given credentials and // if we did not we will redirect back to this current URI with a piece of diff --git a/src/Illuminate/Auth/RequestGuard.php b/src/Illuminate/Auth/RequestGuard.php index d0af83cb4f4f..7c1dfdc553e0 100644 --- a/src/Illuminate/Auth/RequestGuard.php +++ b/src/Illuminate/Auth/RequestGuard.php @@ -33,7 +33,7 @@ class RequestGuard implements Guard * @param \Illuminate\Contracts\Auth\UserProvider|null $provider * @return void */ - public function __construct(callable $callback, Request $request, UserProvider $provider = null) + public function __construct(callable $callback, Request $request, ?UserProvider $provider = null) { $this->request = $request; $this->callback = $callback; diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index cd9ec98d7e2f..b598e942ac65 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -123,8 +123,8 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth public function __construct($name, UserProvider $provider, Session $session, - Request $request = null, - Timebox $timebox = null) + ?Request $request = null, + ?Timebox $timebox = null) { $this->name = $name; $this->session = $session; diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index a4957cde900f..83a07c6af02a 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -60,7 +60,7 @@ public function __construct($app) * @param array|null $attributes * @return void */ - public function routes(array $attributes = null) + public function routes(?array $attributes = null) { if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) { return; diff --git a/src/Illuminate/Bus/Batch.php b/src/Illuminate/Bus/Batch.php index d1464e442579..9b64507b1105 100644 --- a/src/Illuminate/Bus/Batch.php +++ b/src/Illuminate/Bus/Batch.php @@ -424,7 +424,7 @@ public function delete() * @param \Throwable|null $e * @return void */ - protected function invokeHandlerCallback($handler, Batch $batch, Throwable $e = null) + protected function invokeHandlerCallback($handler, Batch $batch, ?Throwable $e = null) { try { return $handler($batch, $e); diff --git a/src/Illuminate/Bus/Dispatcher.php b/src/Illuminate/Bus/Dispatcher.php index 4dc390e653fb..bab9f688f78a 100644 --- a/src/Illuminate/Bus/Dispatcher.php +++ b/src/Illuminate/Bus/Dispatcher.php @@ -58,7 +58,7 @@ class Dispatcher implements QueueingDispatcher * @param \Closure|null $queueResolver * @return void */ - public function __construct(Container $container, Closure $queueResolver = null) + public function __construct(Container $container, ?Closure $queueResolver = null) { $this->container = $container; $this->queueResolver = $queueResolver; diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index fd7dca8a586a..d1b469bf34c2 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -180,7 +180,7 @@ public static function exists($array, $key) * @param mixed $default * @return mixed */ - public static function first($array, callable $callback = null, $default = null) + public static function first($array, ?callable $callback = null, $default = null) { if (is_null($callback)) { if (empty($array)) { @@ -209,7 +209,7 @@ public static function first($array, callable $callback = null, $default = null) * @param mixed $default * @return mixed */ - public static function last($array, callable $callback = null, $default = null) + public static function last($array, ?callable $callback = null, $default = null) { if (is_null($callback)) { return empty($array) ? value($default) : end($array); diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 61a48841c112..e9839710effb 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -352,7 +352,7 @@ public function except($keys) * @param callable|null $callback * @return static */ - public function filter(callable $callback = null) + public function filter(?callable $callback = null) { if ($callback) { return new static(Arr::where($this->items, $callback)); @@ -368,7 +368,7 @@ public function filter(callable $callback = null) * @param mixed $default * @return mixed */ - public function first(callable $callback = null, $default = null) + public function first(?callable $callback = null, $default = null) { return Arr::first($this->items, $callback, $default); } @@ -665,7 +665,7 @@ public function keys() * @param mixed $default * @return mixed */ - public function last(callable $callback = null, $default = null) + public function last(?callable $callback = null, $default = null) { return Arr::last($this->items, $callback, $default); } diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 261a0c856b39..9777bd8ab0bf 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -25,7 +25,7 @@ public static function make($items = []); * @param callable|null $callback * @return static */ - public static function times($number, callable $callback = null); + public static function times($number, ?callable $callback = null); /** * Create a collection with the given range. @@ -265,7 +265,7 @@ public function except($keys); * @param callable|null $callback * @return static */ - public function filter(callable $callback = null); + public function filter(?callable $callback = null); /** * Apply the callback if the value is truthy. @@ -275,7 +275,7 @@ public function filter(callable $callback = null); * @param callable|null $default * @return static|mixed */ - public function when($value, callable $callback, callable $default = null); + public function when($value, callable $callback, ?callable $default = null); /** * Apply the callback if the collection is empty. @@ -284,7 +284,7 @@ public function when($value, callable $callback, callable $default = null); * @param callable|null $default * @return static|mixed */ - public function whenEmpty(callable $callback, callable $default = null); + public function whenEmpty(callable $callback, ?callable $default = null); /** * Apply the callback if the collection is not empty. @@ -293,7 +293,7 @@ public function whenEmpty(callable $callback, callable $default = null); * @param callable|null $default * @return static|mixed */ - public function whenNotEmpty(callable $callback, callable $default = null); + public function whenNotEmpty(callable $callback, ?callable $default = null); /** * Apply the callback if the value is falsy. @@ -303,7 +303,7 @@ public function whenNotEmpty(callable $callback, callable $default = null); * @param callable|null $default * @return static|mixed */ - public function unless($value, callable $callback, callable $default = null); + public function unless($value, callable $callback, ?callable $default = null); /** * Apply the callback unless the collection is empty. @@ -312,7 +312,7 @@ public function unless($value, callable $callback, callable $default = null); * @param callable|null $default * @return static|mixed */ - public function unlessEmpty(callable $callback, callable $default = null); + public function unlessEmpty(callable $callback, ?callable $default = null); /** * Apply the callback unless the collection is not empty. @@ -321,7 +321,7 @@ public function unlessEmpty(callable $callback, callable $default = null); * @param callable|null $default * @return static|mixed */ - public function unlessNotEmpty(callable $callback, callable $default = null); + public function unlessNotEmpty(callable $callback, ?callable $default = null); /** * Filter items by the given key value pair. @@ -429,7 +429,7 @@ public function whereInstanceOf($type); * @param mixed $default * @return mixed */ - public function first(callable $callback = null, $default = null); + public function first(?callable $callback = null, $default = null); /** * Get the first item by the given key value pair. @@ -552,7 +552,7 @@ public function keys(); * @param mixed $default * @return mixed */ - public function last(callable $callback = null, $default = null); + public function last(?callable $callback = null, $default = null); /** * Run a map over each of the items. diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index e1cdcd99d6eb..65230ffc46cb 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -367,7 +367,7 @@ public function except($keys) * @param callable|null $callback * @return static */ - public function filter(callable $callback = null) + public function filter(?callable $callback = null) { if (is_null($callback)) { $callback = function ($value) { @@ -391,7 +391,7 @@ public function filter(callable $callback = null) * @param mixed $default * @return mixed */ - public function first(callable $callback = null, $default = null) + public function first(?callable $callback = null, $default = null) { $iterator = $this->getIterator(); @@ -632,7 +632,7 @@ public function keys() * @param mixed $default * @return mixed */ - public function last(callable $callback = null, $default = null) + public function last(?callable $callback = null, $default = null) { $needle = $placeholder = new stdClass; diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 269d1a66656f..41d2b0702bdc 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -141,7 +141,7 @@ public static function empty() * @param callable|null $callback * @return static */ - public static function times($number, callable $callback = null) + public static function times($number, ?callable $callback = null) { if ($number < 1) { return new static; @@ -472,7 +472,7 @@ public function sum($callback = null) * @param callable|null $default * @return static|mixed */ - public function when($value, callable $callback = null, callable $default = null) + public function when($value, ?callable $callback = null, ?callable $default = null) { if (! $callback) { return new HigherOrderWhenProxy($this, $value); @@ -494,7 +494,7 @@ public function when($value, callable $callback = null, callable $default = null * @param callable|null $default * @return static|mixed */ - public function whenEmpty(callable $callback, callable $default = null) + public function whenEmpty(callable $callback, ?callable $default = null) { return $this->when($this->isEmpty(), $callback, $default); } @@ -506,7 +506,7 @@ public function whenEmpty(callable $callback, callable $default = null) * @param callable|null $default * @return static|mixed */ - public function whenNotEmpty(callable $callback, callable $default = null) + public function whenNotEmpty(callable $callback, ?callable $default = null) { return $this->when($this->isNotEmpty(), $callback, $default); } @@ -519,7 +519,7 @@ public function whenNotEmpty(callable $callback, callable $default = null) * @param callable|null $default * @return static|mixed */ - public function unless($value, callable $callback, callable $default = null) + public function unless($value, callable $callback, ?callable $default = null) { return $this->when(! $value, $callback, $default); } @@ -531,7 +531,7 @@ public function unless($value, callable $callback, callable $default = null) * @param callable|null $default * @return static|mixed */ - public function unlessEmpty(callable $callback, callable $default = null) + public function unlessEmpty(callable $callback, ?callable $default = null) { return $this->whenNotEmpty($callback, $default); } @@ -543,7 +543,7 @@ public function unlessEmpty(callable $callback, callable $default = null) * @param callable|null $default * @return static|mixed */ - public function unlessNotEmpty(callable $callback, callable $default = null) + public function unlessNotEmpty(callable $callback, ?callable $default = null) { return $this->whenEmpty($callback, $default); } diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 88c65c708171..6999ab470610 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -79,7 +79,7 @@ public function __construct(Container $laravel, Dispatcher $events, $version) * * @return int */ - public function run(InputInterface $input = null, OutputInterface $output = null) + public function run(?InputInterface $input = null, ?OutputInterface $output = null) { $commandName = $this->getCommandName( $input = $input ?: new ArgvInput diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index e6cd346fede8..8b2c1a15afc9 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -1111,7 +1111,7 @@ protected function unresolvablePrimitive(ReflectionParameter $parameter) * @param \Closure|null $callback * @return void */ - public function beforeResolving($abstract, Closure $callback = null) + public function beforeResolving($abstract, ?Closure $callback = null) { if (is_string($abstract)) { $abstract = $this->getAlias($abstract); @@ -1131,7 +1131,7 @@ public function beforeResolving($abstract, Closure $callback = null) * @param \Closure|null $callback * @return void */ - public function resolving($abstract, Closure $callback = null) + public function resolving($abstract, ?Closure $callback = null) { if (is_string($abstract)) { $abstract = $this->getAlias($abstract); @@ -1151,7 +1151,7 @@ public function resolving($abstract, Closure $callback = null) * @param \Closure|null $callback * @return void */ - public function afterResolving($abstract, Closure $callback = null) + public function afterResolving($abstract, ?Closure $callback = null) { if (is_string($abstract)) { $abstract = $this->getAlias($abstract); @@ -1390,7 +1390,7 @@ public static function getInstance() * @param \Illuminate\Contracts\Container\Container|null $container * @return \Illuminate\Contracts\Container\Container|static */ - public static function setInstance(ContainerContract $container = null) + public static function setInstance(?ContainerContract $container = null) { return static::$instance = $container; } diff --git a/src/Illuminate/Contracts/Auth/Access/Gate.php b/src/Illuminate/Contracts/Auth/Access/Gate.php index b88ab17965ed..2540506103a2 100644 --- a/src/Illuminate/Contracts/Auth/Access/Gate.php +++ b/src/Illuminate/Contracts/Auth/Access/Gate.php @@ -29,7 +29,7 @@ public function define($ability, $callback); * @param array|null $abilities * @return $this */ - public function resource($name, $class, array $abilities = null); + public function resource($name, $class, ?array $abilities = null); /** * Define a policy class for a given class type. diff --git a/src/Illuminate/Contracts/Auth/PasswordBroker.php b/src/Illuminate/Contracts/Auth/PasswordBroker.php index bbbe9b508688..c6b202329e39 100644 --- a/src/Illuminate/Contracts/Auth/PasswordBroker.php +++ b/src/Illuminate/Contracts/Auth/PasswordBroker.php @@ -48,7 +48,7 @@ interface PasswordBroker * @param \Closure|null $callback * @return string */ - public function sendResetLink(array $credentials, Closure $callback = null); + public function sendResetLink(array $credentials, ?Closure $callback = null); /** * Reset the password for the given token. diff --git a/src/Illuminate/Contracts/Container/Container.php b/src/Illuminate/Contracts/Container/Container.php index 1b8bb6407934..1476d42396c8 100644 --- a/src/Illuminate/Contracts/Container/Container.php +++ b/src/Illuminate/Contracts/Container/Container.php @@ -170,7 +170,7 @@ public function resolved($abstract); * @param \Closure|null $callback * @return void */ - public function resolving($abstract, Closure $callback = null); + public function resolving($abstract, ?Closure $callback = null); /** * Register a new after resolving callback. @@ -179,5 +179,5 @@ public function resolving($abstract, Closure $callback = null); * @param \Closure|null $callback * @return void */ - public function afterResolving($abstract, Closure $callback = null); + public function afterResolving($abstract, ?Closure $callback = null); } diff --git a/src/Illuminate/Database/Capsule/Manager.php b/src/Illuminate/Database/Capsule/Manager.php index b877e7c6d20d..cfc47eb5abcf 100755 --- a/src/Illuminate/Database/Capsule/Manager.php +++ b/src/Illuminate/Database/Capsule/Manager.php @@ -27,7 +27,7 @@ class Manager * @param \Illuminate\Container\Container|null $container * @return void */ - public function __construct(Container $container = null) + public function __construct(?Container $container = null) { $this->setupContainer($container ?: new Container); diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index ceeaaec24a2c..2b9b9e8f000f 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -189,7 +189,7 @@ public function withoutGlobalScope($scope) * @param array|null $scopes * @return $this */ - public function withoutGlobalScopes(array $scopes = null) + public function withoutGlobalScopes(?array $scopes = null) { if (! is_array($scopes)) { $scopes = array_keys($this->scopes); @@ -534,7 +534,7 @@ public function firstOrFail($columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ - public function firstOr($columns = ['*'], Closure $callback = null) + public function firstOr($columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Database/Eloquent/Casts/Attribute.php b/src/Illuminate/Database/Eloquent/Casts/Attribute.php index a21b97bb3d2a..8999307b9426 100644 --- a/src/Illuminate/Database/Eloquent/Casts/Attribute.php +++ b/src/Illuminate/Database/Eloquent/Casts/Attribute.php @@ -32,7 +32,7 @@ class Attribute * @param callable|null $set * @return void */ - public function __construct(callable $get = null, callable $set = null) + public function __construct(?callable $get = null, ?callable $set = null) { $this->get = $get; $this->set = $set; diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php index 1742679c5a30..fe835b504846 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php @@ -18,7 +18,7 @@ trait HasGlobalScopes * * @throws \InvalidArgumentException */ - public static function addGlobalScope($scope, Closure $implementation = null) + public static function addGlobalScope($scope, ?Closure $implementation = null) { if (is_string($scope) && ! is_null($implementation)) { return static::$globalScopes[static::class][$scope] = $implementation; diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index c16af1fa0007..a41e65810a2f 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -27,7 +27,7 @@ trait QueriesRelationships * * @throws \RuntimeException */ - public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) + public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null) { if (is_string($relation)) { if (strpos($relation, '.') !== false) { @@ -120,7 +120,7 @@ public function orHas($relation, $operator = '>=', $count = 1) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function doesntHave($relation, $boolean = 'and', Closure $callback = null) + public function doesntHave($relation, $boolean = 'and', ?Closure $callback = null) { return $this->has($relation, '<', 1, $boolean, $callback); } @@ -145,7 +145,7 @@ public function orDoesntHave($relation) * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ - public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + public function whereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'and', $callback); } @@ -159,7 +159,7 @@ public function whereHas($relation, Closure $callback = null, $operator = '>=', * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ - public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + public function orWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'or', $callback); } @@ -171,7 +171,7 @@ public function orWhereHas($relation, Closure $callback = null, $operator = '>=' * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function whereDoesntHave($relation, Closure $callback = null) + public function whereDoesntHave($relation, ?Closure $callback = null) { return $this->doesntHave($relation, 'and', $callback); } @@ -183,7 +183,7 @@ public function whereDoesntHave($relation, Closure $callback = null) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function orWhereDoesntHave($relation, Closure $callback = null) + public function orWhereDoesntHave($relation, ?Closure $callback = null) { return $this->doesntHave($relation, 'or', $callback); } @@ -199,7 +199,7 @@ public function orWhereDoesntHave($relation, Closure $callback = null) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) + public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null) { if (is_string($relation)) { $relation = $this->getRelationWithoutConstraints($relation); @@ -278,7 +278,7 @@ public function orHasMorph($relation, $types, $operator = '>=', $count = 1) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null) + public function doesntHaveMorph($relation, $types, $boolean = 'and', ?Closure $callback = null) { return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback); } @@ -305,7 +305,7 @@ public function orDoesntHaveMorph($relation, $types) * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ - public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) + public function whereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback); } @@ -320,7 +320,7 @@ public function whereHasMorph($relation, $types, Closure $callback = null, $oper * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ - public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) + public function orWhereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback); } @@ -333,7 +333,7 @@ public function orWhereHasMorph($relation, $types, Closure $callback = null, $op * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function whereDoesntHaveMorph($relation, $types, Closure $callback = null) + public function whereDoesntHaveMorph($relation, $types, ?Closure $callback = null) { return $this->doesntHaveMorph($relation, $types, 'and', $callback); } @@ -346,7 +346,7 @@ public function whereDoesntHaveMorph($relation, $types, Closure $callback = null * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ - public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null) + public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = null) { return $this->doesntHaveMorph($relation, $types, 'or', $callback); } diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index d1742bbebfad..8b106620d667 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -1563,7 +1563,7 @@ public function refresh() * @param array|null $except * @return static */ - public function replicate(array $except = null) + public function replicate(?array $except = null) { $defaults = [ $this->getKeyName(), diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 4cadd7407238..11ee38a532c7 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -760,7 +760,7 @@ public function firstOrFail($columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ - public function firstOr($columns = ['*'], Closure $callback = null) + public function firstOr($columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 207481679829..da979cb2a9be 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -102,7 +102,7 @@ public function addConstraints() * @param \Illuminate\Database\Eloquent\Builder|null $query * @return void */ - protected function performJoin(Builder $query = null) + protected function performJoin(?Builder $query = null) { $query = $query ?: $this->query; diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index aa8ce5a07da0..43ae0d4aa5d1 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -425,7 +425,7 @@ public static function enforceMorphMap(array $map, $merge = true) * @param bool $merge * @return array */ - public static function morphMap(array $map = null, $merge = true) + public static function morphMap(?array $map = null, $merge = true) { $map = static::buildMorphMapFromModels($map); @@ -443,7 +443,7 @@ public static function morphMap(array $map = null, $merge = true) * @param string[]|null $models * @return array|null */ - protected static function buildMorphMapFromModels(array $models = null) + protected static function buildMorphMapFromModels(?array $models = null) { if (is_null($models) || Arr::isAssoc($models)) { return $models; diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index c043e6cd735f..8cbee920bcea 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -80,7 +80,7 @@ class Migrator public function __construct(MigrationRepositoryInterface $repository, Resolver $resolver, Filesystem $files, - Dispatcher $dispatcher = null) + ?Dispatcher $dispatcher = null) { $this->files = $files; $this->events = $dispatcher; diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 9760358cf5f4..ad9f72060cd4 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -66,7 +66,7 @@ protected function getDefaultSchemaGrammar() * @param callable|null $processFactory * @return \Illuminate\Database\Schema\MySqlSchemaState */ - public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + public function getSchemaState(?Filesystem $files = null, ?callable $processFactory = null) { return new MySqlSchemaState($this, $files, $processFactory); } diff --git a/src/Illuminate/Database/PostgresConnection.php b/src/Illuminate/Database/PostgresConnection.php index 5d68d1d665a7..70dd2c22ef7c 100755 --- a/src/Illuminate/Database/PostgresConnection.php +++ b/src/Illuminate/Database/PostgresConnection.php @@ -82,7 +82,7 @@ protected function getDefaultSchemaGrammar() * @param callable|null $processFactory * @return \Illuminate\Database\Schema\PostgresSchemaState */ - public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + public function getSchemaState(?Filesystem $files = null, ?callable $processFactory = null) { return new PostgresSchemaState($this, $files, $processFactory); } diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 40bd0b9589c4..86a79ef00ade 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -228,8 +228,8 @@ class Builder * @return void */ public function __construct(ConnectionInterface $connection, - Grammar $grammar = null, - Processor $processor = null) + ?Grammar $grammar = null, + ?Processor $processor = null) { $this->connection = $connection; $this->grammar = $grammar ?: $connection->getQueryGrammar(); diff --git a/src/Illuminate/Database/SQLiteConnection.php b/src/Illuminate/Database/SQLiteConnection.php index 38116877c3ca..e8c18a213530 100755 --- a/src/Illuminate/Database/SQLiteConnection.php +++ b/src/Illuminate/Database/SQLiteConnection.php @@ -80,7 +80,7 @@ protected function getDefaultSchemaGrammar() * * @throws \RuntimeException */ - public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + public function getSchemaState(?Filesystem $files = null, ?callable $processFactory = null) { return new SqliteSchemaState($this, $files, $processFactory); } diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index dfe53ee792a8..92b210032402 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -86,7 +86,7 @@ class Blueprint * @param string $prefix * @return void */ - public function __construct($table, Closure $callback = null, $prefix = '') + public function __construct($table, ?Closure $callback = null, $prefix = '') { $this->table = $table; $this->prefix = $prefix; diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 40f78880b8ce..3d91a5c467de 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -371,7 +371,7 @@ protected function build(Blueprint $blueprint) * @param \Closure|null $callback * @return \Illuminate\Database\Schema\Blueprint */ - protected function createBlueprint($table, Closure $callback = null) + protected function createBlueprint($table, ?Closure $callback = null) { $prefix = $this->connection->getConfig('prefix_indexes') ? $this->connection->getConfig('prefix') diff --git a/src/Illuminate/Database/Schema/SchemaState.php b/src/Illuminate/Database/Schema/SchemaState.php index e6f35ab91fe9..b0df87fd15a1 100644 --- a/src/Illuminate/Database/Schema/SchemaState.php +++ b/src/Illuminate/Database/Schema/SchemaState.php @@ -51,7 +51,7 @@ abstract class SchemaState * @param callable|null $processFactory * @return void */ - public function __construct(Connection $connection, Filesystem $files = null, callable $processFactory = null) + public function __construct(Connection $connection, ?Filesystem $files = null, ?callable $processFactory = null) { $this->connection = $connection; diff --git a/src/Illuminate/Database/SqlServerConnection.php b/src/Illuminate/Database/SqlServerConnection.php index d6a2b7ae95f3..2522390b6fd4 100755 --- a/src/Illuminate/Database/SqlServerConnection.php +++ b/src/Illuminate/Database/SqlServerConnection.php @@ -98,7 +98,7 @@ protected function getDefaultSchemaGrammar() * * @throws \RuntimeException */ - public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + public function getSchemaState(?Filesystem $files = null, ?callable $processFactory = null) { throw new RuntimeException('Schema dumping is not supported when using SQL Server.'); } diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 5972a8384947..fc1e5c893b19 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -62,7 +62,7 @@ class Dispatcher implements DispatcherContract * @param \Illuminate\Contracts\Container\Container|null $container * @return void */ - public function __construct(ContainerContract $container = null) + public function __construct(?ContainerContract $container = null) { $this->container = $container ?: new Container; } diff --git a/src/Illuminate/Foundation/Http/Exceptions/MaintenanceModeException.php b/src/Illuminate/Foundation/Http/Exceptions/MaintenanceModeException.php index 5553fde625d2..2770e62d918e 100644 --- a/src/Illuminate/Foundation/Http/Exceptions/MaintenanceModeException.php +++ b/src/Illuminate/Foundation/Http/Exceptions/MaintenanceModeException.php @@ -43,7 +43,7 @@ class MaintenanceModeException extends ServiceUnavailableHttpException * @param int $code * @return void */ - public function __construct($time, $retryAfter = null, $message = null, Throwable $previous = null, $code = 0) + public function __construct($time, $retryAfter = null, $message = null, ?Throwable $previous = null, $code = 0) { parent::__construct($retryAfter, $message, $previous, $code); diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index a20dffe2fbe7..4558ab96e9da 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -196,7 +196,7 @@ protected function failedAuthorization() * @param array|null $keys * @return \Illuminate\Support\ValidatedInput|array */ - public function safe(array $keys = null) + public function safe(?array $keys = null) { return is_array($keys) ? $this->validator->safe()->only($keys) diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php index 6949f6f8c5da..b6c251437d05 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php @@ -48,7 +48,7 @@ protected function instance($abstract, $instance) * @param \Closure|null $mock * @return \Mockery\MockInterface */ - protected function mock($abstract, Closure $mock = null) + protected function mock($abstract, ?Closure $mock = null) { return $this->instance($abstract, Mockery::mock(...array_filter(func_get_args()))); } @@ -60,7 +60,7 @@ protected function mock($abstract, Closure $mock = null) * @param \Closure|null $mock * @return \Mockery\MockInterface */ - protected function partialMock($abstract, Closure $mock = null) + protected function partialMock($abstract, ?Closure $mock = null) { return $this->instance($abstract, Mockery::mock(...array_filter(func_get_args()))->makePartial()); } @@ -72,7 +72,7 @@ protected function partialMock($abstract, Closure $mock = null) * @param \Closure|null $mock * @return \Mockery\MockInterface */ - protected function spy($abstract, Closure $mock = null) + protected function spy($abstract, ?Closure $mock = null) { return $this->instance($abstract, Mockery::spy(...array_filter(func_get_args()))); } diff --git a/src/Illuminate/Foundation/Validation/ValidatesRequests.php b/src/Illuminate/Foundation/Validation/ValidatesRequests.php index 2a1593a27a07..8a61f096a997 100644 --- a/src/Illuminate/Foundation/Validation/ValidatesRequests.php +++ b/src/Illuminate/Foundation/Validation/ValidatesRequests.php @@ -17,7 +17,7 @@ trait ValidatesRequests * * @throws \Illuminate\Validation\ValidationException */ - public function validateWith($validator, Request $request = null) + public function validateWith($validator, ?Request $request = null) { $request = $request ?: request(); diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 131e669a4eab..022e90922331 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -98,7 +98,7 @@ class Factory * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher * @return void */ - public function __construct(Dispatcher $dispatcher = null) + public function __construct(?Dispatcher $dispatcher = null) { $this->dispatcher = $dispatcher; diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index fdf5f06d4d8a..0049747f7fd9 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -170,7 +170,7 @@ class PendingRequest * @param \Illuminate\Http\Client\Factory|null $factory * @return void */ - public function __construct(Factory $factory = null) + public function __construct(?Factory $factory = null) { $this->factory = $factory; $this->middleware = new Collection; diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php index bedffcb1d652..da0ef7e736d8 100644 --- a/src/Illuminate/Http/Client/Pool.php +++ b/src/Illuminate/Http/Client/Pool.php @@ -36,7 +36,7 @@ class Pool * @param \Illuminate\Http\Client\Factory|null $factory * @return void */ - public function __construct(Factory $factory = null) + public function __construct(?Factory $factory = null) { $this->factory = $factory ?: new Factory(); diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index ae8b6fe73880..2ae573b46f19 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -119,7 +119,7 @@ public function hasAny($keys) * @param callable|null $default * @return $this|mixed */ - public function whenHas($key, callable $callback, callable $default = null) + public function whenHas($key, callable $callback, ?callable $default = null) { if ($this->has($key)) { return $callback(data_get($this->all(), $key)) ?: $this; @@ -197,7 +197,7 @@ public function anyFilled($keys) * @param callable|null $default * @return $this|mixed */ - public function whenFilled($key, callable $callback, callable $default = null) + public function whenFilled($key, callable $callback, ?callable $default = null) { if ($this->filled($key)) { return $callback(data_get($this->all(), $key)) ?: $this; diff --git a/src/Illuminate/Http/Exceptions/PostTooLargeException.php b/src/Illuminate/Http/Exceptions/PostTooLargeException.php index 75f6cdde313d..58094e853cc5 100644 --- a/src/Illuminate/Http/Exceptions/PostTooLargeException.php +++ b/src/Illuminate/Http/Exceptions/PostTooLargeException.php @@ -16,7 +16,7 @@ class PostTooLargeException extends HttpException * @param int $code * @return void */ - public function __construct($message = null, Throwable $previous = null, array $headers = [], $code = 0) + public function __construct($message = null, ?Throwable $previous = null, array $headers = [], $code = 0) { parent::__construct(413, $message, $previous, $headers, $code); } diff --git a/src/Illuminate/Http/Exceptions/ThrottleRequestsException.php b/src/Illuminate/Http/Exceptions/ThrottleRequestsException.php index c09393174d3a..06675adf7d5a 100644 --- a/src/Illuminate/Http/Exceptions/ThrottleRequestsException.php +++ b/src/Illuminate/Http/Exceptions/ThrottleRequestsException.php @@ -16,7 +16,7 @@ class ThrottleRequestsException extends TooManyRequestsHttpException * @param int $code * @return void */ - public function __construct($message = null, Throwable $previous = null, array $headers = [], $code = 0) + public function __construct($message = null, ?Throwable $previous = null, array $headers = [], $code = 0) { parent::__construct(null, $message, $previous, $code, $headers); } diff --git a/src/Illuminate/Http/RedirectResponse.php b/src/Illuminate/Http/RedirectResponse.php index 32bb5fcffb95..c7cd3527bcbb 100755 --- a/src/Illuminate/Http/RedirectResponse.php +++ b/src/Illuminate/Http/RedirectResponse.php @@ -71,7 +71,7 @@ public function withCookies(array $cookies) * @param array|null $input * @return $this */ - public function withInput(array $input = null) + public function withInput(?array $input = null) { $this->session->flashInput($this->removeFilesFromInput( ! is_null($input) ? $input : $this->request->input() diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index 79175ac4476e..28dc1c26f979 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -472,7 +472,7 @@ public static function createFromBase(SymfonyRequest $request) * * @return static */ - public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null) { return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server); } diff --git a/src/Illuminate/Log/Logger.php b/src/Illuminate/Log/Logger.php index 382b77c6449f..ae1788d26f2b 100755 --- a/src/Illuminate/Log/Logger.php +++ b/src/Illuminate/Log/Logger.php @@ -40,7 +40,7 @@ class Logger implements LoggerInterface * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher * @return void */ - public function __construct(LoggerInterface $logger, Dispatcher $dispatcher = null) + public function __construct(LoggerInterface $logger, ?Dispatcher $dispatcher = null) { $this->logger = $logger; $this->dispatcher = $dispatcher; diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php index 128f211f7651..a2ac58402555 100755 --- a/src/Illuminate/Mail/Mailer.php +++ b/src/Illuminate/Mail/Mailer.php @@ -100,7 +100,7 @@ class Mailer implements MailerContract, MailQueueContract * @param \Illuminate\Contracts\Events\Dispatcher|null $events * @return void */ - public function __construct(string $name, Factory $views, Swift_Mailer $swift, Dispatcher $events = null) + public function __construct(string $name, Factory $views, Swift_Mailer $swift, ?Dispatcher $events = null) { $this->name = $name; $this->views = $views; diff --git a/src/Illuminate/Notifications/ChannelManager.php b/src/Illuminate/Notifications/ChannelManager.php index 8eb9c251024d..0ad7dae671a8 100644 --- a/src/Illuminate/Notifications/ChannelManager.php +++ b/src/Illuminate/Notifications/ChannelManager.php @@ -47,7 +47,7 @@ public function send($notifiables, $notification) * @param array|null $channels * @return void */ - public function sendNow($notifiables, $notification, array $channels = null) + public function sendNow($notifiables, $notification, ?array $channels = null) { (new NotificationSender( $this, $this->container->make(Bus::class), $this->container->make(Dispatcher::class), $this->locale) diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index c7b67ecc3af1..9480f274b685 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -87,7 +87,7 @@ public function send($notifiables, $notification) * @param array|null $channels * @return void */ - public function sendNow($notifiables, $notification, array $channels = null) + public function sendNow($notifiables, $notification, ?array $channels = null) { $notifiables = $this->formatNotifiables($notifiables); diff --git a/src/Illuminate/Notifications/RoutesNotifications.php b/src/Illuminate/Notifications/RoutesNotifications.php index 799845a77ee0..c69080522991 100644 --- a/src/Illuminate/Notifications/RoutesNotifications.php +++ b/src/Illuminate/Notifications/RoutesNotifications.php @@ -25,7 +25,7 @@ public function notify($instance) * @param array|null $channels * @return void */ - public function notifyNow($instance, array $channels = null) + public function notifyNow($instance, ?array $channels = null) { app(Dispatcher::class)->sendNow($this, $instance, $channels); } diff --git a/src/Illuminate/Notifications/SendQueuedNotifications.php b/src/Illuminate/Notifications/SendQueuedNotifications.php index d83c8906e366..6a6aabe28cd8 100644 --- a/src/Illuminate/Notifications/SendQueuedNotifications.php +++ b/src/Illuminate/Notifications/SendQueuedNotifications.php @@ -65,7 +65,7 @@ class SendQueuedNotifications implements ShouldQueue * @param array|null $channels * @return void */ - public function __construct($notifiables, $notification, array $channels = null) + public function __construct($notifiables, $notification, ?array $channels = null) { $this->channels = $channels; $this->notification = $notification; diff --git a/src/Illuminate/Pipeline/Hub.php b/src/Illuminate/Pipeline/Hub.php index 91e9b3f306b8..54b380b038a1 100644 --- a/src/Illuminate/Pipeline/Hub.php +++ b/src/Illuminate/Pipeline/Hub.php @@ -28,7 +28,7 @@ class Hub implements HubContract * @param \Illuminate\Contracts\Container\Container|null $container * @return void */ - public function __construct(Container $container = null) + public function __construct(?Container $container = null) { $this->container = $container; } diff --git a/src/Illuminate/Pipeline/Pipeline.php b/src/Illuminate/Pipeline/Pipeline.php index d2924e536468..86dd76cc0858 100644 --- a/src/Illuminate/Pipeline/Pipeline.php +++ b/src/Illuminate/Pipeline/Pipeline.php @@ -44,7 +44,7 @@ class Pipeline implements PipelineContract * @param \Illuminate\Contracts\Container\Container|null $container * @return void */ - public function __construct(Container $container = null) + public function __construct(?Container $container = null) { $this->container = $container; } diff --git a/src/Illuminate/Queue/Capsule/Manager.php b/src/Illuminate/Queue/Capsule/Manager.php index 046555afe47e..be2bbb2a06a4 100644 --- a/src/Illuminate/Queue/Capsule/Manager.php +++ b/src/Illuminate/Queue/Capsule/Manager.php @@ -28,7 +28,7 @@ class Manager * @param \Illuminate\Container\Container|null $container * @return void */ - public function __construct(Container $container = null) + public function __construct(?Container $container = null) { $this->setupContainer($container ?: new Container); diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index a46a6f798800..f7ac5b608f5a 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -107,7 +107,7 @@ public function __construct(QueueManager $manager, Dispatcher $events, ExceptionHandler $exceptions, callable $isDownForMaintenance, - callable $resetScope = null) + ?callable $resetScope = null) { $this->events = $events; $this->manager = $manager; diff --git a/src/Illuminate/Redis/Connections/PhpRedisConnection.php b/src/Illuminate/Redis/Connections/PhpRedisConnection.php index 4e68547de3d0..33310d7ef062 100644 --- a/src/Illuminate/Redis/Connections/PhpRedisConnection.php +++ b/src/Illuminate/Redis/Connections/PhpRedisConnection.php @@ -38,7 +38,7 @@ class PhpRedisConnection extends Connection implements ConnectionContract * @param array $config * @return void */ - public function __construct($client, callable $connector = null, array $config = []) + public function __construct($client, ?callable $connector = null, array $config = []) { $this->client = $client; $this->config = $config; @@ -398,7 +398,7 @@ public function sscan($key, $cursor, $options = []) * @param callable|null $callback * @return \Redis|array */ - public function pipeline(callable $callback = null) + public function pipeline(?callable $callback = null) { $pipeline = $this->client()->pipeline(); @@ -413,7 +413,7 @@ public function pipeline(callable $callback = null) * @param callable|null $callback * @return \Redis|array */ - public function transaction(callable $callback = null) + public function transaction(?callable $callback = null) { $transaction = $this->client()->multi(); diff --git a/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php b/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php index e66259f59b6e..4572c67e0991 100644 --- a/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php +++ b/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php @@ -105,7 +105,7 @@ public function block($timeout) * * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException */ - public function then(callable $callback, callable $failure = null) + public function then(callable $callback, ?callable $failure = null) { try { return (new ConcurrencyLimiter( diff --git a/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php b/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php index c32cb50f7213..b208232b2f2b 100644 --- a/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php +++ b/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php @@ -105,7 +105,7 @@ public function block($timeout) * * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException */ - public function then(callable $callback, callable $failure = null) + public function then(callable $callback, ?callable $failure = null) { try { return (new DurationLimiter( diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 26f6ec9ba28d..bf52b69e3b1d 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -128,7 +128,7 @@ class Router implements BindingRegistrar, RegistrarContract * @param \Illuminate\Container\Container|null $container * @return void */ - public function __construct(Dispatcher $events, Container $container = null) + public function __construct(Dispatcher $events, ?Container $container = null) { $this->events = $events; $this->routes = new RouteCollection; @@ -1018,7 +1018,7 @@ public function bind($key, $binder) * @param \Closure|null $callback * @return void */ - public function model($key, $class, Closure $callback = null) + public function model($key, $class, ?Closure $callback = null) { $this->bind($key, RouteBinding::forModel($this->container, $class, $callback)); } diff --git a/src/Illuminate/Session/DatabaseSessionHandler.php b/src/Illuminate/Session/DatabaseSessionHandler.php index 18846add33c0..d78a44a8dff6 100644 --- a/src/Illuminate/Session/DatabaseSessionHandler.php +++ b/src/Illuminate/Session/DatabaseSessionHandler.php @@ -59,7 +59,7 @@ class DatabaseSessionHandler implements ExistenceAwareInterface, SessionHandlerI * @param \Illuminate\Contracts\Container\Container|null $container * @return void */ - public function __construct(ConnectionInterface $connection, $table, $minutes, Container $container = null) + public function __construct(ConnectionInterface $connection, $table, $minutes, ?Container $container = null) { $this->table = $table; $this->minutes = $minutes; diff --git a/src/Illuminate/Session/Middleware/StartSession.php b/src/Illuminate/Session/Middleware/StartSession.php index e7d2daa22315..32224d054eb4 100644 --- a/src/Illuminate/Session/Middleware/StartSession.php +++ b/src/Illuminate/Session/Middleware/StartSession.php @@ -35,7 +35,7 @@ class StartSession * @param callable|null $cacheFactoryResolver * @return void */ - public function __construct(SessionManager $manager, callable $cacheFactoryResolver = null) + public function __construct(SessionManager $manager, ?callable $cacheFactoryResolver = null) { $this->manager = $manager; $this->cacheFactoryResolver = $cacheFactoryResolver; @@ -276,7 +276,7 @@ protected function sessionConfigured() * @param array|null $config * @return bool */ - protected function sessionIsPersistent(array $config = null) + protected function sessionIsPersistent(?array $config = null) { $config = $config ?: $this->manager->getSessionConfig(); diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 21e19040389c..74f5140cfd92 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1004,7 +1004,7 @@ public static function orderedUuid() * @param callable|null $factory * @return void */ - public static function createUuidsUsing(callable $factory = null) + public static function createUuidsUsing(?callable $factory = null) { static::$uuidFactory = $factory; } diff --git a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php index c7b12f42d47c..7269e6352b16 100644 --- a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php +++ b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php @@ -262,7 +262,7 @@ public function send($notifiables, $notification) * @param array|null $channels * @return void */ - public function sendNow($notifiables, $notification, array $channels = null) + public function sendNow($notifiables, $notification, ?array $channels = null) { if (! $notifiables instanceof Collection && ! is_array($notifiables)) { $notifiables = [$notifiables]; diff --git a/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index a85aee2b1b62..330487939830 100755 --- a/src/Illuminate/Support/helpers.php +++ b/src/Illuminate/Support/helpers.php @@ -194,7 +194,7 @@ function object_get($object, $key, $default = null) * @param callable|null $callback * @return mixed */ - function optional($value = null, callable $callback = null) + function optional($value = null, ?callable $callback = null) { if (is_null($callback)) { return new Optional($value); @@ -385,7 +385,7 @@ function windows_os() * @param callable|null $callback * @return mixed */ - function with($value, callable $callback = null) + function with($value, ?callable $callback = null) { return is_null($callback) ? $value : $callback($value); } diff --git a/src/Illuminate/Testing/AssertableJsonString.php b/src/Illuminate/Testing/AssertableJsonString.php index 1964a7c5ce7c..07cc69f33a73 100644 --- a/src/Illuminate/Testing/AssertableJsonString.php +++ b/src/Illuminate/Testing/AssertableJsonString.php @@ -231,7 +231,7 @@ public function assertPath($path, $expect) * @param array|null $responseData * @return $this */ - public function assertStructure(array $structure = null, $responseData = null) + public function assertStructure(?array $structure = null, $responseData = null) { if (is_null($structure)) { return $this->assertSimilar($this->decoded); diff --git a/src/Illuminate/Testing/Fluent/AssertableJson.php b/src/Illuminate/Testing/Fluent/AssertableJson.php index d548e28247f4..abf81d0d57e0 100644 --- a/src/Illuminate/Testing/Fluent/AssertableJson.php +++ b/src/Illuminate/Testing/Fluent/AssertableJson.php @@ -40,7 +40,7 @@ class AssertableJson implements Arrayable * @param string|null $path * @return void */ - protected function __construct(array $props, string $path = null) + protected function __construct(array $props, ?string $path = null) { $this->path = $path; $this->props = $props; @@ -67,7 +67,7 @@ protected function dotPath(string $key = ''): string * @param string|null $key * @return mixed */ - protected function prop(string $key = null) + protected function prop(?string $key = null) { return Arr::get($this->props, $key); } diff --git a/src/Illuminate/Testing/Fluent/Concerns/Debugging.php b/src/Illuminate/Testing/Fluent/Concerns/Debugging.php index f51d119074ae..75e999c36d78 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Debugging.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Debugging.php @@ -10,7 +10,7 @@ trait Debugging * @param string|null $prop * @return $this */ - public function dump(string $prop = null): self + public function dump(?string $prop = null): self { dump($this->prop($prop)); @@ -23,7 +23,7 @@ public function dump(string $prop = null): self * @param string|null $prop * @return void */ - public function dd(string $prop = null): void + public function dd(?string $prop = null): void { dd($this->prop($prop)); } @@ -34,5 +34,5 @@ public function dd(string $prop = null): void * @param string|null $key * @return mixed */ - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); } diff --git a/src/Illuminate/Testing/Fluent/Concerns/Has.php b/src/Illuminate/Testing/Fluent/Concerns/Has.php index 7765f4a061a5..20bfe9d189e3 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Has.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Has.php @@ -15,7 +15,7 @@ trait Has * @param int|null $length * @return $this */ - public function count($key, int $length = null): self + public function count($key, ?int $length = null): self { if (is_null($length)) { $path = $this->dotPath(); @@ -48,7 +48,7 @@ public function count($key, int $length = null): self * @param \Closure|null $callback * @return $this */ - public function has($key, $length = null, Closure $callback = null): self + public function has($key, $length = null, ?Closure $callback = null): self { $prop = $this->prop(); @@ -185,7 +185,7 @@ abstract protected function interactsWith(string $key): void; * @param string|null $key * @return mixed */ - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); /** * Instantiate a new "scope" at the path of the given key. diff --git a/src/Illuminate/Testing/Fluent/Concerns/Interaction.php b/src/Illuminate/Testing/Fluent/Concerns/Interaction.php index 15e7e9508f55..fc811fd95dd7 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Interaction.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Interaction.php @@ -63,5 +63,5 @@ public function etc(): self * @param string|null $key * @return mixed */ - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); } diff --git a/src/Illuminate/Testing/Fluent/Concerns/Matching.php b/src/Illuminate/Testing/Fluent/Concerns/Matching.php index 949047b82bad..3d0578db5fed 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Matching.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Matching.php @@ -181,7 +181,7 @@ abstract protected function dotPath(string $key = ''): string; * @param \Closure|null $scope * @return $this */ - abstract public function has(string $key, $value = null, Closure $scope = null); + abstract public function has(string $key, $value = null, ?Closure $scope = null); /** * Retrieve a prop within the current scope using "dot" notation. @@ -189,5 +189,5 @@ abstract public function has(string $key, $value = null, Closure $scope = null); * @param string|null $key * @return mixed */ - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); } diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 17ad09c1b5e4..61286498039d 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -793,7 +793,7 @@ public function assertJsonMissingExact(array $data) * @param array|null $responseData * @return $this */ - public function assertJsonStructure(array $structure = null, $responseData = null) + public function assertJsonStructure(?array $structure = null, $responseData = null) { $this->decodeResponseJson()->assertStructure($structure, $responseData); diff --git a/src/Illuminate/Validation/Factory.php b/src/Illuminate/Validation/Factory.php index 3d9d19035559..7f08e3a34cd2 100755 --- a/src/Illuminate/Validation/Factory.php +++ b/src/Illuminate/Validation/Factory.php @@ -87,7 +87,7 @@ class Factory implements FactoryContract * @param \Illuminate\Contracts\Container\Container|null $container * @return void */ - public function __construct(Translator $translator, Container $container = null) + public function __construct(Translator $translator, ?Container $container = null) { $this->container = $container; $this->translator = $translator; diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 9cbc91b2ff3e..422445592e11 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -516,7 +516,7 @@ public function validateWithBag(string $errorBag) * @param array|null $keys * @return \Illuminate\Support\ValidatedInput|array */ - public function safe(array $keys = null) + public function safe(?array $keys = null) { return is_array($keys) ? (new ValidatedInput($this->validated()))->only($keys) @@ -1353,7 +1353,7 @@ public function addCustomAttributes(array $customAttributes) * @param callable|null $formatter * @return $this */ - public function setImplicitAttributesFormatter(callable $formatter = null) + public function setImplicitAttributesFormatter(?callable $formatter = null) { $this->implicitAttributesFormatter = $formatter; diff --git a/src/Illuminate/View/Engines/CompilerEngine.php b/src/Illuminate/View/Engines/CompilerEngine.php index dca6a8710560..499c6837be0b 100755 --- a/src/Illuminate/View/Engines/CompilerEngine.php +++ b/src/Illuminate/View/Engines/CompilerEngine.php @@ -30,7 +30,7 @@ class CompilerEngine extends PhpEngine * @param \Illuminate\Filesystem\Filesystem|null $files * @return void */ - public function __construct(CompilerInterface $compiler, Filesystem $files = null) + public function __construct(CompilerInterface $compiler, ?Filesystem $files = null) { parent::__construct($files ?: new Filesystem); diff --git a/src/Illuminate/View/FileViewFinder.php b/src/Illuminate/View/FileViewFinder.php index a488a9d56b6f..24cd33c5ba7c 100755 --- a/src/Illuminate/View/FileViewFinder.php +++ b/src/Illuminate/View/FileViewFinder.php @@ -50,7 +50,7 @@ class FileViewFinder implements ViewFinderInterface * @param array|null $extensions * @return void */ - public function __construct(Filesystem $files, array $paths, array $extensions = null) + public function __construct(Filesystem $files, array $paths, ?array $extensions = null) { $this->files = $files; $this->paths = array_map([$this, 'resolvePath'], $paths); diff --git a/src/Illuminate/View/View.php b/src/Illuminate/View/View.php index a1969350c18a..d0ebec6845c3 100755 --- a/src/Illuminate/View/View.php +++ b/src/Illuminate/View/View.php @@ -85,7 +85,7 @@ public function __construct(Factory $factory, Engine $engine, $view, $path, $dat * * @throws \Throwable */ - public function render(callable $callback = null) + public function render(?callable $callback = null) { try { $contents = $this->renderContents(); diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index 4c68e2f00974..d6f4aac97a9a 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -745,7 +745,7 @@ public function testAllowIfAuthorizesCallbackResponseAllowed() public function testAllowsIfCallbackAcceptsGuestsWhenAuthenticated() { - $response = $this->getBasicGate()->allowIf(function (stdClass $user = null) { + $response = $this->getBasicGate()->allowIf(function (?stdClass $user = null) { return $user !== null; }); @@ -756,7 +756,7 @@ public function testAllowIfCallbackAcceptsGuestsWhenUnauthenticated() { $gate = $this->getBasicGate()->forUser(null); - $response = $gate->allowIf(function (stdClass $user = null) { + $response = $gate->allowIf(function (?stdClass $user = null) { return $user === null; }); @@ -883,7 +883,7 @@ public function testDenyIfAuthorizesCallbackResponseAllowed() public function testDenyIfCallbackAcceptsGuestsWhenAuthenticated() { - $response = $this->getBasicGate()->denyIf(function (stdClass $user = null) { + $response = $this->getBasicGate()->denyIf(function (?stdClass $user = null) { return $user === null; }); @@ -894,7 +894,7 @@ public function testDenyIfCallbackAcceptsGuestsWhenUnauthenticated() { $gate = $this->getBasicGate()->forUser(null); - $response = $gate->denyIf(function (stdClass $user = null) { + $response = $gate->denyIf(function (?stdClass $user = null) { return $user !== null; }); diff --git a/tests/Container/ContainerResolveNonInstantiableTest.php b/tests/Container/ContainerResolveNonInstantiableTest.php index 1f39322c40b8..5cc7be7a524d 100644 --- a/tests/Container/ContainerResolveNonInstantiableTest.php +++ b/tests/Container/ContainerResolveNonInstantiableTest.php @@ -36,7 +36,7 @@ class ParentClass */ public $i; - public function __construct(TestInterface $testObject = null, int $i = 0) + public function __construct(?TestInterface $testObject = null, int $i = 0) { $this->i = $i; } diff --git a/tests/Container/ContextualBindingTest.php b/tests/Container/ContextualBindingTest.php index 026a22f2ab82..3892a5834946 100644 --- a/tests/Container/ContextualBindingTest.php +++ b/tests/Container/ContextualBindingTest.php @@ -559,7 +559,7 @@ class ContainerTestContextWithOptionalInnerDependency { public $inner; - public function __construct(ContainerTestContextInjectOne $inner = null) + public function __construct(?ContainerTestContextInjectOne $inner = null) { $this->inner = $inner; } diff --git a/tests/Notifications/NotificationDatabaseChannelTest.php b/tests/Notifications/NotificationDatabaseChannelTest.php index 3a45a65a3fc5..d10722693ab6 100644 --- a/tests/Notifications/NotificationDatabaseChannelTest.php +++ b/tests/Notifications/NotificationDatabaseChannelTest.php @@ -57,10 +57,10 @@ public function testCustomizeTypeIsSentToDatabase() $notifiable = m::mock(); $notifiable->shouldReceive('routeNotificationFor->create')->with([ - 'id' => 1, - 'type' => 'MONTHLY', - 'data' => ['invoice_id' => 1], - 'read_at' => null, + 'id' => 1, + 'type' => 'MONTHLY', + 'data' => ['invoice_id' => 1], + 'read_at' => null, 'something' => 'else', ]); diff --git a/tests/Routing/RoutingRouteTest.php b/tests/Routing/RoutingRouteTest.php index 37799448bf28..48ac85291cd1 100644 --- a/tests/Routing/RoutingRouteTest.php +++ b/tests/Routing/RoutingRouteTest.php @@ -1663,7 +1663,7 @@ public function testImplicitBindingsWithOptionalParameterWithExistingKeyInUri() $router = $this->getRouter(); $router->get('foo/{bar?}', [ 'middleware' => SubstituteBindings::class, - 'uses' => function (RoutingTestUserModel $bar = null) { + 'uses' => function (?RoutingTestUserModel $bar = null) { $this->assertInstanceOf(RoutingTestUserModel::class, $bar); return $bar->value; @@ -1677,7 +1677,7 @@ public function testImplicitBindingsWithMissingModelHandledByMissing() $router = $this->getRouter(); $router->get('foo/{bar}', [ 'middleware' => SubstituteBindings::class, - 'uses' => function (RouteModelBindingNullStub $bar = null) { + 'uses' => function (?RouteModelBindingNullStub $bar = null) { $this->assertInstanceOf(RouteModelBindingNullStub::class, $bar); return $bar->first(); @@ -1698,7 +1698,7 @@ public function testImplicitBindingsWithOptionalParameterWithNoKeyInUri() $router = $this->getRouter(); $router->get('foo/{bar?}', [ 'middleware' => SubstituteBindings::class, - 'uses' => function (RoutingTestUserModel $bar = null) { + 'uses' => function (?RoutingTestUserModel $bar = null) { $this->assertNull($bar); }, ]); @@ -1712,7 +1712,7 @@ public function testImplicitBindingsWithOptionalParameterWithNonExistingKeyInUri $router = $this->getRouter(); $router->get('foo/{bar?}', [ 'middleware' => SubstituteBindings::class, - 'uses' => function (RoutingTestNonExistingUserModel $bar = null) { + 'uses' => function (?RoutingTestNonExistingUserModel $bar = null) { $this->fail('ModelNotFoundException was expected.'); }, ]); @@ -2052,7 +2052,7 @@ public function reversedArguments($two, $one) // } - public function withModels(Request $request, RoutingTestUserModel $user, $defaultNull = null, RoutingTestTeamModel $team = null) + public function withModels(Request $request, RoutingTestUserModel $user, $defaultNull = null, ?RoutingTestTeamModel $team = null) { // } diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index cdd865b8b470..76652dafa4b9 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -42,7 +42,7 @@ public function testInstance() public function testCarbonIsMacroableWhenNotCalledStatically() { - Carbon::macro('diffInDecades', function (Carbon $dt = null, $abs = true) { + Carbon::macro('diffInDecades', function (?Carbon $dt = null, $abs = true) { return (int) ($this->diffInYears($dt, $abs) / 10); }); diff --git a/tests/Support/SupportReflectsClosuresTest.php b/tests/Support/SupportReflectsClosuresTest.php index be8cba697c21..9486f3864a36 100644 --- a/tests/Support/SupportReflectsClosuresTest.php +++ b/tests/Support/SupportReflectsClosuresTest.php @@ -23,7 +23,7 @@ public function testReflectsClosures() // }); - $this->assertParameterTypes([null, ExampleParameter::class], function ($one, ExampleParameter $two = null) { + $this->assertParameterTypes([null, ExampleParameter::class], function ($one, ?ExampleParameter $two = null) { // }); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 924329d6a537..ce4437b04fff 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -4427,7 +4427,7 @@ public function testItemAwareSometimesAddingRules() { // ['users'] -> if users is not empty it must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]], ['users.*.name'=> 'required|string']); + $v = new Validator($trans, ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]], ['users.*.name' => 'required|string']); $v->sometimes(['users'], 'array', function ($i, $item) { return $item !== null; }); @@ -4435,7 +4435,7 @@ public function testItemAwareSometimesAddingRules() // ['users'] -> if users is null no rules will be applied $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['users' => null], ['users.*.name'=> 'required|string']); + $v = new Validator($trans, ['users' => null], ['users.*.name' => 'required|string']); $v->sometimes(['users'], 'array', function ($i, $item) { return (bool) $item; }); @@ -4443,7 +4443,7 @@ public function testItemAwareSometimesAddingRules() // ['company.users'] -> if users is not empty it must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name' => 'required|string']); $v->sometimes(['company.users'], 'array', function ($i, $item) { return $item->users !== null; }); @@ -4451,7 +4451,7 @@ public function testItemAwareSometimesAddingRules() // ['company.users'] -> if users is null no rules will be applied $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => null]], ['company'=> 'required', 'company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => null]], ['company' => 'required', 'company.users.*.name' => 'required|string']); $v->sometimes(['company.users'], 'array', function ($i, $item) { return (bool) $item->users; }); @@ -4459,7 +4459,7 @@ public function testItemAwareSometimesAddingRules() // ['company.*'] -> if users is not empty it must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name' => 'required|string']); $v->sometimes(['company.*'], 'array', function ($i, $item) { return $item !== null; }); @@ -4467,7 +4467,7 @@ public function testItemAwareSometimesAddingRules() // ['company.*'] -> if users is null no rules will be applied $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => null]], ['company'=> 'required', 'company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => null]], ['company' => 'required', 'company.users.*.name' => 'required|string']); $v->sometimes(['company.*'], 'array', function ($i, $item) { return (bool) $item; }); @@ -4475,7 +4475,7 @@ public function testItemAwareSometimesAddingRules() // ['users.*'] -> all nested array items in users must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]], ['users.*.name'=> 'required|string']); + $v = new Validator($trans, ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]], ['users.*.name' => 'required|string']); $v->sometimes(['users.*'], 'array', function ($i, $item) { return (bool) $item; }); @@ -4483,7 +4483,7 @@ public function testItemAwareSometimesAddingRules() // ['company.users.*'] -> all nested array items in users must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name' => 'required|string']); $v->sometimes(['company.users.*'], 'array', function () { return true; }); @@ -4491,7 +4491,7 @@ public function testItemAwareSometimesAddingRules() // ['company.*.*'] -> all nested array items in users must be validated as array $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name'=> 'required|string']); + $v = new Validator($trans, ['company' => ['users' => [['name' => 'Taylor'], ['name' => 'Abigail']]]], ['company.users.*.name' => 'required|string']); $v->sometimes(['company.*.*'], 'array', function ($i, $item) { return true; }); @@ -4596,7 +4596,7 @@ public function testItemAwareSometimesAddingRules() // ['attendee.*'] -> if attendee name is set, all other fields will be required as well $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['attendee' => ['name' => 'Taylor', 'title' => 'Creator of Laravel', 'type' => 'Developer']], ['attendee.*'=> 'string']); + $v = new Validator($trans, ['attendee' => ['name' => 'Taylor', 'title' => 'Creator of Laravel', 'type' => 'Developer']], ['attendee.*' => 'string']); $v->sometimes(['attendee.*'], 'required', function ($i, $item) { return (bool) $item; }); From 351dad53dd875c247fa2f96bacfd2f3c59d23a41 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 24 Mar 2025 11:35:39 +0000 Subject: [PATCH 044/158] Take out changes from 8.x that Taylor removed in 9.x caused by last merge --- .../Foundation/Bootstrap/HandleExceptions.php | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 1d32b2e2eeff..393be5c17866 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -7,9 +7,7 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; -use Monolog\Formatter\JsonFormatter; use Monolog\Handler\NullHandler; -use Monolog\Handler\SocketHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; @@ -53,10 +51,6 @@ public function bootstrap(Application $app) if (! $app->environment('testing')) { ini_set('display_errors', 'Off'); } - - if (laravel_cloud()) { - $this->configureCloudLogging($app); - } } /** @@ -299,34 +293,6 @@ protected function isFatal($type) return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]); } - /** - * Configure the Laravel Cloud log channels. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @return void - */ - protected function configureCloudLogging(Application $app) - { - $app['config']->set('logging.channels.stderr.formatter_with', [ - 'includeStacktraces' => true, - ]); - - $app['config']->set('logging.channels.laravel-cloud-socket', [ - 'driver' => 'monolog', - 'handler' => SocketHandler::class, - 'formatter' => JsonFormatter::class, - 'formatter_with' => [ - 'includeStacktraces' => true, - ], - 'with' => [ - 'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ?? - $_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ?? - 'unix:///tmp/cloud-init.sock', - 'persistent' => true, - ], - ]); - } - /** * Get an instance of the exception handler. * From 535733e16b2f3e8e53d5071f3d71d96dde786803 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 24 Mar 2025 11:41:31 +0000 Subject: [PATCH 045/158] StyleCI fixes --- src/Illuminate/Broadcasting/BroadcastManager.php | 4 ++-- src/Illuminate/Bus/Batchable.php | 2 +- .../Conditionable/Traits/Conditionable.php | 4 ++-- src/Illuminate/Contracts/Container/Container.php | 2 +- .../Database/Console/DatabaseInspectionCommand.php | 2 +- src/Illuminate/Database/Eloquent/Builder.php | 2 +- src/Illuminate/Database/Eloquent/Casts/Attribute.php | 2 +- .../Eloquent/Concerns/QueriesRelationships.php | 2 +- src/Illuminate/Database/Eloquent/Model.php | 2 +- .../Database/Eloquent/Relations/BelongsToMany.php | 2 +- .../Database/Eloquent/Relations/HasManyThrough.php | 4 ++-- src/Illuminate/Database/Query/Builder.php | 2 +- src/Illuminate/Foundation/Console/AboutCommand.php | 4 ++-- src/Illuminate/Http/Client/PendingRequest.php | 2 +- src/Illuminate/Http/Concerns/InteractsWithInput.php | 2 +- src/Illuminate/Mail/Mailables/Address.php | 2 +- src/Illuminate/Mail/Mailables/Content.php | 2 +- src/Illuminate/Mail/Mailables/Envelope.php | 12 ++++++------ src/Illuminate/Mail/Mailables/Headers.php | 2 +- src/Illuminate/Mail/Transport/ArrayTransport.php | 2 +- src/Illuminate/Mail/Transport/LogTransport.php | 2 +- src/Illuminate/Session/SymfonySessionDecorator.php | 4 ++-- src/Illuminate/Support/Facades/Bus.php | 2 +- src/Illuminate/Support/Str.php | 4 ++-- src/Illuminate/Support/Testing/Fakes/BusFake.php | 2 +- .../Validation/Concerns/FormatsMessages.php | 2 +- src/Illuminate/View/Compilers/BladeCompiler.php | 4 ++-- 27 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index 720769b8c0d9..8d52245c64ee 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -86,7 +86,7 @@ public function routes(?array $attributes = null) * @param array|null $attributes * @return void */ - public function userRoutes(array $attributes = null) + public function userRoutes(?array $attributes = null) { if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) { return; @@ -110,7 +110,7 @@ public function userRoutes(array $attributes = null) * @param array|null $attributes * @return void */ - public function channelRoutes(array $attributes = null) + public function channelRoutes(?array $attributes = null) { return $this->routes($attributes); } diff --git a/src/Illuminate/Bus/Batchable.php b/src/Illuminate/Bus/Batchable.php index 0b082700f8a2..e42bc5c3f59f 100644 --- a/src/Illuminate/Bus/Batchable.php +++ b/src/Illuminate/Bus/Batchable.php @@ -86,7 +86,7 @@ public function withFakeBatch(string $id = '', int $failedJobs = 0, array $failedJobIds = [], array $options = [], - CarbonImmutable $createdAt = null, + ?CarbonImmutable $createdAt = null, ?CarbonImmutable $cancelledAt = null, ?CarbonImmutable $finishedAt = null) { diff --git a/src/Illuminate/Conditionable/Traits/Conditionable.php b/src/Illuminate/Conditionable/Traits/Conditionable.php index 19307437cbaa..5e3194bbcb6a 100644 --- a/src/Illuminate/Conditionable/Traits/Conditionable.php +++ b/src/Illuminate/Conditionable/Traits/Conditionable.php @@ -18,7 +18,7 @@ trait Conditionable * @param (callable($this, TWhenParameter): TWhenReturnType)|null $default * @return $this|TWhenReturnType */ - public function when($value = null, callable $callback = null, callable $default = null) + public function when($value = null, ?callable $callback = null, ?callable $default = null) { $value = $value instanceof Closure ? $value($this) : $value; @@ -50,7 +50,7 @@ public function when($value = null, callable $callback = null, callable $default * @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $default * @return $this|TUnlessReturnType */ - public function unless($value = null, callable $callback = null, callable $default = null) + public function unless($value = null, ?callable $callback = null, ?callable $default = null) { $value = $value instanceof Closure ? $value($this) : $value; diff --git a/src/Illuminate/Contracts/Container/Container.php b/src/Illuminate/Contracts/Container/Container.php index 224d9d47d939..7e98b3952994 100644 --- a/src/Illuminate/Contracts/Container/Container.php +++ b/src/Illuminate/Contracts/Container/Container.php @@ -188,7 +188,7 @@ public function resolved($abstract); * @param \Closure|null $callback * @return void */ - public function beforeResolving($abstract, Closure $callback = null); + public function beforeResolving($abstract, ?Closure $callback = null); /** * Register a new resolving callback. diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php index e3391a09e6d4..ae8ea88d09d8 100644 --- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php +++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php @@ -52,7 +52,7 @@ abstract class DatabaseInspectionCommand extends Command * @param \Illuminate\Support\Composer|null $composer * @return void */ - public function __construct(Composer $composer = null) + public function __construct(?Composer $composer = null) { parent::__construct(); diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 16f4b121c91f..028ab0c3008e 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -518,7 +518,7 @@ public function findOrNew($id, $columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|mixed */ - public function findOr($id, $columns = ['*'], Closure $callback = null) + public function findOr($id, $columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Database/Eloquent/Casts/Attribute.php b/src/Illuminate/Database/Eloquent/Casts/Attribute.php index d56df6e5d5a2..4fe2d807b690 100644 --- a/src/Illuminate/Database/Eloquent/Casts/Attribute.php +++ b/src/Illuminate/Database/Eloquent/Casts/Attribute.php @@ -52,7 +52,7 @@ public function __construct(?callable $get = null, ?callable $set = null) * @param callable|null $set * @return static */ - public static function make(callable $get = null, callable $set = null): static + public static function make(?callable $get = null, ?callable $set = null): static { return new static($get, $set); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index dbc979228299..2d5cc39549f4 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -163,7 +163,7 @@ public function whereHas($relation, ?Closure $callback = null, $operator = '>=', * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ - public function withWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + public function withWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count) ->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation); diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 17bb7921237f..d5929d896524 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -1741,7 +1741,7 @@ public function replicate(?array $except = null) * @param array|null $except * @return static */ - public function replicateQuietly(array $except = null) + public function replicateQuietly(?array $except = null) { return static::withoutEvents(fn () => $this->replicate($except)); } diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 8c10e9fefe80..f858507b2cf4 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -727,7 +727,7 @@ public function findOrFail($id, $columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed */ - public function findOr($id, $columns = ['*'], Closure $callback = null) + public function findOr($id, $columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index c6c57d1d193b..b950c9d5b9a1 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -309,7 +309,7 @@ public function firstOrFail($columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ - public function firstOr($columns = ['*'], Closure $callback = null) + public function firstOr($columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; @@ -396,7 +396,7 @@ public function findOrFail($id, $columns = ['*']) * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed */ - public function findOr($id, $columns = ['*'], Closure $callback = null) + public function findOr($id, $columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 37d68a718bff..de23e811f712 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -2624,7 +2624,7 @@ public function find($id, $columns = ['*']) * @param \Closure|null $callback * @return mixed|static */ - public function findOr($id, $columns = ['*'], Closure $callback = null) + public function findOr($id, $columns = ['*'], ?Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; diff --git a/src/Illuminate/Foundation/Console/AboutCommand.php b/src/Illuminate/Foundation/Console/AboutCommand.php index c6a38ce9cb4d..c0d37e9df646 100644 --- a/src/Illuminate/Foundation/Console/AboutCommand.php +++ b/src/Illuminate/Foundation/Console/AboutCommand.php @@ -228,7 +228,7 @@ protected function hasPhpFiles(string $path): bool * @param string|null $value * @return void */ - public static function add(string $section, $data, string $value = null) + public static function add(string $section, $data, ?string $value = null) { static::$customDataResolvers[] = fn () => static::addToSection($section, $data, $value); } @@ -241,7 +241,7 @@ public static function add(string $section, $data, string $value = null) * @param string|null $value * @return void */ - protected static function addToSection(string $section, $data, string $value = null) + protected static function addToSection(string $section, $data, ?string $value = null) { if (is_array($data)) { foreach ($data as $key => $value) { diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 437a80ca18ee..5877434dd5f2 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -619,7 +619,7 @@ public function beforeSending($callback) * @param callable|null $callback * @return $this */ - public function throw(callable $callback = null) + public function throw(?callable $callback = null) { $this->throwCallback = $callback ?: fn () => null; diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index 930a7134dfde..11b8adfed971 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -232,7 +232,7 @@ public function missing($key) * @param callable|null $default * @return $this|mixed */ - public function whenMissing($key, callable $callback, callable $default = null) + public function whenMissing($key, callable $callback, ?callable $default = null) { if ($this->missing($key)) { return $callback(data_get($this->all(), $key)) ?: $this; diff --git a/src/Illuminate/Mail/Mailables/Address.php b/src/Illuminate/Mail/Mailables/Address.php index be54a24a7413..7a9ed2aa66cd 100644 --- a/src/Illuminate/Mail/Mailables/Address.php +++ b/src/Illuminate/Mail/Mailables/Address.php @@ -25,7 +25,7 @@ class Address * @param string|null $name * @return void */ - public function __construct(string $address, string $name = null) + public function __construct(string $address, ?string $name = null) { $this->address = $address; $this->name = $name; diff --git a/src/Illuminate/Mail/Mailables/Content.php b/src/Illuminate/Mail/Mailables/Content.php index 319cfefa744c..63c9392b3b30 100644 --- a/src/Illuminate/Mail/Mailables/Content.php +++ b/src/Illuminate/Mail/Mailables/Content.php @@ -64,7 +64,7 @@ class Content * * @named-arguments-supported */ - public function __construct(string $view = null, string $html = null, string $text = null, $markdown = null, array $with = [], string $htmlString = null) + public function __construct(?string $view = null, ?string $html = null, ?string $text = null, $markdown = null, array $with = [], ?string $htmlString = null) { $this->view = $view; $this->html = $html; diff --git a/src/Illuminate/Mail/Mailables/Envelope.php b/src/Illuminate/Mail/Mailables/Envelope.php index 7d6c4b1ee55f..05b165a46971 100644 --- a/src/Illuminate/Mail/Mailables/Envelope.php +++ b/src/Illuminate/Mail/Mailables/Envelope.php @@ -89,7 +89,7 @@ class Envelope * * @named-arguments-supported */ - public function __construct(Address|string $from = null, $to = [], $cc = [], $bcc = [], $replyTo = [], string $subject = null, array $tags = [], array $metadata = [], Closure|array $using = []) + public function __construct(Address|string|null $from = null, $to = [], $cc = [], $bcc = [], $replyTo = [], ?string $subject = null, array $tags = [], array $metadata = [], Closure|array $using = []) { $this->from = is_string($from) ? new Address($from) : $from; $this->to = $this->normalizeAddresses($to); @@ -266,7 +266,7 @@ public function using(Closure $callback) * @param string|null $name * @return bool */ - public function isFrom(string $address, string $name = null) + public function isFrom(string $address, ?string $name = null) { if (is_null($name)) { return $this->from->address === $address; @@ -283,7 +283,7 @@ public function isFrom(string $address, string $name = null) * @param string|null $name * @return bool */ - public function hasTo(string $address, string $name = null) + public function hasTo(string $address, ?string $name = null) { return $this->hasRecipient($this->to, $address, $name); } @@ -295,7 +295,7 @@ public function hasTo(string $address, string $name = null) * @param string|null $name * @return bool */ - public function hasCc(string $address, string $name = null) + public function hasCc(string $address, ?string $name = null) { return $this->hasRecipient($this->cc, $address, $name); } @@ -307,7 +307,7 @@ public function hasCc(string $address, string $name = null) * @param string|null $name * @return bool */ - public function hasBcc(string $address, string $name = null) + public function hasBcc(string $address, ?string $name = null) { return $this->hasRecipient($this->bcc, $address, $name); } @@ -319,7 +319,7 @@ public function hasBcc(string $address, string $name = null) * @param string|null $name * @return bool */ - public function hasReplyTo(string $address, string $name = null) + public function hasReplyTo(string $address, ?string $name = null) { return $this->hasRecipient($this->replyTo, $address, $name); } diff --git a/src/Illuminate/Mail/Mailables/Headers.php b/src/Illuminate/Mail/Mailables/Headers.php index 87cee52b4768..0428f250416b 100644 --- a/src/Illuminate/Mail/Mailables/Headers.php +++ b/src/Illuminate/Mail/Mailables/Headers.php @@ -40,7 +40,7 @@ class Headers * * @named-arguments-supported */ - public function __construct(string $messageId = null, array $references = [], array $text = []) + public function __construct(?string $messageId = null, array $references = [], array $text = []) { $this->messageId = $messageId; $this->references = $references; diff --git a/src/Illuminate/Mail/Transport/ArrayTransport.php b/src/Illuminate/Mail/Transport/ArrayTransport.php index dc26ed69d90b..02ba21d90c70 100644 --- a/src/Illuminate/Mail/Transport/ArrayTransport.php +++ b/src/Illuminate/Mail/Transport/ArrayTransport.php @@ -30,7 +30,7 @@ public function __construct() /** * {@inheritdoc} */ - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { return $this->messages[] = new SentMessage($message, $envelope ?? Envelope::create($message)); } diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index d9ec8ac09d7e..cd2db7e771c3 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -31,7 +31,7 @@ public function __construct(LoggerInterface $logger) /** * {@inheritdoc} */ - public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage { $this->logger->debug($message->toString()); diff --git a/src/Illuminate/Session/SymfonySessionDecorator.php b/src/Illuminate/Session/SymfonySessionDecorator.php index 02034910bb9e..1dd8455df252 100644 --- a/src/Illuminate/Session/SymfonySessionDecorator.php +++ b/src/Illuminate/Session/SymfonySessionDecorator.php @@ -75,7 +75,7 @@ public function setName(string $name) /** * {@inheritdoc} */ - public function invalidate(int $lifetime = null): bool + public function invalidate(?int $lifetime = null): bool { $this->store->invalidate(); @@ -85,7 +85,7 @@ public function invalidate(int $lifetime = null): bool /** * {@inheritdoc} */ - public function migrate(bool $destroy = false, int $lifetime = null): bool + public function migrate(bool $destroy = false, ?int $lifetime = null): bool { $this->store->migrate($destroy); diff --git a/src/Illuminate/Support/Facades/Bus.php b/src/Illuminate/Support/Facades/Bus.php index 62ac0588b106..197c45c5008f 100644 --- a/src/Illuminate/Support/Facades/Bus.php +++ b/src/Illuminate/Support/Facades/Bus.php @@ -58,7 +58,7 @@ class Bus extends Facade * @param \Illuminate\Bus\BatchRepository|null $batchRepository * @return \Illuminate\Support\Testing\Fakes\BusFake */ - public static function fake($jobsToFake = [], BatchRepository $batchRepository = null) + public static function fake($jobsToFake = [], ?BatchRepository $batchRepository = null) { static::swap($fake = new BusFake(static::getFacadeRoot(), $jobsToFake, $batchRepository)); diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 3bbbb7ebf4da..f97c5247d5a2 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -752,7 +752,7 @@ public static function random($length = 16) * @param callable|null $factory * @return void */ - public static function createRandomStringsUsing(callable $factory = null) + public static function createRandomStringsUsing(?callable $factory = null) { static::$randomStringFactory = $factory; } @@ -1316,7 +1316,7 @@ public static function createUuidsUsingSequence(array $sequence, $whenMissing = * @param \Closure|null $callback * @return \Ramsey\Uuid\UuidInterface */ - public static function freezeUuids(Closure $callback = null) + public static function freezeUuids(?Closure $callback = null) { $uuid = Str::uuid(); diff --git a/src/Illuminate/Support/Testing/Fakes/BusFake.php b/src/Illuminate/Support/Testing/Fakes/BusFake.php index 9da1fac7ce61..9c5746d3aa0c 100644 --- a/src/Illuminate/Support/Testing/Fakes/BusFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BusFake.php @@ -79,7 +79,7 @@ class BusFake implements QueueingDispatcher * @param \Illuminate\Bus\BatchRepository|null $batchRepository * @return void */ - public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [], BatchRepository $batchRepository = null) + public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [], ?BatchRepository $batchRepository = null) { $this->dispatcher = $dispatcher; $this->jobsToFake = Arr::wrap($jobsToFake); diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index 0758819daaad..6d0c5b6f5b34 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -362,7 +362,7 @@ protected function replacePositionPlaceholder($message, $attribute) * @param \Closure|null $modifier * @return string */ - protected function replaceIndexOrPositionPlaceholder($message, $attribute, $placeholder, Closure $modifier = null) + protected function replaceIndexOrPositionPlaceholder($message, $attribute, $placeholder, ?Closure $modifier = null) { $segments = explode('.', $attribute); diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php index 9edbc5cecc8a..f4fc53989cc8 100644 --- a/src/Illuminate/View/Compilers/BladeCompiler.php +++ b/src/Illuminate/View/Compilers/BladeCompiler.php @@ -793,7 +793,7 @@ public function getClassComponentAliases() * @param string|null $prefix * @return void */ - public function anonymousComponentPath(string $path, string $prefix = null) + public function anonymousComponentPath(string $path, ?string $prefix = null) { $prefixHash = md5($prefix ?: $path); @@ -815,7 +815,7 @@ public function anonymousComponentPath(string $path, string $prefix = null) * @param string|null $prefix * @return void */ - public function anonymousComponentNamespace(string $directory, string $prefix = null) + public function anonymousComponentNamespace(string $directory, ?string $prefix = null) { $prefix ??= $directory; From 37455bbd9ece2ab48443b4ad2af85abf2140e326 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 24 Mar 2025 11:51:20 +0000 Subject: [PATCH 046/158] Update .styleci.yml --- .styleci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.styleci.yml b/.styleci.yml index e841dce2d3f5..aed1fe818f33 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,8 +1,6 @@ php: preset: laravel version: 8.1 - enabled: - - nullable_type_declarations finder: not-name: - bad-syntax-strategy.php @@ -10,4 +8,5 @@ js: finder: not-name: - webpack.mix.js + css: true From 46f97c4bad306ab36dceb1799eb7d284804aa0d4 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 24 Mar 2025 11:58:51 +0000 Subject: [PATCH 047/158] StyleCI fixes --- src/Illuminate/Http/Middleware/TrustProxies.php | 10 +++++----- src/Illuminate/Support/Uri.php | 2 +- tests/Database/DatabaseEloquentInverseRelationTest.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 0a13ff79a361..ecf35bf6dadf 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -20,11 +20,11 @@ class TrustProxies * @var int */ protected $headers = Request::HEADER_X_FORWARDED_FOR | - Request::HEADER_X_FORWARDED_HOST | - Request::HEADER_X_FORWARDED_PORT | - Request::HEADER_X_FORWARDED_PROTO | - Request::HEADER_X_FORWARDED_PREFIX | - Request::HEADER_X_FORWARDED_AWS_ELB; + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_PREFIX | + Request::HEADER_X_FORWARDED_AWS_ELB; /** * The proxies that have been configured to always be trusted. diff --git a/src/Illuminate/Support/Uri.php b/src/Illuminate/Support/Uri.php index c25814d54220..f137fc25b985 100644 --- a/src/Illuminate/Support/Uri.php +++ b/src/Illuminate/Support/Uri.php @@ -195,7 +195,7 @@ public function withHost(Stringable|string $host): static /** * Specify the port of the URI. */ - public function withPort(int|null $port): static + public function withPort(?int $port): static { return new static($this->uri->withPort($port)); } diff --git a/tests/Database/DatabaseEloquentInverseRelationTest.php b/tests/Database/DatabaseEloquentInverseRelationTest.php index f860e297a410..5c47ee5167f7 100755 --- a/tests/Database/DatabaseEloquentInverseRelationTest.php +++ b/tests/Database/DatabaseEloquentInverseRelationTest.php @@ -378,7 +378,7 @@ public function exposeGetPossibleInverseRelations(): array return $this->getPossibleInverseRelations(); } - public function exposeGuessInverseRelation(): string|null + public function exposeGuessInverseRelation(): ?string { return $this->guessInverseRelation(); } From b5ce2116d42a8d297d5cb0b1d7d6b57b09e76f78 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 24 Mar 2025 22:53:50 +0800 Subject: [PATCH 048/158] [11.x] Fix `Illuminate\Support\EncodedHtmlString` from causing breaking change (#55149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update src/Illuminate/Mail/Markdown.php Co-authored-by: Sebastian Hädrich <11225821+shaedrich@users.noreply.github.com> --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Sebastian Hädrich <11225821+shaedrich@users.noreply.github.com> --- src/Illuminate/Mail/Markdown.php | 31 ++++++++-- .../resources/views/html/button.blade.php | 2 +- .../resources/views/html/header.blade.php | 2 +- .../resources/views/html/layout.blade.php | 10 ++-- .../resources/views/html/message.blade.php | 4 +- .../Mail/resources/views/html/panel.blade.php | 2 +- .../Fixtures/message-with-template.blade.php | 4 ++ tests/Integration/Mail/MailableTest.php | 57 +++++++++++++++++++ tests/Integration/Mail/MarkdownParserTest.php | 2 +- 9 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 tests/Integration/Mail/Fixtures/message-with-template.blade.php diff --git a/src/Illuminate/Mail/Markdown.php b/src/Illuminate/Mail/Markdown.php index e8ec2defc4c4..54c9c2ece372 100644 --- a/src/Illuminate/Mail/Markdown.php +++ b/src/Illuminate/Mail/Markdown.php @@ -69,9 +69,25 @@ public function render($view, array $data = [], $inliner = null) $contents = $bladeCompiler->usingEchoFormat( 'new \Illuminate\Support\EncodedHtmlString(%s)', function () use ($view, $data) { - return $this->view->replaceNamespace( - 'mail', $this->htmlComponentPaths() - )->make($view, $data)->render(); + EncodedHtmlString::encodeUsing(function ($value) { + $replacements = [ + '[' => '\[', + '<' => '<', + '>' => '>', + ]; + + return str_replace(array_keys($replacements), array_values($replacements), $value); + }); + + try { + $contents = $this->view->replaceNamespace( + 'mail', $this->htmlComponentPaths() + )->make($view, $data)->render(); + } finally { + EncodedHtmlString::flushState(); + } + + return $contents; } ); @@ -84,7 +100,7 @@ function () use ($view, $data) { } return new HtmlString(($inliner ?: new CssToInlineStyles)->convert( - $contents, $this->view->make($theme, $data)->render() + str_replace('\[', '[', $contents), $this->view->make($theme, $data)->render() )); } @@ -112,10 +128,15 @@ public function renderText($view, array $data = []) * Parse the given Markdown text into HTML. * * @param string $text + * @param bool $encoded * @return \Illuminate\Support\HtmlString */ - public static function parse($text) + public static function parse($text, bool $encoded = false) { + if ($encoded === false) { + return new HtmlString(static::converter()->convert($text)->getContent()); + } + EncodedHtmlString::encodeUsing(function ($value) { $replacements = [ '[' => '\[', diff --git a/src/Illuminate/Mail/resources/views/html/button.blade.php b/src/Illuminate/Mail/resources/views/html/button.blade.php index 4a9bf7d00495..050e969d2130 100644 --- a/src/Illuminate/Mail/resources/views/html/button.blade.php +++ b/src/Illuminate/Mail/resources/views/html/button.blade.php @@ -12,7 +12,7 @@
-{{ $slot }} +{!! $slot !!}
diff --git a/src/Illuminate/Mail/resources/views/html/header.blade.php b/src/Illuminate/Mail/resources/views/html/header.blade.php index 56197f8d23f3..c47a260c56b2 100644 --- a/src/Illuminate/Mail/resources/views/html/header.blade.php +++ b/src/Illuminate/Mail/resources/views/html/header.blade.php @@ -5,7 +5,7 @@ @if (trim($slot) === 'Laravel') @else -{{ $slot }} +{!! $slot !!} @endif diff --git a/src/Illuminate/Mail/resources/views/html/layout.blade.php b/src/Illuminate/Mail/resources/views/html/layout.blade.php index d31a01de8630..0fa6b82f72b2 100644 --- a/src/Illuminate/Mail/resources/views/html/layout.blade.php +++ b/src/Illuminate/Mail/resources/views/html/layout.blade.php @@ -23,7 +23,7 @@ } } -{{ $head ?? '' }} +{!! $head ?? '' !!} @@ -31,7 +31,7 @@ -{{ $header ?? '' }} +{!! $header ?? '' !!} @@ -40,16 +40,16 @@ -{{ $footer ?? '' }} +{!! $footer ?? '' !!} diff --git a/src/Illuminate/Mail/resources/views/html/message.blade.php b/src/Illuminate/Mail/resources/views/html/message.blade.php index 1a874fc26de5..f1e815f32a41 100644 --- a/src/Illuminate/Mail/resources/views/html/message.blade.php +++ b/src/Illuminate/Mail/resources/views/html/message.blade.php @@ -7,13 +7,13 @@ {{-- Body --}} -{{ $slot }} +{!! $slot !!} {{-- Subcopy --}} @isset($subcopy) -{{ $subcopy }} +{!! $subcopy !!}} @endisset diff --git a/src/Illuminate/Mail/resources/views/html/panel.blade.php b/src/Illuminate/Mail/resources/views/html/panel.blade.php index 2975a60a021e..812db7c08e77 100644 --- a/src/Illuminate/Mail/resources/views/html/panel.blade.php +++ b/src/Illuminate/Mail/resources/views/html/panel.blade.php @@ -4,7 +4,7 @@
-{{ Illuminate\Mail\Markdown::parse($slot) }} +{!! Illuminate\Mail\Markdown::parse($slot) !!}
diff --git a/tests/Integration/Mail/Fixtures/message-with-template.blade.php b/tests/Integration/Mail/Fixtures/message-with-template.blade.php new file mode 100644 index 000000000000..9c53cef7e1bb --- /dev/null +++ b/tests/Integration/Mail/Fixtures/message-with-template.blade.php @@ -0,0 +1,4 @@ +@component('mail::message') +*Hi* {{ $user->name }} + +@endcomponent diff --git a/tests/Integration/Mail/MailableTest.php b/tests/Integration/Mail/MailableTest.php index 339ebb2422d7..4ff0f539b6ad 100644 --- a/tests/Integration/Mail/MailableTest.php +++ b/tests/Integration/Mail/MailableTest.php @@ -2,14 +2,20 @@ namespace Illuminate\Tests\Integration\Mail; +use Illuminate\Foundation\Auth\User; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Orchestra\Testbench\Attributes\WithMigration; +use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; class MailableTest extends TestCase { + use LazilyRefreshDatabase; + /** {@inheritdoc} */ #[\Override] protected function defineEnvironment($app) @@ -69,4 +75,55 @@ public static function markdownEncodedDataProvider() 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', ]; } + + #[WithMigration] + #[DataProvider('markdownEncodedTemplateDataProvider')] + public function testItCanAssertMarkdownEncodedStringUsingTemplate($given, $expected) + { + $user = UserFactory::new()->create([ + 'name' => $given, + ]); + + $mailable = new class($user) extends Mailable + { + public $theme = 'taylor'; + + public function __construct(public User $user) + { + // + } + + public function build() + { + return $this->markdown('message-with-template'); + } + }; + + $mailable->assertSeeInHtml($expected, false); + } + + public static function markdownEncodedTemplateDataProvider() + { + yield ['[Laravel](https://laravel.com)', 'Hi [Laravel](https://laravel.com)']; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + 'Hi ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'Hi Visit https://laravel.com/docs to browse the documentation', + ]; + + yield [ + 'Visit to browse the documentation', + 'Hi Visit <https://laravel.com/docs> to browse the documentation', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'Hi Visit <span>https://laravel.com/docs</span> to browse the documentation', + ]; + } } diff --git a/tests/Integration/Mail/MarkdownParserTest.php b/tests/Integration/Mail/MarkdownParserTest.php index d21602c9ad00..6669fe038ba3 100644 --- a/tests/Integration/Mail/MarkdownParserTest.php +++ b/tests/Integration/Mail/MarkdownParserTest.php @@ -24,7 +24,7 @@ public function testItCanParseMarkdownString($given, $expected) #[DataProvider('markdownEncodedDataProvider')] public function testItCanParseMarkdownEncodedString($given, $expected) { - tap(Markdown::parse($given), function ($html) use ($expected) { + tap(Markdown::parse($given, encoded: true), function ($html) use ($expected) { $this->assertInstanceOf(HtmlString::class, $html); $this->assertStringEqualsStringIgnoringLineEndings($expected.PHP_EOL, (string) $html); From e65c9b79ed1365ddbfd91585c8c6066cade7ee2d Mon Sep 17 00:00:00 2001 From: Alies Lapatsin <5278175+alies-dev@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:09:23 +0400 Subject: [PATCH 049/158] [11.x] Respect custom path for cached views by the `AboutCommand` (#55179) * Respect custom view path by AboutCommand * Add a test for the custom VIEW_COMPILED_PATH --- src/Illuminate/Foundation/Console/AboutCommand.php | 2 +- .../Foundation/Console/AboutCommandTest.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Console/AboutCommand.php b/src/Illuminate/Foundation/Console/AboutCommand.php index b7d39b775c43..a02ba3b668db 100644 --- a/src/Illuminate/Foundation/Console/AboutCommand.php +++ b/src/Illuminate/Foundation/Console/AboutCommand.php @@ -183,7 +183,7 @@ protected function gatherApplicationInformation() 'Config' => static::format($this->laravel->configurationIsCached(), console: $formatCachedStatus), 'Events' => static::format($this->laravel->eventsAreCached(), console: $formatCachedStatus), 'Routes' => static::format($this->laravel->routesAreCached(), console: $formatCachedStatus), - 'Views' => static::format($this->hasPhpFiles($this->laravel->storagePath('framework/views')), console: $formatCachedStatus), + 'Views' => static::format($this->hasPhpFiles(config('view.compiled')), console: $formatCachedStatus), ]); static::addToSection('Drivers', fn () => array_filter([ diff --git a/tests/Integration/Foundation/Console/AboutCommandTest.php b/tests/Integration/Foundation/Console/AboutCommandTest.php index 9741d174861a..7eb15a12bd7e 100644 --- a/tests/Integration/Foundation/Console/AboutCommandTest.php +++ b/tests/Integration/Foundation/Console/AboutCommandTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Integration\Foundation\Console; use Illuminate\Testing\Assert; +use Orchestra\Testbench\Attributes\WithEnv; use Orchestra\Testbench\TestCase; use function Orchestra\Testbench\remote; @@ -40,4 +41,16 @@ public function testItCanDisplayAboutCommandAsJson() ], $output['drivers']); }); } + + #[WithEnv('VIEW_COMPILED_PATH', __DIR__.'/../../View/templates')] + public function testItRespectsCustomPathForCompiledViews(): void + { + $process = remote('about --json', ['APP_ENV' => 'local'])->mustRun(); + + tap(json_decode($process->getOutput(), true), static function (array $output) { + Assert::assertArraySubset([ + 'views' => true, + ], $output['cache']); + }); + } } From 5eb8a7ce1fdf5f012d3f4d514b95c3932bb498e8 Mon Sep 17 00:00:00 2001 From: laserhybiz <100562257+laserhybiz@users.noreply.github.com> Date: Fri, 28 Mar 2025 19:08:47 +0300 Subject: [PATCH 050/158] [11.x] Include all invisible characters in Str::trim (#54281) * Include all invisible characters in Str::trim * Use constant --- src/Illuminate/Support/Str.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 5acd66529544..4f35b843b8c0 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -23,6 +23,13 @@ class Str { use Macroable; + /** + * The list of characters that are considered "invisible" in strings. + * + * @var string + */ + const INVISIBLE_CHARACTERS = '\x{0009}\x{0020}\x{00A0}\x{00AD}\x{034F}\x{061C}\x{115F}\x{1160}\x{17B4}\x{17B5}\x{180E}\x{2000}\x{2001}\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}\x{200A}\x{200B}\x{200C}\x{200D}\x{200E}\x{200F}\x{202F}\x{205F}\x{2060}\x{2061}\x{2062}\x{2063}\x{2064}\x{2065}\x{206A}\x{206B}\x{206C}\x{206D}\x{206E}\x{206F}\x{3000}\x{2800}\x{3164}\x{FEFF}\x{FFA0}\x{1D159}\x{1D173}\x{1D174}\x{1D175}\x{1D176}\x{1D177}\x{1D178}\x{1D179}\x{1D17A}\x{E0020}'; + /** * The cache of snake-cased words. * @@ -1538,7 +1545,7 @@ public static function trim($value, $charlist = null) if ($charlist === null) { $trimDefaultCharacters = " \n\r\t\v\0"; - return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}'.$trimDefaultCharacters.']+|[\s\x{FEFF}\x{200B}\x{200E}'.$trimDefaultCharacters.']+$~u', '', $value) ?? trim($value); + return preg_replace('~^[\s'.self::INVISIBLE_CHARACTERS.$trimDefaultCharacters.']+|[\s'.self::INVISIBLE_CHARACTERS.$trimDefaultCharacters.']+$~u', '', $value) ?? trim($value); } return trim($value, $charlist); @@ -1556,7 +1563,7 @@ public static function ltrim($value, $charlist = null) if ($charlist === null) { $ltrimDefaultCharacters = " \n\r\t\v\0"; - return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}'.$ltrimDefaultCharacters.']+~u', '', $value) ?? ltrim($value); + return preg_replace('~^[\s'.self::INVISIBLE_CHARACTERS.$ltrimDefaultCharacters.']+~u', '', $value) ?? ltrim($value); } return ltrim($value, $charlist); @@ -1574,7 +1581,7 @@ public static function rtrim($value, $charlist = null) if ($charlist === null) { $rtrimDefaultCharacters = " \n\r\t\v\0"; - return preg_replace('~[\s\x{FEFF}\x{200B}\x{200E}'.$rtrimDefaultCharacters.']+$~u', '', $value) ?? rtrim($value); + return preg_replace('~[\s'.self::INVISIBLE_CHARACTERS.$rtrimDefaultCharacters.']+$~u', '', $value) ?? rtrim($value); } return rtrim($value, $charlist); From 7bf607bd6f9c919e08de96582170fe64a77037dc Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 7 Apr 2025 10:35:28 +0800 Subject: [PATCH 051/158] [11.x] Test Improvements (#55302) Signed-off-by: Mior Muhammad Zaki --- .github/workflows/tests.yml | 31 +++++++++------------------- tests/Integration/View/BladeTest.php | 22 ++++++++++++++------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70b4ff968674..ed1199b07aa9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,11 +40,13 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3, 8.4] - phpunit: ['10.5.35', '11.3.2', '12.0.0'] + phpunit: ['10.5.35', '11.3.2', '12.0.0', '12.1.0'] stability: [prefer-lowest, prefer-stable] exclude: - php: 8.2 phpunit: '12.0.0' + - php: 8.2 + phpunit: '12.1.0' name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} @@ -76,19 +78,12 @@ jobs: shell: bash if: matrix.php >= 8.4 - - name: Set PHPUnit - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update - - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 - command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --with="phpunit/phpunit:~${{ matrix.phpunit }}" - name: Execute tests run: vendor/bin/phpunit --display-deprecation ${{ matrix.stability == 'prefer-stable' && '--fail-on-deprecation' || '' }} @@ -115,11 +110,13 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3, 8.4] - phpunit: ['10.5', '11.0.1'] + phpunit: ['10.5.35', '11.3.2', '12.0.0', '12.1.0'] stability: [prefer-lowest, prefer-stable] exclude: - - php: 8.4 - stability: prefer-lowest + - php: 8.2 + phpunit: '12.0.0' + - php: 8.2 + phpunit: '12.1.0' name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} - Windows @@ -143,20 +140,12 @@ jobs: - name: Set Framework version run: composer config version "11.x-dev" - - name: Set PHPUnit - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update - shell: bash - - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 - command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --with="phpunit/phpunit:~${{ matrix.phpunit }}" - name: Execute tests run: vendor/bin/phpunit diff --git a/tests/Integration/View/BladeTest.php b/tests/Integration/View/BladeTest.php index 6495175337c4..1acb38372b86 100644 --- a/tests/Integration/View/BladeTest.php +++ b/tests/Integration/View/BladeTest.php @@ -11,8 +11,20 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; +use function Orchestra\Testbench\artisan; +use function Orchestra\Testbench\phpunit_version_compare; + class BladeTest extends TestCase { + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + artisan($this, 'view:clear'); + + parent::tearDown(); + } + public function test_rendering_blade_string() { $this->assertSame('Hello Taylor', Blade::render('Hello {{ $name }}', ['name' => 'Taylor'])); @@ -33,8 +45,8 @@ public function test_rendering_blade_long_maxpathlen_string_with_exact_length() // The PHP_MAXPATHLEN restriction is only active, if // open_basedir is set and active. Otherwise, the check // for the PHP_MAXPATHLEN is not active. - if (ini_get('open_basedir') === '') { - $openBaseDir = windows_os() ? explode('\\', __DIR__)[0].'\\'.';'.sys_get_temp_dir() : '/'; + if (ini_get('open_basedir') === '' && phpunit_version_compare('12.1.0', '<')) { + $openBaseDir = explode(DIRECTORY_SEPARATOR, __DIR__)[0].DIRECTORY_SEPARATOR.PATH_SEPARATOR.sys_get_temp_dir(); $iniSet = ini_set( 'open_basedir', $openBaseDir @@ -197,8 +209,6 @@ public function test_bound_name_attribute_can_be_used_if_using_short_slot_names_ public function testViewCacheCommandHandlesConfiguredBladeExtensions() { - $this->artisan('view:clear'); - View::addExtension('sh', 'blade'); $this->artisan('view:cache'); @@ -206,10 +216,10 @@ public function testViewCacheCommandHandlesConfiguredBladeExtensions() $found = collect($compiledFiles) ->contains(fn (SplFileInfo $file) => str_contains($file->getContents(), 'echo "" > output.log')); $this->assertTrue($found); - - $this->artisan('view:clear'); } + /** {@inheritdoc} */ + #[\Override] protected function defineEnvironment($app) { $app['config']->set('view.paths', [__DIR__.'/templates']); From b0a87fbd13c0b05227a00da7968701f4274e3195 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:45:24 +0000 Subject: [PATCH 052/158] Update version to v11.44.3 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index be7345c8ef28..eeedee8f3331 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.2'; + const VERSION = '11.44.3'; /** * The base path for the Laravel installation. From a784ef91fdae0c993317f4c7386bbe5ca42417df Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:47:14 +0000 Subject: [PATCH 053/158] Update CHANGELOG --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfd4dc1e9a4..342bcb405aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.2...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.3...11.x) + +## [v11.44.3](https://github.com/laravel/framework/compare/v11.44.2...v11.44.3) - 2025-04-23 + +* [10.x] Refine error messages for detecting lost connections (Debian bookworm compatibility) by [@mfn](https://github.com/mfn) in https://github.com/laravel/framework/pull/53794 +* [10.x] Bump minimum `league/commonmark` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53829 +* [10.x] Backport 11.x PHP 8.4 fix for str_getcsv deprecation by [@aka-tpayne](https://github.com/aka-tpayne) in https://github.com/laravel/framework/pull/54074 +* [10.x] Fix attribute name used on `Validator` instance within certain rule classes by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54943 +* Add `Illuminate\Support\EncodedHtmlString` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54737 +* [11.x] Fix missing `return $this` for `assertOnlyJsonValidationErrors` by [@LeTamanoir](https://github.com/LeTamanoir) in https://github.com/laravel/framework/pull/55099 +* [11.x] Fix `Illuminate\Support\EncodedHtmlString` from causing breaking change by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55149 +* [11.x] Respect custom path for cached views by the `AboutCommand` by [@alies-dev](https://github.com/alies-dev) in https://github.com/laravel/framework/pull/55179 +* [11.x] Include all invisible characters in Str::trim by [@laserhybiz](https://github.com/laserhybiz) in https://github.com/laravel/framework/pull/54281 +* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55302 ## [v11.44.2](https://github.com/laravel/framework/compare/v11.44.1...v11.44.2) - 2025-03-12 From 88fae3e4676627ef392b8463d6ef4b0f2877d903 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 24 Apr 2025 06:56:29 +0800 Subject: [PATCH 054/158] Disable MSSQL 2017 workflow as image no longer available Signed-off-by: Mior Muhammad Zaki --- .github/workflows/databases.yml | 94 ++++++++++++++++----------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 834166049942..1b950622fefb 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -293,53 +293,53 @@ jobs: DB_USERNAME: SA DB_PASSWORD: Forge123 - mssql_2017: - runs-on: ubuntu-20.04 - timeout-minutes: 5 - - services: - sqlsrv: - image: mcr.microsoft.com/mssql/server:2017-latest - env: - ACCEPT_EULA: Y - SA_PASSWORD: Forge123 - ports: - - 1433:1433 - - strategy: - fail-fast: true - - name: SQL Server 2017 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr - tools: composer:v2 - coverage: none - - - name: Set Framework version - run: composer config version "11.x-dev" - - - name: Install dependencies - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - - name: Execute tests - run: vendor/bin/phpunit tests/Integration/Database - env: - DB_CONNECTION: sqlsrv - DB_DATABASE: master - DB_USERNAME: SA - DB_PASSWORD: Forge123 + # mssql_2017: + # runs-on: ubuntu-20.04 + # timeout-minutes: 5 + + # services: + # sqlsrv: + # image: mcr.microsoft.com/mssql/server:2017-latest + # env: + # ACCEPT_EULA: Y + # SA_PASSWORD: Forge123 + # ports: + # - 1433:1433 + + # strategy: + # fail-fast: true + + # name: SQL Server 2017 + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Setup PHP + # uses: shivammathur/setup-php@v2 + # with: + # php-version: 8.3 + # extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr + # tools: composer:v2 + # coverage: none + + # - name: Set Framework version + # run: composer config version "11.x-dev" + + # - name: Install dependencies + # uses: nick-fields/retry@v3 + # with: + # timeout_minutes: 5 + # max_attempts: 5 + # command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + # - name: Execute tests + # run: vendor/bin/phpunit tests/Integration/Database + # env: + # DB_CONNECTION: sqlsrv + # DB_DATABASE: master + # DB_USERNAME: SA + # DB_PASSWORD: Forge123 sqlite: runs-on: ubuntu-24.04 From f6cb153e5abd3b3cc1b0d7d416edb5775e8458d0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 24 Apr 2025 07:33:09 +0800 Subject: [PATCH 055/158] [11.x] Remove incorrect syntax from mail's `message` template (#55530) --- src/Illuminate/Mail/resources/views/html/message.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Mail/resources/views/html/message.blade.php b/src/Illuminate/Mail/resources/views/html/message.blade.php index f1e815f32a41..a16bace0a691 100644 --- a/src/Illuminate/Mail/resources/views/html/message.blade.php +++ b/src/Illuminate/Mail/resources/views/html/message.blade.php @@ -13,7 +13,7 @@ @isset($subcopy) -{!! $subcopy !!}} +{!! $subcopy !!} @endisset From 6b29bb79b18474ea5265b7022ba7b0f76eebeb4f Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:35:40 +0000 Subject: [PATCH 056/158] Update version to v11.44.4 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index eeedee8f3331..6e220d2ad2d1 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.3'; + const VERSION = '11.44.4'; /** * The base path for the Laravel installation. From 56dc3e2672695e00526351d74454dfd4074d2a0e Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:37:20 +0000 Subject: [PATCH 057/158] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 342bcb405aea..2095f06fc9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.3...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.4...11.x) + +## [v11.44.4](https://github.com/laravel/framework/compare/v11.44.3...v11.44.4) - 2025-04-23 + +* [11.x] Remove incorrect syntax from mail's `message` template by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55530 ## [v11.44.3](https://github.com/laravel/framework/compare/v11.44.2...v11.44.3) - 2025-04-23 From bee282afc9ff966efff1c082bc6792253960eee7 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 24 Apr 2025 21:58:14 +0800 Subject: [PATCH 058/158] [11.x] Allows to toggle markdown email encoding (#55539) * Allow to toggle markdown encoding Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update Markdown.php * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Taylor Otwell --- .../InteractsWithTestCaseLifecycle.php | 2 + src/Illuminate/Mail/Markdown.php | 79 +++++++--- ...hp => MailableWithSecuredEncodingTest.php} | 16 +- .../MailableWithoutSecuredEncodingTest.php | 143 ++++++++++++++++++ tests/Integration/Mail/MarkdownParserTest.php | 10 ++ 5 files changed, 230 insertions(+), 20 deletions(-) rename tests/Integration/Mail/{MailableTest.php => MailableWithSecuredEncodingTest.php} (91%) create mode 100644 tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php index 4b81d3c4d45d..c372d55cc93f 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -23,6 +23,7 @@ use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Http\Middleware\TrustHosts; use Illuminate\Http\Middleware\TrustProxies; +use Illuminate\Mail\Markdown; use Illuminate\Queue\Console\WorkCommand; use Illuminate\Queue\Queue; use Illuminate\Support\Carbon; @@ -175,6 +176,7 @@ protected function tearDownTheTestEnvironment(): void EncodedHtmlString::flushState(); EncryptCookies::flushState(); HandleExceptions::flushState(); + Markdown::flushState(); Migrator::withoutMigrations([]); Once::flush(); PreventRequestsDuringMaintenance::flushState(); diff --git a/src/Illuminate/Mail/Markdown.php b/src/Illuminate/Mail/Markdown.php index 54c9c2ece372..5d411f4550a4 100644 --- a/src/Illuminate/Mail/Markdown.php +++ b/src/Illuminate/Mail/Markdown.php @@ -35,6 +35,13 @@ class Markdown */ protected $componentPaths = []; + /** + * Indicates if secure encoding should be enabled. + * + * @var bool + */ + protected static $withSecuredEncoding = false; + /** * Create a new Markdown renderer instance. * @@ -69,15 +76,17 @@ public function render($view, array $data = [], $inliner = null) $contents = $bladeCompiler->usingEchoFormat( 'new \Illuminate\Support\EncodedHtmlString(%s)', function () use ($view, $data) { - EncodedHtmlString::encodeUsing(function ($value) { - $replacements = [ - '[' => '\[', - '<' => '<', - '>' => '>', - ]; - - return str_replace(array_keys($replacements), array_values($replacements), $value); - }); + if (static::$withSecuredEncoding === true) { + EncodedHtmlString::encodeUsing(function ($value) { + $replacements = [ + '[' => '\[', + '<' => '<', + '>' => '>', + ]; + + return str_replace(array_keys($replacements), array_values($replacements), $value); + }); + } try { $contents = $this->view->replaceNamespace( @@ -137,18 +146,20 @@ public static function parse($text, bool $encoded = false) return new HtmlString(static::converter()->convert($text)->getContent()); } - EncodedHtmlString::encodeUsing(function ($value) { - $replacements = [ - '[' => '\[', - '<' => '\<', - ]; + if (static::$withSecuredEncoding === true || $encoded === true) { + EncodedHtmlString::encodeUsing(function ($value) { + $replacements = [ + '[' => '\[', + '<' => '\<', + ]; - $html = str_replace(array_keys($replacements), array_values($replacements), $value); + $html = str_replace(array_keys($replacements), array_values($replacements), $value); - return static::converter([ - 'html_input' => 'escape', - ])->convert($html)->getContent(); - }); + return static::converter([ + 'html_input' => 'escape', + ])->convert($html)->getContent(); + }); + } $html = ''; @@ -250,4 +261,34 @@ public function getTheme() { return $this->theme; } + + /** + * Enable secured encoding when parsing Markdown. + * + * @return void + */ + public static function withSecuredEncoding() + { + static::$withSecuredEncoding = true; + } + + /** + * Disable secured encoding when parsing Markdown. + * + * @return void + */ + public static function withoutSecuredEncoding() + { + static::$withSecuredEncoding = false; + } + + /** + * Flush the class's global state. + * + * @return void + */ + public static function flushState() + { + static::$withSecuredEncoding = false; + } } diff --git a/tests/Integration/Mail/MailableTest.php b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php similarity index 91% rename from tests/Integration/Mail/MailableTest.php rename to tests/Integration/Mail/MailableWithSecuredEncodingTest.php index 4ff0f539b6ad..a84fd487c20d 100644 --- a/tests/Integration/Mail/MailableTest.php +++ b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php @@ -7,20 +7,34 @@ use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Mail\Markdown; +use Illuminate\Support\EncodedHtmlString; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; -class MailableTest extends TestCase +class MailableWithSecuredEncodingTest extends TestCase { use LazilyRefreshDatabase; + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + Markdown::flushState(); + EncodedHtmlString::flushState(); + + parent::tearDown(); + } + /** {@inheritdoc} */ #[\Override] protected function defineEnvironment($app) { $app['view']->addLocation(__DIR__.'/Fixtures'); + + Markdown::withSecuredEncoding(); } #[DataProvider('markdownEncodedDataProvider')] diff --git a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php new file mode 100644 index 000000000000..52db775cefae --- /dev/null +++ b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php @@ -0,0 +1,143 @@ +addLocation(__DIR__.'/Fixtures'); + + Markdown::withoutSecuredEncoding(); + } + + #[DataProvider('markdownEncodedDataProvider')] + public function testItCanAssertMarkdownEncodedString($given, $expected) + { + $mailable = new class($given) extends Mailable + { + public function __construct(public string $message) + { + // + } + + public function envelope() + { + return new Envelope( + subject: 'My basic title', + ); + } + + public function content() + { + return new Content( + markdown: 'message', + ); + } + }; + + $mailable->assertSeeInHtml($expected, false); + } + + public static function markdownEncodedDataProvider() + { + yield ['[Laravel](https://laravel.com)', 'My message is: [Laravel](https://laravel.com)']; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + 'My message is: ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit https://laravel.com/docs to browse the documentation', + ]; + + yield [ + 'Visit to browse the documentation', + 'My message is: Visit <https://laravel.com/docs> to browse the documentation', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', + ]; + } + + #[WithMigration] + #[DataProvider('markdownEncodedTemplateDataProvider')] + public function testItCanAssertMarkdownEncodedStringUsingTemplate($given, $expected) + { + $user = UserFactory::new()->create([ + 'name' => $given, + ]); + + $mailable = new class($user) extends Mailable + { + public $theme = 'taylor'; + + public function __construct(public User $user) + { + // + } + + public function build() + { + return $this->markdown('message-with-template'); + } + }; + + $mailable->assertSeeInHtml($expected, false); + } + + public static function markdownEncodedTemplateDataProvider() + { + yield ['[Laravel](https://laravel.com)', '

Hi Laravel

']; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + '

Hi Welcome to Laravel

', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'Hi Visit https://laravel.com/docs to browse the documentation', + ]; + + yield [ + 'Visit to browse the documentation', + 'Hi Visit <https://laravel.com/docs> to browse the documentation', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'Hi Visit <span>https://laravel.com/docs</span> to browse the documentation', + ]; + } +} diff --git a/tests/Integration/Mail/MarkdownParserTest.php b/tests/Integration/Mail/MarkdownParserTest.php index 6669fe038ba3..7ff74ae21eb3 100644 --- a/tests/Integration/Mail/MarkdownParserTest.php +++ b/tests/Integration/Mail/MarkdownParserTest.php @@ -10,6 +10,16 @@ class MarkdownParserTest extends TestCase { + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + Markdown::flushState(); + EncodedHtmlString::flushState(); + + parent::tearDown(); + } + #[DataProvider('markdownDataProvider')] public function testItCanParseMarkdownString($given, $expected) { From 819555ccc7e88ea92945ea785ef9f0fa9b6d4931 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Thu, 24 Apr 2025 13:59:11 +0000 Subject: [PATCH 059/158] Update version to v11.44.5 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 6e220d2ad2d1..e8feb3128c30 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.4'; + const VERSION = '11.44.5'; /** * The base path for the Laravel installation. From 7358937cbd9c952e3c3853c1f294a796e9f933fc Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:00:55 +0000 Subject: [PATCH 060/158] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2095f06fc9f7..703aaaa9ea77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.4...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.5...11.x) + +## [v11.44.5](https://github.com/laravel/framework/compare/v11.44.4...v11.44.5) - 2025-04-24 + +* [11.x] Allows to toggle markdown email encoding by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55539 ## [v11.44.4](https://github.com/laravel/framework/compare/v11.44.3...v11.44.4) - 2025-04-23 From ec51a6608b02a63c80d7d7ef1b529185d0376dfc Mon Sep 17 00:00:00 2001 From: Jeremy Braband Date: Fri, 25 Apr 2025 03:31:51 -0500 Subject: [PATCH 061/158] [11.x] Fix `EncodedHtmlString` to ignore instance of `HtmlString` (#55543) * fix html encoding for 3 more mail component templates * add tests Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * `Markdown::parse()` returns `HtmlString()` so that should be enough to avoid double encoding Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki --- .../Mail/resources/views/html/panel.blade.php | 2 +- src/Illuminate/Support/EncodedHtmlString.php | 29 ++++++++++- .../Fixtures/table-with-template.blade.php | 12 +++++ .../Mail/MailableWithSecuredEncodingTest.php | 50 +++++++++++++++++++ .../MailableWithoutSecuredEncodingTest.php | 50 +++++++++++++++++++ tests/Integration/Mail/MarkdownParserTest.php | 5 ++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Mail/Fixtures/table-with-template.blade.php diff --git a/src/Illuminate/Mail/resources/views/html/panel.blade.php b/src/Illuminate/Mail/resources/views/html/panel.blade.php index 812db7c08e77..2975a60a021e 100644 --- a/src/Illuminate/Mail/resources/views/html/panel.blade.php +++ b/src/Illuminate/Mail/resources/views/html/panel.blade.php @@ -4,7 +4,7 @@
-{!! Illuminate\Mail\Markdown::parse($slot) !!} +{{ Illuminate\Mail\Markdown::parse($slot) }}
diff --git a/src/Illuminate/Support/EncodedHtmlString.php b/src/Illuminate/Support/EncodedHtmlString.php index 18928e75b633..a25115740277 100644 --- a/src/Illuminate/Support/EncodedHtmlString.php +++ b/src/Illuminate/Support/EncodedHtmlString.php @@ -2,8 +2,19 @@ namespace Illuminate\Support; +use BackedEnum; +use Illuminate\Contracts\Support\DeferringDisplayableValue; +use Illuminate\Contracts\Support\Htmlable; + class EncodedHtmlString extends HtmlString { + /** + * The HTML string. + * + * @var \Illuminate\Contracts\Support\DeferringDisplayableValue|\Illuminate\Contracts\Support\Htmlable|\BackedEnum|string|int|float|null + */ + protected $html; + /** * The callback that should be used to encode the HTML strings. * @@ -14,7 +25,7 @@ class EncodedHtmlString extends HtmlString /** * Create a new encoded HTML string instance. * - * @param string $html + * @param \Illuminate\Contracts\Support\DeferringDisplayableValue|\Illuminate\Contracts\Support\Htmlable|\BackedEnum|string|int|float|null $html * @param bool $doubleEncode * @return void */ @@ -48,9 +59,23 @@ public static function convert($value, bool $withQuote = true, bool $doubleEncod #[\Override] public function toHtml() { + $value = $this->html; + + if ($value instanceof DeferringDisplayableValue) { + $value = $value->resolveDisplayableValue(); + } + + if ($value instanceof Htmlable) { + return $value->toHtml(); + } + + if ($value instanceof BackedEnum) { + $value = $value->value; + } + return (static::$encodeUsingFactory ?? function ($value, $doubleEncode) { return static::convert($value, doubleEncode: $doubleEncode); - })($this->html, $this->doubleEncode); + })($value, $this->doubleEncode); } /** diff --git a/tests/Integration/Mail/Fixtures/table-with-template.blade.php b/tests/Integration/Mail/Fixtures/table-with-template.blade.php new file mode 100644 index 000000000000..3a4ec4c6260e --- /dev/null +++ b/tests/Integration/Mail/Fixtures/table-with-template.blade.php @@ -0,0 +1,12 @@ + + + +*Hi* {{ $user->name }} + +| Laravel | Table | Example | +| ------------- | :-----------: | ------------: | +| Col 2 is | Centered | $10 | +| Col 3 is | Right-Aligned | $20 | + + + diff --git a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php index a84fd487c20d..9feb03886335 100644 --- a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php @@ -116,6 +116,56 @@ public function build() $mailable->assertSeeInHtml($expected, false); } + #[WithMigration] + #[DataProvider('markdownEncodedTemplateDataProvider')] + public function testItCanAssertMarkdownEncodedStringUsingTemplateWithTable($given, $expected) + { + $user = UserFactory::new()->create([ + 'name' => $given, + ]); + + $mailable = new class($user) extends Mailable + { + public $theme = 'taylor'; + + public function __construct(public User $user) + { + // + } + + public function build() + { + return $this->markdown('table-with-template'); + } + }; + + $mailable->assertSeeInHtml($expected, false); + $mailable->assertSeeInHtml('

This is a subcopy

', false); + $mailable->assertSeeInHtml(<<<'TABLE' + + + + + + + + + + + + + + + + + + + + +
LaravelTableExample
Col 2 isCentered$10
Col 3 isRight-Aligned$20
+TABLE, false); + } + public static function markdownEncodedTemplateDataProvider() { yield ['[Laravel](https://laravel.com)', 'Hi [Laravel](https://laravel.com)']; diff --git a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php index 52db775cefae..aea392c520ca 100644 --- a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php @@ -116,6 +116,56 @@ public function build() $mailable->assertSeeInHtml($expected, false); } + #[WithMigration] + #[DataProvider('markdownEncodedTemplateDataProvider')] + public function testItCanAssertMarkdownEncodedStringUsingTemplateWithTable($given, $expected) + { + $user = UserFactory::new()->create([ + 'name' => $given, + ]); + + $mailable = new class($user) extends Mailable + { + public $theme = 'taylor'; + + public function __construct(public User $user) + { + // + } + + public function build() + { + return $this->markdown('table-with-template'); + } + }; + + $mailable->assertSeeInHtml($expected, false); + $mailable->assertSeeInHtml('

This is a subcopy

', false); + $mailable->assertSeeInHtml(<<<'TABLE' + + + + + + + + + + + + + + + + + + + + +
LaravelTableExample
Col 2 isCentered$10
Col 3 isRight-Aligned$20
+TABLE, false); + } + public static function markdownEncodedTemplateDataProvider() { yield ['[Laravel](https://laravel.com)', '

Hi Laravel

']; diff --git a/tests/Integration/Mail/MarkdownParserTest.php b/tests/Integration/Mail/MarkdownParserTest.php index 7ff74ae21eb3..16910e79fd18 100644 --- a/tests/Integration/Mail/MarkdownParserTest.php +++ b/tests/Integration/Mail/MarkdownParserTest.php @@ -76,6 +76,11 @@ public static function markdownEncodedDataProvider() '

Visit <span>https://laravel.com/docs</span> to browse the documentation

', ]; + yield [ + new EncodedHtmlString(new HtmlString('Visit https://laravel.com/docs to browse the documentation')), + '

Visit https://laravel.com/docs to browse the documentation

', + ]; + yield [ '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)
'.new EncodedHtmlString('Visit https://laravel.com/docs to browse the documentation'), '

Welcome to Laravel
Visit <span>https://laravel.com/docs</span> to browse the documentation

', From 9d34abe28085fa74b46382ebddd0350f780d055f Mon Sep 17 00:00:00 2001 From: crynobone <172966+crynobone@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:33:55 +0000 Subject: [PATCH 062/158] Update version to v11.44.6 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index e8feb3128c30..a02309202eaf 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.5'; + const VERSION = '11.44.6'; /** * The base path for the Laravel installation. From 32fb31d4ef210be888287aef5ce5eedfd9d49f08 Mon Sep 17 00:00:00 2001 From: crynobone <172966+crynobone@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:35:38 +0000 Subject: [PATCH 063/158] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 703aaaa9ea77..6e367c68b18c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.5...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.6...11.x) + +## [v11.44.6](https://github.com/laravel/framework/compare/v11.44.5...v11.44.6) - 2025-04-25 + +* [11.x] Fix `EncodedHtmlString` to ignore instance of `HtmlString` by [@jbraband](https://github.com/jbraband) in https://github.com/laravel/framework/pull/55543 ## [v11.44.5](https://github.com/laravel/framework/compare/v11.44.4...v11.44.5) - 2025-04-24 From 00bc6ac91a6d577bf051c18ddaa638c0d221e1c7 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:40:47 +0000 Subject: [PATCH 064/158] Update version to v11.44.7 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index a02309202eaf..61546f679b2b 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.6'; + const VERSION = '11.44.7'; /** * The base path for the Laravel installation. From 3557d14ef785401e9be64eb2dd3765d67c0a5dce Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:42:43 +0000 Subject: [PATCH 065/158] Update CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e367c68b18c..453ba8d2aac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.6...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.44.7...11.x) + +## [v11.44.7](https://github.com/laravel/framework/compare/v11.44.6...v11.44.7) - 2025-04-25 ## [v11.44.6](https://github.com/laravel/framework/compare/v11.44.5...v11.44.6) - 2025-04-25 From 401649cd2499d78c228ad19238766a6bf2e8b18f Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 25 Apr 2025 21:28:24 +0800 Subject: [PATCH 066/158] [11.x] Test Improvements (#55549) - Split redundant code Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- tests/Integration/Mail/MailableTestCase.php | 72 +++++++++++++++++++ .../Mail/MailableWithSecuredEncodingTest.php | 71 +----------------- .../MailableWithoutSecuredEncodingTest.php | 71 +----------------- 4 files changed, 77 insertions(+), 139 deletions(-) create mode 100644 tests/Integration/Mail/MailableTestCase.php diff --git a/composer.json b/composer.json index 282d9ff0aa8d..c6dc2e278836 100644 --- a/composer.json +++ b/composer.json @@ -111,7 +111,7 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.11.2", + "orchestra/testbench-core": "^9.13.2", "pda/pheanstalk": "^5.0.6", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", diff --git a/tests/Integration/Mail/MailableTestCase.php b/tests/Integration/Mail/MailableTestCase.php new file mode 100644 index 000000000000..4790652fb20d --- /dev/null +++ b/tests/Integration/Mail/MailableTestCase.php @@ -0,0 +1,72 @@ +addLocation(__DIR__.'/Fixtures'); + } + + #[DataProvider('markdownEncodedDataProvider')] + public function testItCanAssertMarkdownEncodedString($given, $expected) + { + $mailable = new class($given) extends Mailable + { + public function __construct(public string $message) + { + // + } + + public function envelope() + { + return new Envelope( + subject: 'My basic title', + ); + } + + public function content() + { + return new Content( + markdown: 'message', + ); + } + }; + + $mailable->assertSeeInHtml($expected, false); + } + + public static function markdownEncodedDataProvider() + { + yield ['[Laravel](https://laravel.com)', 'My message is: [Laravel](https://laravel.com)']; + + yield [ + '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + 'My message is: ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit https://laravel.com/docs to browse the documentation', + ]; + + yield [ + 'Visit to browse the documentation', + 'My message is: Visit <https://laravel.com/docs> to browse the documentation', + ]; + + yield [ + 'Visit https://laravel.com/docs to browse the documentation', + 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', + ]; + } +} diff --git a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php index 9feb03886335..12c95c05262d 100644 --- a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php @@ -5,91 +5,24 @@ use Illuminate\Foundation\Auth\User; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; -use Illuminate\Mail\Mailables\Envelope; use Illuminate\Mail\Markdown; -use Illuminate\Support\EncodedHtmlString; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Factories\UserFactory; -use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; -class MailableWithSecuredEncodingTest extends TestCase +class MailableWithSecuredEncodingTest extends MailableTestCase { use LazilyRefreshDatabase; - /** {@inheritdoc} */ - #[\Override] - protected function tearDown(): void - { - Markdown::flushState(); - EncodedHtmlString::flushState(); - - parent::tearDown(); - } - /** {@inheritdoc} */ #[\Override] protected function defineEnvironment($app) { - $app['view']->addLocation(__DIR__.'/Fixtures'); + parent::defineEnvironment($app); Markdown::withSecuredEncoding(); } - #[DataProvider('markdownEncodedDataProvider')] - public function testItCanAssertMarkdownEncodedString($given, $expected) - { - $mailable = new class($given) extends Mailable - { - public function __construct(public string $message) - { - // - } - - public function envelope() - { - return new Envelope( - subject: 'My basic title', - ); - } - - public function content() - { - return new Content( - markdown: 'message', - ); - } - }; - - $mailable->assertSeeInHtml($expected, false); - } - - public static function markdownEncodedDataProvider() - { - yield ['[Laravel](https://laravel.com)', 'My message is: [Laravel](https://laravel.com)']; - - yield [ - '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', - 'My message is: ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', - ]; - - yield [ - 'Visit https://laravel.com/docs to browse the documentation', - 'My message is: Visit https://laravel.com/docs to browse the documentation', - ]; - - yield [ - 'Visit to browse the documentation', - 'My message is: Visit <https://laravel.com/docs> to browse the documentation', - ]; - - yield [ - 'Visit https://laravel.com/docs to browse the documentation', - 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', - ]; - } - #[WithMigration] #[DataProvider('markdownEncodedTemplateDataProvider')] public function testItCanAssertMarkdownEncodedStringUsingTemplate($given, $expected) diff --git a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php index aea392c520ca..5ded70eb246c 100644 --- a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php @@ -5,91 +5,24 @@ use Illuminate\Foundation\Auth\User; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; -use Illuminate\Mail\Mailables\Envelope; use Illuminate\Mail\Markdown; -use Illuminate\Support\EncodedHtmlString; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Factories\UserFactory; -use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; -class MailableWithoutSecuredEncodingTest extends TestCase +class MailableWithoutSecuredEncodingTest extends MailableTestCase { use LazilyRefreshDatabase; - /** {@inheritdoc} */ - #[\Override] - protected function tearDown(): void - { - Markdown::flushState(); - EncodedHtmlString::flushState(); - - parent::tearDown(); - } - /** {@inheritdoc} */ #[\Override] protected function defineEnvironment($app) { - $app['view']->addLocation(__DIR__.'/Fixtures'); + parent::defineEnvironment($app); Markdown::withoutSecuredEncoding(); } - #[DataProvider('markdownEncodedDataProvider')] - public function testItCanAssertMarkdownEncodedString($given, $expected) - { - $mailable = new class($given) extends Mailable - { - public function __construct(public string $message) - { - // - } - - public function envelope() - { - return new Envelope( - subject: 'My basic title', - ); - } - - public function content() - { - return new Content( - markdown: 'message', - ); - } - }; - - $mailable->assertSeeInHtml($expected, false); - } - - public static function markdownEncodedDataProvider() - { - yield ['[Laravel](https://laravel.com)', 'My message is: [Laravel](https://laravel.com)']; - - yield [ - '![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', - 'My message is: ![Welcome to Laravel](https://laravel.com/assets/img/welcome/background.svg)', - ]; - - yield [ - 'Visit https://laravel.com/docs to browse the documentation', - 'My message is: Visit https://laravel.com/docs to browse the documentation', - ]; - - yield [ - 'Visit to browse the documentation', - 'My message is: Visit <https://laravel.com/docs> to browse the documentation', - ]; - - yield [ - 'Visit https://laravel.com/docs to browse the documentation', - 'My message is: Visit <span>https://laravel.com/docs</span> to browse the documentation', - ]; - } - #[WithMigration] #[DataProvider('markdownEncodedTemplateDataProvider')] public function testItCanAssertMarkdownEncodedStringUsingTemplate($given, $expected) From bcc92201ab6d786477fed578cc2e3ea4c4e1b093 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:20:00 +0000 Subject: [PATCH 067/158] Update CHANGELOG --- CHANGELOG.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b292512610ef..ce120028ef16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.10.2...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.11.0...12.x) + +## [v12.11.0](https://github.com/laravel/framework/compare/v12.10.2...v12.11.0) - 2025-04-29 + +* Add payload creation and original delay info to job payload by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55529 +* Add config option to ignore view cache timestamps by [@pizkaz](https://github.com/pizkaz) in https://github.com/laravel/framework/pull/55536 +* [12.x] Dispatch NotificationFailed when sending fails by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55507 +* [12.x] Option to disable dispatchAfterResponse in a test by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55456 +* [12.x] Pass flags to custom Json::$encoder by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55548 +* [12.x] Use pendingAttributes of relationships when creating relationship models via model factories by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55558 +* [12.x] Fix double query in model relation serialization by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55547 +* [12.x] Improve circular relation check in Automatic Relation Loading by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55542 +* [12.x] Prevent relation autoload context from being serialized by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55582 +* Remove `@internal` Annotation from `$components` Property in `InteractsWithIO` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/55580 +* Ensure fake job implements job contract by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55574 +* [12.x] Fix `AnyOf` constructor parameter type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/55577 +* Sync changes to Illuminate components before release by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/55591 +* [12.x] Set class-string generics on `Enum` rule by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55588 +* [12.x] added detailed doc types to bindings related methods by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55576 +* [12.x] Improve [@use](https://github.com/use) directive to support function and const modifiers by [@rodolfosrg](https://github.com/rodolfosrg) in https://github.com/laravel/framework/pull/55583 +* 12.x scheduled task failed not dispatched on scheduled task failing by [@achrafAa](https://github.com/achrafAa) in https://github.com/laravel/framework/pull/55572 +* [12.x] Introduce Reflector methods for accessing class attributes by [@daniser](https://github.com/daniser) in https://github.com/laravel/framework/pull/55568 +* [12.x] Typed getters for Arr helper by [@tibbsa](https://github.com/tibbsa) in https://github.com/laravel/framework/pull/55567 ## [v12.10.2](https://github.com/laravel/framework/compare/v12.10.1...v12.10.2) - 2025-04-24 From 12befdaad103df2e9c1310b26a78836e1a8c46f5 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 30 Apr 2025 08:47:09 -0500 Subject: [PATCH 068/158] =?UTF-8?q?Revert=20"12.x=20scheduled=20task=20fai?= =?UTF-8?q?led=20not=20dispatched=20on=20scheduled=20task=20failing=20(?= =?UTF-8?q?=E2=80=A6"=20(#55612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1deaa36de67da614cccc7b84bb38093735a02b99. --- src/Illuminate/Console/Scheduling/ScheduleRunCommand.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php index 047577372b18..75cb579925cf 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -2,7 +2,6 @@ namespace Illuminate\Console\Scheduling; -use Exception; use Illuminate\Console\Application; use Illuminate\Console\Command; use Illuminate\Console\Events\ScheduledTaskFailed; @@ -197,10 +196,6 @@ protected function runEvent($event) round(microtime(true) - $start, 2) )); - if ($event->exitCode !== 0) { - throw new Exception("Scheduled command [{$event->command}] failed with exit code [{$event->exitCode}]."); - } - $this->eventsRan = true; } catch (Throwable $e) { $this->dispatcher->dispatch(new ScheduledTaskFailed($event, $e)); From 5b0d72c4479f5244b838373ce6eb138e27aa59d0 Mon Sep 17 00:00:00 2001 From: Jack Bayliss Date: Wed, 30 Apr 2025 14:47:49 +0100 Subject: [PATCH 069/158] [12.x] Resolve issue with BelongsToManyRelationship factory (#55608) * Update BelongsToManyRelationship.php * cs --- .../Eloquent/Factories/BelongsToManyRelationship.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php b/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php index fd350e6fce6c..5498dc856516 100644 --- a/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php +++ b/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php @@ -50,9 +50,13 @@ public function __construct($factory, $pivot, $relationship) */ public function createFor(Model $model) { - $relationship = $model->{$this->relationship}(); + $factoryInstance = $this->factory instanceof Factory; - Collection::wrap($this->factory instanceof Factory ? $this->factory->prependState($relationship->getQuery()->pendingAttributes)->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { + if ($factoryInstance) { + $relationship = $model->{$this->relationship}(); + } + + Collection::wrap($factoryInstance ? $this->factory->prependState($relationship->getQuery()->pendingAttributes)->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { $model->{$this->relationship}()->attach( $attachable, is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot From bd0d62bd9c5196728e428cd695d89ec8640daac1 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:48:39 +0000 Subject: [PATCH 070/158] Update version to v12.11.1 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 9a18e7737177..85a7387a50fc 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.11.0'; + const VERSION = '12.11.1'; /** * The base path for the Laravel installation. From f9a96f7b5c3b5cce391d22cdc4cc3e3da9f2656a Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:50:25 +0000 Subject: [PATCH 071/158] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce120028ef16..9ca39ba4620a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.11.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.11.1...12.x) + +## [v12.11.1](https://github.com/laravel/framework/compare/v12.11.0...v12.11.1) - 2025-04-30 + +* Revert "[12.x]`ScheduledTaskFailed` not dispatched on scheduled task failing" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55612 +* [12.x] Resolve issue with BelongsToManyRelationship factory by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/55608 ## [v12.11.0](https://github.com/laravel/framework/compare/v12.10.2...v12.11.0) - 2025-04-29 From ea9c4813bd278d9e172d46f73d295dfb9cd7d372 Mon Sep 17 00:00:00 2001 From: Igor Finagin Date: Wed, 30 Apr 2025 18:33:18 +0400 Subject: [PATCH 072/158] Make Blueprint Resolver Statically (#55607) --- src/Illuminate/Database/Schema/Builder.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 109932a27d12..d70cb9314231 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -9,6 +9,9 @@ use InvalidArgumentException; use LogicException; +/** + * @template TResolver of \Closure(string, \Closure, string): \Illuminate\Database\Schema\Blueprint + */ class Builder { use Macroable; @@ -30,9 +33,9 @@ class Builder /** * The Blueprint resolver callback. * - * @var \Closure + * @var TResolver|null */ - protected $resolver; + protected static $resolver = null; /** * The default string length for migrations. @@ -629,8 +632,8 @@ protected function createBlueprint($table, ?Closure $callback = null) { $connection = $this->connection; - if (isset($this->resolver)) { - return call_user_func($this->resolver, $connection, $table, $callback); + if (static::$resolver !== null) { + return call_user_func(static::$resolver, $connection, $table, $callback); } return Container::getInstance()->make(Blueprint::class, compact('connection', 'table', 'callback')); @@ -698,11 +701,11 @@ public function getConnection() /** * Set the Schema Blueprint resolver callback. * - * @param \Closure $resolver + * @param TResolver|null $resolver * @return void */ - public function blueprintResolver(Closure $resolver) + public function blueprintResolver(?Closure $resolver) { - $this->resolver = $resolver; + static::$resolver = $resolver; } } From b9342ff755dfea947e9d22687d0a0169d568b09e Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:33:58 +0000 Subject: [PATCH 073/158] Update facade docblocks --- src/Illuminate/Support/Facades/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 09d0844c8610..d0e3e5f84bf1 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -44,7 +44,7 @@ * @method static string|null getCurrentSchemaName() * @method static array parseSchemaAndTable(string $reference, string|bool|null $withDefaultSchema = null) * @method static \Illuminate\Database\Connection getConnection() - * @method static void blueprintResolver(\Closure $resolver) + * @method static void blueprintResolver(\Closure|null $resolver) * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) From c895e4d971649a36f6e3612868d11d849069fed7 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 2 May 2025 00:54:03 +1000 Subject: [PATCH 074/158] [12.x] Allow limiting number of assets to preload (#55618) * Allow limiting number of assets to preload * Lint --- .../AddLinkHeadersForPreloadedAssets.php | 17 +++++++- tests/Http/Middleware/VitePreloadingTest.php | 43 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php index 7c1de1dae942..247c1507c506 100644 --- a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php +++ b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php @@ -8,18 +8,31 @@ class AddLinkHeadersForPreloadedAssets { + /** + * Configure the middleware. + * + * @param int $limit + * @return string + */ + public static function using($limit) + { + return static::class.':'.$limit; + } + /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next + * @param int $limit * @return \Illuminate\Http\Response */ - public function handle($request, $next) + public function handle($request, $next, $limit = null) { - return tap($next($request), function ($response) { + return tap($next($request), function ($response) use ($limit) { if ($response instanceof Response && Vite::preloadedAssets() !== []) { $response->header('Link', (new Collection(Vite::preloadedAssets())) + ->when($limit, fn ($assets, $limit) => $assets->take($limit)) ->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes)) ->join(', '), false); } diff --git a/tests/Http/Middleware/VitePreloadingTest.php b/tests/Http/Middleware/VitePreloadingTest.php index 4fb11f8b0b37..765ee029590a 100644 --- a/tests/Http/Middleware/VitePreloadingTest.php +++ b/tests/Http/Middleware/VitePreloadingTest.php @@ -106,4 +106,47 @@ public function testItDoesNotOverwriteOtherLinkHeaders() $response->headers->all('Link'), ); } + + public function testItCanLimitNumberOfAssetsPreloaded() + { + $app = new Container; + $app->instance(Vite::class, new class extends Vite + { + protected $preloadedAssets = [ + 'https://laravel.com/first.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/second.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/third.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/fourth.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + ]; + }); + Facade::setFacadeApplication($app); + + $response = (new AddLinkHeadersForPreloadedAssets)->handle(new Request, fn () => new Response('ok'), 2); + + $this->assertSame( + [ + '; rel="modulepreload"; foo="bar", ; rel="modulepreload"; foo="bar"', + ], + $response->headers->all('Link'), + ); + } + + public function test_it_can_configure_the_middleware() + { + $definition = AddLinkHeadersForPreloadedAssets::using(limit: 5); + + $this->assertSame('Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets:5', $definition); + } } From b0cb2377f1532ec71de2ec078431a2679becbabe Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Thu, 1 May 2025 11:12:16 -0400 Subject: [PATCH 075/158] [12.x] Set job instance on "failed" command instance (#55617) * Add failing test * Update test to expect the new argument * Set the job instance when creating a new instance of command after failure * Update CallQueuedHandler.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Queue/CallQueuedHandler.php | 7 +++- src/Illuminate/Queue/Jobs/Job.php | 2 +- tests/Queue/QueueBeanstalkdJobTest.php | 3 +- tests/Queue/QueueSyncQueueTest.php | 39 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Queue/CallQueuedHandler.php b/src/Illuminate/Queue/CallQueuedHandler.php index 0da7e735ca39..c7b887a4d953 100644 --- a/src/Illuminate/Queue/CallQueuedHandler.php +++ b/src/Illuminate/Queue/CallQueuedHandler.php @@ -274,12 +274,17 @@ protected function ensureUniqueJobLockIsReleasedViaContext() * @param array $data * @param \Throwable|null $e * @param string $uuid + * @param \Illuminate\Contracts\Queue\Job|null $job * @return void */ - public function failed(array $data, $e, string $uuid) + public function failed(array $data, $e, string $uuid, ?Job $job = null) { $command = $this->getCommand($data); + if (! is_null($job)) { + $command = $this->setJobInstanceIfNecessary($job, $command); + } + if (! $command instanceof ShouldBeUniqueUntilProcessing) { $this->ensureUniqueJobLockIsReleased($command); } diff --git a/src/Illuminate/Queue/Jobs/Job.php b/src/Illuminate/Queue/Jobs/Job.php index 8ec6ac54f805..112501b26580 100755 --- a/src/Illuminate/Queue/Jobs/Job.php +++ b/src/Illuminate/Queue/Jobs/Job.php @@ -251,7 +251,7 @@ protected function failed($e) [$class, $method] = JobName::parse($payload['job']); if (method_exists($this->instance = $this->resolve($class), 'failed')) { - $this->instance->failed($payload['data'], $e, $payload['uuid'] ?? ''); + $this->instance->failed($payload['data'], $e, $payload['uuid'] ?? '', $this); } } diff --git a/tests/Queue/QueueBeanstalkdJobTest.php b/tests/Queue/QueueBeanstalkdJobTest.php index 514555001e67..1405cb3f0712 100755 --- a/tests/Queue/QueueBeanstalkdJobTest.php +++ b/tests/Queue/QueueBeanstalkdJobTest.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Queue\Events\JobFailed; use Illuminate\Queue\Jobs\BeanstalkdJob; +use Illuminate\Queue\Jobs\Job; use Mockery as m; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkManagerInterface; @@ -39,7 +40,7 @@ public function testFailProperlyCallsTheJobHandler() $job->getPheanstalkJob()->shouldReceive('getData')->andReturn(json_encode(['job' => 'foo', 'uuid' => 'test-uuid', 'data' => ['data']])); $job->getContainer()->shouldReceive('make')->once()->with('foo')->andReturn($handler = m::mock(BeanstalkdJobTestFailedTest::class)); $job->getPheanstalk()->shouldReceive('delete')->once()->with($job->getPheanstalkJob())->andReturnSelf(); - $handler->shouldReceive('failed')->once()->with(['data'], m::type(Exception::class), 'test-uuid'); + $handler->shouldReceive('failed')->once()->with(['data'], m::type(Exception::class), 'test-uuid', m::type(Job::class)); $job->getContainer()->shouldReceive('make')->once()->with(Dispatcher::class)->andReturn($events = m::mock(Dispatcher::class)); $events->shouldReceive('dispatch')->once()->with(m::type(JobFailed::class))->andReturnNull(); diff --git a/tests/Queue/QueueSyncQueueTest.php b/tests/Queue/QueueSyncQueueTest.php index e7aab86ad779..901f66c2d75e 100755 --- a/tests/Queue/QueueSyncQueueTest.php +++ b/tests/Queue/QueueSyncQueueTest.php @@ -61,6 +61,28 @@ public function testFailedJobGetsHandledWhenAnExceptionIsThrown() Container::setInstance(); } + public function testFailedJobHasAccessToJobInstance() + { + unset($_SERVER['__sync.failed']); + + $sync = new SyncQueue; + $container = new Container; + $container->bind(\Illuminate\Contracts\Events\Dispatcher::class, \Illuminate\Events\Dispatcher::class); + $container->bind(\Illuminate\Contracts\Bus\Dispatcher::class, \Illuminate\Bus\Dispatcher::class); + $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); + $sync->setContainer($container); + + SyncQueue::createPayloadUsing(function ($connection, $queue, $payload) { + return ['data' => ['extra' => 'extraValue']]; + }); + + try { + $sync->push(new FailingSyncQueueJob()); + } catch (LogicException $e) { + $this->assertSame('extraValue', $_SERVER['__sync.failed']); + } + } + public function testCreatesPayloadObject() { $sync = new SyncQueue; @@ -177,6 +199,23 @@ public function failed() } } +class FailingSyncQueueJob implements ShouldQueue +{ + use InteractsWithQueue; + + public function handle() + { + throw new LogicException(); + } + + public function failed() + { + $payload = $this->job->payload(); + + $_SERVER['__sync.failed'] = $payload['data']['extra']; + } +} + class SyncQueueJob implements ShouldQueue { use InteractsWithQueue; From f1fda73c9feffba08a66429fddf4aeca379f1d6e Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 1 May 2025 19:40:54 +0330 Subject: [PATCH 076/158] install Passport 13.x (#55621) --- .../Foundation/Console/ApiInstallCommand.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Foundation/Console/ApiInstallCommand.php b/src/Illuminate/Foundation/Console/ApiInstallCommand.php index aeec5c0c2db2..45be0bca6572 100644 --- a/src/Illuminate/Foundation/Console/ApiInstallCommand.php +++ b/src/Illuminate/Foundation/Console/ApiInstallCommand.php @@ -37,7 +37,7 @@ class ApiInstallCommand extends Command /** * Execute the console command. * - * @return int + * @return void */ public function handle() { @@ -67,12 +67,11 @@ public function handle() } if ($this->option('passport')) { - Process::run(array_filter([ + Process::run([ php_binary(), artisan_binary(), 'passport:install', - $this->confirm('Would you like to use UUIDs for all client IDs?') ? '--uuids' : null, - ])); + ]); $this->components->info('API scaffolding installed. Please add the [Laravel\Passport\HasApiTokens] trait to your User model.'); } else { @@ -111,8 +110,6 @@ protected function uncommentApiRoutesFile() ); } else { $this->components->warn('Unable to automatically add API route definition to bootstrap file. API route file should be registered manually.'); - - return; } } @@ -150,7 +147,7 @@ protected function installSanctum() protected function installPassport() { $this->requireComposerPackages($this->option('composer'), [ - 'laravel/passport:^12.0', + 'laravel/passport:^13.0', ]); } } From 05bd808ec1b3d1ea164912a39480c42f5a0f15e4 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 1 May 2025 11:12:19 -0500 Subject: [PATCH 077/158] update api install command --- src/Illuminate/Foundation/Console/ApiInstallCommand.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Foundation/Console/ApiInstallCommand.php b/src/Illuminate/Foundation/Console/ApiInstallCommand.php index 9c460555e80a..f8e19c4853f8 100644 --- a/src/Illuminate/Foundation/Console/ApiInstallCommand.php +++ b/src/Illuminate/Foundation/Console/ApiInstallCommand.php @@ -37,7 +37,7 @@ class ApiInstallCommand extends Command /** * Execute the console command. * - * @return int + * @return void */ public function handle() { @@ -67,12 +67,11 @@ public function handle() } if ($this->option('passport')) { - Process::run(array_filter([ + Process::run([ php_binary(), artisan_binary(), 'passport:install', - $this->confirm('Would you like to use UUIDs for all client IDs?') ? '--uuids' : null, - ])); + ]); $this->components->info('API scaffolding installed. Please add the [Laravel\Passport\HasApiTokens] trait to your User model.'); } else { @@ -150,7 +149,7 @@ protected function installSanctum() protected function installPassport() { $this->requireComposerPackages($this->option('composer'), [ - 'laravel/passport:^12.0', + 'laravel/passport:^13.0', ]); } } From 8f6cd73696068c28f30f5964556ec9d14e5d90d7 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Thu, 1 May 2025 16:13:12 +0000 Subject: [PATCH 078/158] Update version to v12.12.0 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 85a7387a50fc..0fed290349c5 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.11.1'; + const VERSION = '12.12.0'; /** * The base path for the Laravel installation. From 39fa0e9492393a607db23d5dd91f90f74666e474 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Thu, 1 May 2025 16:15:02 +0000 Subject: [PATCH 079/158] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca39ba4620a..55abba5ea7ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.11.1...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.12.0...12.x) + +## [v12.12.0](https://github.com/laravel/framework/compare/v12.11.1...v12.12.0) - 2025-05-01 + +* [12.x] Make Blueprint Resolver Statically by [@finagin](https://github.com/finagin) in https://github.com/laravel/framework/pull/55607 +* [12.x] Allow limiting number of assets to preload by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55618 +* [12.x] Set job instance on "failed" command instance by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/55617 ## [v12.11.1](https://github.com/laravel/framework/compare/v12.11.0...v12.11.1) - 2025-04-30 From 6a0500613fffa4538c63f920368fb61bed2ea5f8 Mon Sep 17 00:00:00 2001 From: Oliver Nybroe Date: Fri, 2 May 2025 16:43:46 +0200 Subject: [PATCH 080/158] [12.x] fix no arguments return type in request class (#55631) * fix `null` parameter return type in request class * fix styling --- src/Illuminate/Http/Request.php | 4 ++-- types/Http/Request.php | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index bee09601ef03..d64082868105 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -417,7 +417,7 @@ public function get(string $key, mixed $default = null): mixed * * @param string|null $key * @param mixed $default - * @return \Symfony\Component\HttpFoundation\InputBag|mixed + * @return ($key is null ? \Symfony\Component\HttpFoundation\InputBag : mixed) */ public function json($key = null, $default = null) { @@ -633,7 +633,7 @@ public function user($guard = null) * * @param string|null $param * @param mixed $default - * @return \Illuminate\Routing\Route|object|string|null + * @return ($param is null ? \Illuminate\Routing\Route : object|string|null) */ public function route($param = null, $default = null) { diff --git a/types/Http/Request.php b/types/Http/Request.php index 340b77bb973f..c95bed31cf70 100644 --- a/types/Http/Request.php +++ b/types/Http/Request.php @@ -14,3 +14,9 @@ enum TestEnum: string ]); assertType('TestEnum|null', $request->enum('key', TestEnum::class)); + +assertType('Illuminate\Routing\Route', $request->route()); +assertType('object|string|null', $request->route('key')); + +assertType('Symfony\Component\HttpFoundation\InputBag', $request->json()); +assertType('mixed', $request->json('key')); From d2d4dedaff5ce0040b3e5fa056ca554dfb4c501a Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 2 May 2025 14:44:30 +0000 Subject: [PATCH 081/158] Update facade docblocks --- src/Illuminate/Support/Facades/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 8461fa41b1ee..11f6059af71c 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -44,7 +44,7 @@ * @method static void setRequestLocale(string $locale) * @method static void setDefaultRequestLocale(string $locale) * @method static mixed user(string|null $guard = null) - * @method static \Illuminate\Routing\Route|object|string|null route(string|null $param = null, mixed $default = null) + * @method static \Illuminate\Routing\Route|(object|string|null route(string|null $param = null, mixed $default = null) * @method static string fingerprint() * @method static \Illuminate\Http\Request setJson(\Symfony\Component\HttpFoundation\InputBag $json) * @method static \Closure getUserResolver() From 9b076c96c5912591019cd7d0d014a662e02b2eb0 Mon Sep 17 00:00:00 2001 From: Fernando Garcia Date: Fri, 2 May 2025 08:54:36 -0600 Subject: [PATCH 082/158] [12.x] Add support for callback evaluation in containsOneItem method (#55622) * [12.x] Add support for callback evaluation in containsOneItem method * Update Collection.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Collection.php | 9 +++++++-- tests/Support/SupportCollectionTest.php | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 23e1af7bbfee..95faa17a7121 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -712,12 +712,17 @@ public function isEmpty() } /** - * Determine if the collection contains a single item. + * Determine if the collection contains exactly one item. If a callback is provided, determine if exactly one item matches the condition. * + * @param (callable(TValue, TKey): bool)|null $callback * @return bool */ - public function containsOneItem() + public function containsOneItem(?callable $callback = null): bool { + if ($callback) { + return $this->filter($callback)->count() === 1; + } + return $this->count() === 1; } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 2edd11ca6718..7c8104b570ae 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -838,6 +838,10 @@ public function testContainsOneItem($collection) $this->assertFalse((new $collection([]))->containsOneItem()); $this->assertTrue((new $collection([1]))->containsOneItem()); $this->assertFalse((new $collection([1, 2]))->containsOneItem()); + + $this->assertFalse(collect([1, 2, 2])->containsOneItem(fn ($number) => $number === 2)); + $this->assertTrue(collect(['ant', 'bear', 'cat'])->containsOneItem(fn ($word) => strlen($word) === 4)); + $this->assertFalse(collect(['ant', 'bear', 'cat'])->containsOneItem(fn ($word) => strlen($word) > 4)); } public function testIterable() From 8f8235fbfbfcfe14ae7e2f607068fc71f56ca522 Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Fri, 2 May 2025 23:55:10 +0900 Subject: [PATCH 083/158] [12.x] add generics to aggregate related methods and properties (#55628) * [12.x] add generics to aggregate related methods * revert unrelated --- src/Illuminate/Database/Query/Builder.php | 7 +++++-- src/Illuminate/Database/Query/Grammars/Grammar.php | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 1f00b7bd655f..56c3996779a4 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -90,7 +90,10 @@ class Builder implements BuilderContract /** * An aggregate function and column to be run. * - * @var array|null + * @var array{ + * function: string, + * columns: array<\Illuminate\Contracts\Database\Query\Expression|string> + * }|null */ public $aggregate; @@ -3653,7 +3656,7 @@ public function numericAggregate($function, $columns = ['*']) * Set the aggregate property without running the query. * * @param string $function - * @param array $columns + * @param array<\Illuminate\Contracts\Database\Query\Expression|string> $columns * @return $this */ protected function setAggregate($function, $columns) diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index 9b35083af603..ea1c44e00866 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -123,7 +123,7 @@ protected function compileComponents(Builder $query) * Compile an aggregated select clause. * * @param \Illuminate\Database\Query\Builder $query - * @param array $aggregate + * @param array{function: string, columns: array<\Illuminate\Contracts\Database\Query\Expression|string>} $aggregate * @return string */ protected function compileAggregate(Builder $query, $aggregate) From f371e35a03a675844b7aa0b052842bd075acfac1 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Mon, 5 May 2025 04:34:49 +0300 Subject: [PATCH 084/158] Fix typo in PHPDoc (#55636) --- src/Illuminate/Support/Facades/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 11f6059af71c..8461fa41b1ee 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -44,7 +44,7 @@ * @method static void setRequestLocale(string $locale) * @method static void setDefaultRequestLocale(string $locale) * @method static mixed user(string|null $guard = null) - * @method static \Illuminate\Routing\Route|(object|string|null route(string|null $param = null, mixed $default = null) + * @method static \Illuminate\Routing\Route|object|string|null route(string|null $param = null, mixed $default = null) * @method static string fingerprint() * @method static \Illuminate\Http\Request setJson(\Symfony\Component\HttpFoundation\InputBag $json) * @method static \Closure getUserResolver() From b4cf99ed0d71a052fd96ca2712f02509028d83f9 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Mon, 5 May 2025 01:35:21 +0000 Subject: [PATCH 085/158] Update facade docblocks --- src/Illuminate/Support/Facades/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 8461fa41b1ee..8200fcec7b34 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -151,7 +151,7 @@ * @method static string|array|null cookie(string|null $key = null, string|array|null $default = null) * @method static array allFiles() * @method static bool hasFile(string $key) - * @method static array|(\Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null file(string|null $key = null, mixed $default = null) + * @method static array|\Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null file(string|null $key = null, mixed $default = null) * @method static \Illuminate\Http\Request dump(mixed $keys = []) * @method static never dd(mixed ...$args) * @method static bool exists(string|array $key) From ae9153462bcedfde96d0ec9192e2ef5bdf87ce0c Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Sun, 4 May 2025 21:41:16 -0400 Subject: [PATCH 086/158] [12.x] Allow naming queued closures (#55634) * Add failing test * Add ability to name queued closures * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Queue/CallQueuedClosure.php | 24 ++++++++++++++++++- .../Integration/Queue/JobDispatchingTest.php | 18 ++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Queue/CallQueuedClosure.php b/src/Illuminate/Queue/CallQueuedClosure.php index 732600ccfea1..34bdf3b85796 100644 --- a/src/Illuminate/Queue/CallQueuedClosure.php +++ b/src/Illuminate/Queue/CallQueuedClosure.php @@ -22,6 +22,13 @@ class CallQueuedClosure implements ShouldQueue */ public $closure; + /** + * The name assigned to the job. + * + * @var string|null + */ + public $name = null; + /** * The callbacks that should be executed on failure. * @@ -105,6 +112,21 @@ public function displayName() { $reflection = new ReflectionFunction($this->closure->getClosure()); - return 'Closure ('.basename($reflection->getFileName()).':'.$reflection->getStartLine().')'; + $prefix = is_null($this->name) ? '' : "{$this->name} - "; + + return $prefix.'Closure ('.basename($reflection->getFileName()).':'.$reflection->getStartLine().')'; + } + + /** + * Assign a name to the job. + * + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; } } diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index eddfd83c23c1..441cb59dea97 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -166,6 +166,24 @@ public function testQueueMayBeNullForJobQueueingAndJobQueuedEvent() $this->assertNull($events[3]->queue); } + public function testQueuedClosureCanBeNamed() + { + Config::set('queue.default', 'database'); + $events = []; + $this->app['events']->listen(function (JobQueued $e) use (&$events) { + $events[] = $e; + }); + + dispatch(function () { + // + })->name('custom name'); + + $this->assertCount(1, $events); + $this->assertInstanceOf(JobQueued::class, $events[0]); + $this->assertSame('custom name', $events[0]->job->name); + $this->assertStringContainsString('custom name', $events[0]->job->displayName()); + } + public function testCanDisableDispatchingAfterResponse() { Job::dispatchAfterResponse('test'); From 6d825edfbb8b92711d2a2fb490f07725abb79254 Mon Sep 17 00:00:00 2001 From: Ryan Chandler Date: Mon, 5 May 2025 02:43:35 +0100 Subject: [PATCH 087/158] Add assertRedirectBack assertion method (#55635) --- src/Illuminate/Testing/TestResponse.php | 17 +++++++++++++++++ tests/Testing/TestResponseTest.php | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 3f6f59a36730..45d970959536 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -213,6 +213,23 @@ public function assertRedirectContains($uri) return $this; } + /** + * Assert whether the response is redirecting back to the previous location. + * + * @return $this + */ + public function assertRedirectBack() + { + PHPUnit::withResponse($this)->assertTrue( + $this->isRedirect(), + $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()), + ); + + $this->assertLocation(app('url')->previous()); + + return $this; + } + /** * Assert whether the response is redirecting to a given route. * diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index cae497bdd133..32642d0df55c 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -2652,6 +2652,27 @@ public function testAssertRedirect() $response->assertRedirect(); } + public function testAssertRedirectBack() + { + app()->instance('session.store', $store = new Store('test-session', new ArraySessionHandler(1))); + + $store->setPreviousUrl('https://url.com'); + + app('url')->setSessionResolver(fn () => app('session.store')); + + $response = TestResponse::fromBaseResponse( + (new Response('', 302))->withHeaders(['Location' => 'https://url.com']) + ); + + $response->assertRedirectBack(); + + $this->expectException(ExpectationFailedException::class); + + $store->setPreviousUrl('https://url.net'); + + $response->assertRedirectBack(); + } + public function testGetDecryptedCookie() { $response = TestResponse::fromBaseResponse( From b9e395b2b7b632c363858c900f4db83b529722d7 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 5 May 2025 01:44:01 +0000 Subject: [PATCH 088/158] Apply fixes from StyleCI --- 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 45d970959536..2f3d2ea32f45 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -215,7 +215,7 @@ public function assertRedirectContains($uri) /** * Assert whether the response is redirecting back to the previous location. - * + * * @return $this */ public function assertRedirectBack() From 1dda4638cea08781c536ee3a7b424e07c6653da4 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sun, 4 May 2025 21:46:50 -0400 Subject: [PATCH 089/158] typehints for bindings (#55633) --- src/Illuminate/Database/Query/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 56c3996779a4..93fb2799f648 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -4171,7 +4171,7 @@ public function getRawBindings() * Set the bindings on the query builder. * * @param list $bindings - * @param string $type + * @param "select"|"from"|"join"|"where"|"groupBy"|"having"|"order"|"union"|"unionOrder" $type * @return $this * * @throws \InvalidArgumentException @@ -4191,7 +4191,7 @@ public function setBindings(array $bindings, $type = 'where') * Add a binding to the query. * * @param mixed $value - * @param string $type + * @param "select"|"from"|"join"|"where"|"groupBy"|"having"|"order"|"union"|"unionOrder" $type * @return $this * * @throws \InvalidArgumentException From 1d4667da1aad81309477ea49e44b2f4957481058 Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Mon, 5 May 2025 10:47:49 +0900 Subject: [PATCH 090/158] [12.x] add PHP Doc types to arrays for methods in Database\Grammar (#55629) --- src/Illuminate/Database/Grammar.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Grammar.php b/src/Illuminate/Database/Grammar.php index d56482dc889b..1d437f0566ce 100755 --- a/src/Illuminate/Database/Grammar.php +++ b/src/Illuminate/Database/Grammar.php @@ -31,8 +31,8 @@ public function __construct(Connection $connection) /** * Wrap an array of values. * - * @param array $values - * @return array + * @param array<\Illuminate\Contracts\Database\Query\Expression|string> $values + * @return array */ public function wrapArray(array $values) { @@ -136,7 +136,7 @@ protected function wrapAliasedTable($value, $prefix = null) /** * Wrap the given value segments. * - * @param array $segments + * @param list $segments * @return string */ protected function wrapSegments($segments) @@ -190,7 +190,7 @@ protected function isJsonSelector($value) /** * Convert an array of column names into a delimited string. * - * @param array $columns + * @param array<\Illuminate\Contracts\Database\Query\Expression|string> $columns * @return string */ public function columnize(array $columns) @@ -201,7 +201,7 @@ public function columnize(array $columns) /** * Create query parameter place-holders for an array. * - * @param array $values + * @param array $values * @return string */ public function parameterize(array $values) @@ -223,7 +223,7 @@ public function parameter($value) /** * Quote the given string literal. * - * @param string|array $value + * @param string|array $value * @return string */ public function quoteString($value) From 25ba38e4880afe0ff8739260e228d3c356fb7eca Mon Sep 17 00:00:00 2001 From: apreiml Date: Tue, 6 May 2025 00:26:20 +0200 Subject: [PATCH 091/158] fix trim null arg deprecation (#55649) --- src/Illuminate/Log/LogManager.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index da69039adcb1..270b716f9d8d 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -628,6 +628,10 @@ protected function parseDriver($driver) $driver ??= 'null'; } + if ($driver === null) { + return null; + } + return trim($driver); } From 2ca51e4753f23d539dfce11b5a092d9225e36a10 Mon Sep 17 00:00:00 2001 From: "Gabriel R. Barbosa" Date: Mon, 5 May 2025 20:32:05 -0300 Subject: [PATCH 092/158] [12.x] Support predis/predis 3.x (#55641) * Support predis/predis 3.x * Update src/Illuminate/Redis/composer.json --- composer.json | 4 ++-- src/Illuminate/Redis/composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 708b0c12c893..49c85f60f0f0 100644 --- a/composer.json +++ b/composer.json @@ -116,7 +116,7 @@ "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", - "predis/predis": "^2.3", + "predis/predis": "^2.4.0|^3.0.0", "resend/resend-php": "^0.10.0", "symfony/cache": "^7.2.0", "symfony/http-client": "^7.2.0", @@ -189,7 +189,7 @@ "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", + "predis/predis": "Required to use the predis connector (^2.4.0|^3.0.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index 3addbb07ff68..f7e219a23964 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -27,7 +27,7 @@ }, "suggest": { "ext-redis": "Required to use the phpredis connector (^4.0|^5.0|^6.0).", - "predis/predis": "Required to use the predis connector (^2.3)." + "predis/predis": "Required to use the predis connector (^2.4.0|3.0.0)." }, "extra": { "branch-alias": { From 9258e94a0a8d2cd1e507277ad18158a91d66c912 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 19:09:49 -0500 Subject: [PATCH 093/158] Bump vite in /src/Illuminate/Foundation/resources/exceptions/renderer (#55655) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.18 to 5.4.19. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.19/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.19/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.19 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 45eee2341a84..1935db7d4b58 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.18", + "vite": "^5.4.19", "vite-require": "^0.2.3" } }, @@ -2106,9 +2106,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.18", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", - "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "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 efa13c77fc74..d9f2b42b685e 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.18", + "vite": "^5.4.19", "vite-require": "^0.2.3" } } From fd07c3269b898e4f90cc3bc812c154ba5c78641f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 6 May 2025 01:10:08 +0100 Subject: [PATCH 094/158] [12.x] Fix predis versions (#55654) * Fix predis versions * Fixes * Update composer.json --- composer.json | 4 ++-- src/Illuminate/Redis/composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 49c85f60f0f0..5d89e5c76109 100644 --- a/composer.json +++ b/composer.json @@ -116,7 +116,7 @@ "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", - "predis/predis": "^2.4.0|^3.0.0", + "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.0", "symfony/cache": "^7.2.0", "symfony/http-client": "^7.2.0", @@ -189,7 +189,7 @@ "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.4.0|^3.0.0).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index f7e219a23964..05535a2460b2 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -27,7 +27,7 @@ }, "suggest": { "ext-redis": "Required to use the phpredis connector (^4.0|^5.0|^6.0).", - "predis/predis": "Required to use the predis connector (^2.4.0|3.0.0)." + "predis/predis": "Required to use the predis connector (^2.3|^3.0)." }, "extra": { "branch-alias": { From 62b50f69294502fa718fa85ec87a2b985a0cd0eb Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 6 May 2025 09:23:56 +0800 Subject: [PATCH 095/158] Update `update-assets.yml` Fix write permission --- .github/workflows/update-assets.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/update-assets.yml b/.github/workflows/update-assets.yml index 8ecd63efce0a..0563172549a1 100644 --- a/.github/workflows/update-assets.yml +++ b/.github/workflows/update-assets.yml @@ -8,6 +8,9 @@ on: - '/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json' workflow_dispatch: +permissions: + contents: write + jobs: update: runs-on: ubuntu-latest From 94c26967f8f7eebd61af49a90f1ee49b1bb6758b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 6 May 2025 09:31:48 +0800 Subject: [PATCH 096/158] Ignore `node_modules` directory Signed-off-by: Mior Muhammad Zaki --- .../Foundation/resources/exceptions/renderer/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/Illuminate/Foundation/resources/exceptions/renderer/.gitignore diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/.gitignore b/src/Illuminate/Foundation/resources/exceptions/renderer/.gitignore new file mode 100644 index 000000000000..07e6e472cc75 --- /dev/null +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/.gitignore @@ -0,0 +1 @@ +/node_modules From abb1119fc411ad4024e50d5a8726db004494cddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Andr=C3=A9s=20L=2E?= Date: Tue, 6 May 2025 15:32:29 -0500 Subject: [PATCH 097/158] [11.x] Bump minimum league/commonmark (#55660) * Bump minimum league/commonmark * security: update league/commonmark to ^2.7 in Mail and Support components --- composer.json | 2 +- src/Illuminate/Mail/composer.json | 2 +- src/Illuminate/Support/composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c6dc2e278836..0c72143375bb 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 23f0cd246676..b434037964a4 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -20,7 +20,7 @@ "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "psr/log": "^1.0|^2.0|^3.0", "symfony/mailer": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5" diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 286a90b0a76e..493ce84eba14 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -49,7 +49,7 @@ "suggest": { "illuminate/filesystem": "Required to use the Composer class (^11.0).", "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", - "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", "league/uri": "Required to use the Uri class (^7.5.1).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", "symfony/process": "Required to use the Composer class (^7.0).", From 4ed7c372081833b5c1eb630b1ddbb138dca3033f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Tue, 6 May 2025 22:32:43 +0200 Subject: [PATCH 098/158] [12.x] Bump minimum league/commonmark (#55659) * [12.x] Bump minimum league/commonmark * Update src/Illuminate/Mail/composer.json * Update src/Illuminate/Support/composer.json --- composer.json | 2 +- src/Illuminate/Mail/composer.json | 2 +- src/Illuminate/Support/composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 5d89e5c76109..8c76eb8b7c06 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index cf28958fdcad..6f976a9e45dc 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -20,7 +20,7 @@ "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", "illuminate/support": "^12.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "psr/log": "^1.0|^2.0|^3.0", "symfony/mailer": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5" diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 4b53abef7e57..404eff091464 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -49,7 +49,7 @@ "suggest": { "illuminate/filesystem": "Required to use the Composer class (^12.0).", "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", - "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", "league/uri": "Required to use the Uri class (^7.5.1).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", "symfony/process": "Required to use the Composer class (^7.2).", From cbe68cc718a11affb2693df78910992d8b619ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Wed, 7 May 2025 01:30:52 +0200 Subject: [PATCH 099/158] [12.x] Fix typo in MemoizedStoreTest (#55662) --- tests/Integration/Cache/MemoizedStoreTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Integration/Cache/MemoizedStoreTest.php b/tests/Integration/Cache/MemoizedStoreTest.php index 028688b262e2..8ca80eda5135 100644 --- a/tests/Integration/Cache/MemoizedStoreTest.php +++ b/tests/Integration/Cache/MemoizedStoreTest.php @@ -70,7 +70,7 @@ public function test_null_values_are_memoized_when_retrieving_single_value() $this->assertNull($memoized); } - public function test_it_can_memoize_when_retrieving_mulitple_values() + public function test_it_can_memoize_when_retrieving_multiple_values() { Cache::put('name.0', 'Tim', 60); Cache::put('name.1', 'Taylor', 60); @@ -116,7 +116,7 @@ public function test_it_uses_correct_keys_for_getMultiple() $this->assertSame($cacheValue, $memoValue); } - public function test_null_values_are_memoized_when_retrieving_mulitple_values() + public function test_null_values_are_memoized_when_retrieving_multiple_values() { $live = Cache::getMultiple(['name.0', 'name.1']); $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); @@ -132,7 +132,7 @@ public function test_null_values_are_memoized_when_retrieving_mulitple_values() $this->assertSame($memoized, ['name.0' => null, 'name.1' => null]); } - public function test_it_can_retrieve_already_memoized_and_not_yet_memoized_values_when_retrieving_mulitple_values() + public function test_it_can_retrieve_already_memoized_and_not_yet_memoized_values_when_retrieving_multiple_values() { Cache::put('name.0', 'Tim', 60); Cache::put('name.1', 'Taylor', 60); From e333cc80b8f5414daec8ef7b15c1333685c4988b Mon Sep 17 00:00:00 2001 From: Wim Griffioen Date: Wed, 7 May 2025 02:35:45 +0200 Subject: [PATCH 100/158] [12.x] Queue event listeners with enum values (#55656) * Queue event listeners with enum values * Apply code styling fixes --- src/Illuminate/Events/Dispatcher.php | 6 ++++-- tests/Events/QueuedEventsTest.php | 30 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index ee409586efc8..c49a49d30ad7 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -21,6 +21,8 @@ use Illuminate\Support\Traits\ReflectsClosures; use ReflectionClass; +use function Illuminate\Support\enum_value; + class Dispatcher implements DispatcherContract { use Macroable, ReflectsClosures; @@ -631,8 +633,8 @@ protected function queueHandler($class, $method, $arguments) : $listener->delay ?? null; is_null($delay) - ? $connection->pushOn($queue, $job) - : $connection->laterOn($queue, $delay, $job); + ? $connection->pushOn(enum_value($queue), $job) + : $connection->laterOn(enum_value($queue), $delay, $job); } /** diff --git a/tests/Events/QueuedEventsTest.php b/tests/Events/QueuedEventsTest.php index 9724a36bdbd2..4f9ec170ba1f 100644 --- a/tests/Events/QueuedEventsTest.php +++ b/tests/Events/QueuedEventsTest.php @@ -199,6 +199,23 @@ public function testQueuePropagateMiddleware() && $job->middleware[0]->b === 'bar'; }); } + + public function testDispatchesOnQueueDefinedWithEnum() + { + $d = new Dispatcher; + $queue = m::mock(Queue::class); + + $fakeQueue = new QueueFake(new Container); + + $d->setQueueResolver(function () use ($fakeQueue) { + return $fakeQueue; + }); + + $d->listen('some.event', TestDispatcherViaQueueSupportsEnum::class.'@handle'); + $d->dispatch('some.event', ['foo', 'bar']); + + $fakeQueue->assertPushedOn('enumerated-queue', CallQueuedListener::class); + } } class TestDispatcherQueuedHandler implements ShouldQueue @@ -367,3 +384,16 @@ public function withDelay($event) return 20; } } + +enum TestQueueType: string +{ + case EnumeratedQueue = 'enumerated-queue'; +} + +class TestDispatcherViaQueueSupportsEnum implements ShouldQueue +{ + public function viaQueue() + { + return TestQueueType::EnumeratedQueue; + } +} From 67f3f84068add2bd61005cd5aff89943f7d860ed Mon Sep 17 00:00:00 2001 From: Adam Griffith <5164766+adamjgriffith@users.noreply.github.com> Date: Wed, 7 May 2025 18:13:13 +0100 Subject: [PATCH 101/158] [12.x] Implement releaseAfter method in RateLimited middleware (#55671) * Add test for new releaseAfter method * Implement releaseAfter method --- .../Queue/Middleware/RateLimited.php | 22 ++++++++++- .../Queue/Middleware/RateLimitedWithRedis.php | 2 +- tests/Integration/Queue/RateLimitedTest.php | 39 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Queue/Middleware/RateLimited.php b/src/Illuminate/Queue/Middleware/RateLimited.php index 1b4b2b78d941..a2b5343e59db 100644 --- a/src/Illuminate/Queue/Middleware/RateLimited.php +++ b/src/Illuminate/Queue/Middleware/RateLimited.php @@ -25,6 +25,13 @@ class RateLimited */ protected $limiterName; + /** + * The number of seconds before a job should be available again if the limit is exceeded. + * + * @var \DateTimeInterface|int|null + */ + public $releaseAfter; + /** * Indicates if the job should be released if the limit is exceeded. * @@ -89,7 +96,7 @@ protected function handleJob($job, $next, array $limits) foreach ($limits as $limit) { if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { return $this->shouldRelease - ? $job->release($this->getTimeUntilNextRetry($limit->key)) + ? $job->release($this->releaseAfter ?: $this->getTimeUntilNextRetry($limit->key)) : false; } @@ -99,6 +106,19 @@ protected function handleJob($job, $next, array $limits) return $next($job); } + /** + * Set the delay (in seconds) to release the job back to the queue. + * + * @param \DateTimeInterface|int $releaseAfter + * @return $this + */ + public function releaseAfter($releaseAfter) + { + $this->releaseAfter = $releaseAfter; + + return $this; + } + /** * Do not release the job back to the queue if the limit is exceeded. * diff --git a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php index bb87b101d404..25870e08f034 100644 --- a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php +++ b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php @@ -50,7 +50,7 @@ protected function handleJob($job, $next, array $limits) foreach ($limits as $limit) { if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) { return $this->shouldRelease - ? $job->release($this->getTimeUntilNextRetry($limit->key)) + ? $job->release($this->releaseAfter ?: $this->getTimeUntilNextRetry($limit->key)) : false; } } diff --git a/tests/Integration/Queue/RateLimitedTest.php b/tests/Integration/Queue/RateLimitedTest.php index 334ec657536e..45116af9e3e8 100644 --- a/tests/Integration/Queue/RateLimitedTest.php +++ b/tests/Integration/Queue/RateLimitedTest.php @@ -138,6 +138,18 @@ public function testJobsCanHaveConditionalRateLimits() $this->assertJobWasReleased(NonAdminTestJob::class); } + public function testRateLimitedJobsCanBeSkippedOnLimitReachedAndReleasedAfter() + { + $rateLimiter = $this->app->make(RateLimiter::class); + + $rateLimiter->for('test', function ($job) { + return Limit::perHour(1); + }); + + $this->assertJobRanSuccessfully(RateLimitedReleaseAfterTestJob::class); + $this->assertJobWasReleasedAfter(RateLimitedReleaseAfterTestJob::class, 60); + } + public function testMiddlewareSerialization() { $rateLimited = new RateLimited('limiterName'); @@ -192,6 +204,25 @@ protected function assertJobWasReleased($class) $this->assertFalse($class::$handled); } + protected function assertJobWasReleasedAfter($class, $releaseAfter) + { + $class::$handled = false; + $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app); + + $job = m::mock(Job::class); + + $job->shouldReceive('hasFailed')->once()->andReturn(false); + $job->shouldReceive('release')->once()->withArgs([$releaseAfter]); + $job->shouldReceive('isReleased')->andReturn(true); + $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true); + + $instance->call($job, [ + 'command' => serialize($command = new $class), + ]); + + $this->assertFalse($class::$handled); + } + protected function assertJobWasSkipped($class) { $class::$handled = false; @@ -341,6 +372,14 @@ public function middleware() } } +class RateLimitedReleaseAfterTestJob extends RateLimitedTestJob +{ + public function middleware() + { + return [(new RateLimited('test'))->releaseAfter(60)]; + } +} + enum BackedEnumNamedRateLimited: string { case FOO = 'bar'; From 99debbaa2b605c862af15c60ad0984836b90624f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Wed, 7 May 2025 19:13:46 +0200 Subject: [PATCH 102/158] [12.x] Improve Cache Tests (#55670) * move cleanup to setup * fake sleep to speed test up by 2s * add missing test for exception --- tests/Integration/Cache/FileCacheLockTest.php | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/tests/Integration/Cache/FileCacheLockTest.php b/tests/Integration/Cache/FileCacheLockTest.php index c9eff5ced0e9..575f1220b852 100644 --- a/tests/Integration/Cache/FileCacheLockTest.php +++ b/tests/Integration/Cache/FileCacheLockTest.php @@ -3,17 +3,25 @@ namespace Illuminate\Tests\Integration\Cache; use Exception; +use Illuminate\Contracts\Cache\LockTimeoutException; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Sleep; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; #[WithConfig('cache.default', 'file')] class FileCacheLockTest extends TestCase { - public function testLocksCanBeAcquiredAndReleased() + protected function setUp(): void { + parent::setUp(); + + // flush lock from previous tests Cache::lock('foo')->forceRelease(); + } + public function testLocksCanBeAcquiredAndReleased() + { $lock = Cache::lock('foo', 10); $this->assertTrue($lock->get()); $this->assertFalse(Cache::lock('foo', 10)->get()); @@ -27,7 +35,6 @@ public function testLocksCanBeAcquiredAndReleased() public function testLocksCanBlockForSeconds() { - Cache::lock('foo')->forceRelease(); $this->assertSame('taylor', Cache::lock('foo', 10)->block(1, function () { return 'taylor'; })); @@ -38,11 +45,11 @@ public function testLocksCanBlockForSeconds() public function testConcurrentLocksAreReleasedSafely() { - Cache::lock('foo')->forceRelease(); + Sleep::fake(syncWithCarbon: true); $firstLock = Cache::lock('foo', 1); $this->assertTrue($firstLock->get()); - sleep(2); + Sleep::for(2)->seconds(); $secondLock = Cache::lock('foo', 10); $this->assertTrue($secondLock->get()); @@ -54,8 +61,6 @@ public function testConcurrentLocksAreReleasedSafely() public function testLocksWithFailedBlockCallbackAreReleased() { - Cache::lock('foo')->forceRelease(); - $firstLock = Cache::lock('foo', 10); try { @@ -75,8 +80,6 @@ public function testLocksWithFailedBlockCallbackAreReleased() public function testLocksCanBeReleasedUsingOwnerToken() { - Cache::lock('foo')->forceRelease(); - $firstLock = Cache::lock('foo', 10); $this->assertTrue($firstLock->get()); $owner = $firstLock->owner(); @@ -89,8 +92,6 @@ public function testLocksCanBeReleasedUsingOwnerToken() public function testOwnerStatusCanBeCheckedAfterRestoringLock() { - Cache::lock('foo')->forceRelease(); - $firstLock = Cache::lock('foo', 10); $this->assertTrue($firstLock->get()); $owner = $firstLock->owner(); @@ -101,8 +102,6 @@ public function testOwnerStatusCanBeCheckedAfterRestoringLock() public function testOtherOwnerDoesNotOwnLockAfterRestore() { - Cache::lock('foo')->forceRelease(); - $firstLock = Cache::lock('foo', 10); $this->assertTrue($firstLock->isOwnedBy(null)); $this->assertTrue($firstLock->get()); @@ -112,4 +111,16 @@ public function testOtherOwnerDoesNotOwnLockAfterRestore() $this->assertTrue($secondLock->isOwnedBy($firstLock->owner())); $this->assertFalse($secondLock->isOwnedByCurrentProcess()); } + + public function testExceptionIfBlockCanNotAcquireLock() + { + Sleep::fake(syncWithCarbon: true); + + // acquire and not release lock + Cache::lock('foo', 10)->get(); + + // try to get lock and hit block timeout + $this->expectException(LockTimeoutException::class); + Cache::lock('foo', 10)->block(5); + } } From 30d92c956b38cbc2dcb996f03aca0d352c0f89df Mon Sep 17 00:00:00 2001 From: Ashley Shenton Date: Wed, 7 May 2025 18:20:23 +0100 Subject: [PATCH 103/158] [12.x] Only pass model IDs to Eloquent `whereAttachedTo` method (#55666) * test: update whereAttachedTo builder tests to showcase the issue * test: update whereAttachedTo integration tests to showcase the issue * fix: pluck the key from the collection before passing to whereKey --- .../Database/Eloquent/Concerns/QueriesRelationships.php | 2 +- tests/Database/DatabaseEloquentBuilderTest.php | 3 +++ tests/Database/DatabaseEloquentIntegrationTest.php | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index f9f21536d1fc..b7955bd111a9 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -803,7 +803,7 @@ public function whereAttachedTo($related, $relationshipName = null, $boolean = ' $this->has( $relationshipName, boolean: $boolean, - callback: fn (Builder $query) => $query->whereKey($relatedCollection), + callback: fn (Builder $query) => $query->whereKey($relatedCollection->pluck($related->getKeyName())), ); return $this; diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 96a6beed7e20..09146044a349 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -1282,6 +1282,7 @@ public function testWhereAttachedTo() { $related = new EloquentBuilderTestModelFarRelatedStub; $related->id = 49; + $related->name = 'test'; $builder = EloquentBuilderTestModelParentStub::whereAttachedTo($related, 'roles'); @@ -1292,9 +1293,11 @@ public function testWhereAttachedToCollection() { $model1 = new EloquentBuilderTestModelParentStub; $model1->id = 3; + $model1->name = 'test3'; $model2 = new EloquentBuilderTestModelParentStub; $model2->id = 4; + $model2->name = 'test4'; $builder = EloquentBuilderTestModelFarRelatedStub::whereAttachedTo(new Collection([$model1, $model2]), 'roles'); diff --git a/tests/Database/DatabaseEloquentIntegrationTest.php b/tests/Database/DatabaseEloquentIntegrationTest.php index 1d176afa00f1..c27cbfe476b1 100644 --- a/tests/Database/DatabaseEloquentIntegrationTest.php +++ b/tests/Database/DatabaseEloquentIntegrationTest.php @@ -173,6 +173,7 @@ protected function createSchema() $this->schema($connection)->create('achievements', function ($table) { $table->increments('id'); + $table->integer('status')->nullable(); }); $this->schema($connection)->create('eloquent_test_achievement_eloquent_test_user', function ($table) { @@ -1497,7 +1498,7 @@ public function testWhereAttachedTo() $user1 = EloquentTestUser::create(['email' => 'user1@gmail.com']); $user2 = EloquentTestUser::create(['email' => 'user2@gmail.com']); $user3 = EloquentTestUser::create(['email' => 'user3@gmail.com']); - $achievement1 = EloquentTestAchievement::create(); + $achievement1 = EloquentTestAchievement::create(['status' => 3]); $achievement2 = EloquentTestAchievement::create(); $achievement3 = EloquentTestAchievement::create(); @@ -2988,6 +2989,7 @@ class EloquentTestAchievement extends Eloquent public $timestamps = false; protected $table = 'achievements'; + protected $guarded = []; public function eloquentTestUsers() { From 0900dc5f40011736cf223cb5cf0f1a1257af218a Mon Sep 17 00:00:00 2001 From: Vincent Neubauer Date: Wed, 7 May 2025 19:23:00 +0200 Subject: [PATCH 104/158] feat(bus): allow adding multiple jobs to chain (#55668) `prependToChain()` reverses the collection before inserting the jobs one by one to ensure that the first job is processed before the second one. --- src/Illuminate/Bus/Queueable.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Bus/Queueable.php b/src/Illuminate/Bus/Queueable.php index c42614d9e65d..b8a439dad2ac 100644 --- a/src/Illuminate/Bus/Queueable.php +++ b/src/Illuminate/Bus/Queueable.php @@ -221,9 +221,11 @@ public function chain($chain) */ public function prependToChain($job) { - $jobs = ChainedBatch::prepareNestedBatches(new Collection([$job])); + $jobs = ChainedBatch::prepareNestedBatches(Collection::wrap($job)); - $this->chained = Arr::prepend($this->chained, $this->serializeJob($jobs->first())); + foreach ($jobs->reverse() as $job) { + $this->chained = Arr::prepend($this->chained, $this->serializeJob($job)); + } return $this; } @@ -236,9 +238,11 @@ public function prependToChain($job) */ public function appendToChain($job) { - $jobs = ChainedBatch::prepareNestedBatches(new Collection([$job])); + $jobs = ChainedBatch::prepareNestedBatches(Collection::wrap($job)); - $this->chained = array_merge($this->chained, [$this->serializeJob($jobs->first())]); + foreach ($jobs as $job) { + $this->chained = array_merge($this->chained, [$this->serializeJob($job)]); + } return $this; } From ac6efaa91fe2897bc6b9a2428fa618401a4cf148 Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Thu, 8 May 2025 02:26:04 +0900 Subject: [PATCH 105/158] =?UTF-8?q?[12.x]=20add=20generics=20to=20QueryBui?= =?UTF-8?q?lder=E2=80=99s=20column=20related=20methods=20(#55663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [12.x] add generics to QueryBuilder’s column related methods * fix styling * more detailed types * narrow types * more narrowing * not at this time * fix test * use type alias * more use of alias * not now * fix styling * use covariant * fixed * fix styling * dont include string in SubQueryBuilderTypes * reorder * add generics to callback * fix styling * remove type alias * fix singlular name * replace all aliases * revert --- src/Illuminate/Database/Query/Builder.php | 37 ++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 93fb2799f648..ed13026725ce 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -100,7 +100,7 @@ class Builder implements BuilderContract /** * The columns that should be returned. * - * @var array|null + * @var array|null */ public $columns; @@ -278,7 +278,7 @@ public function __construct( /** * Set the columns to be selected. * - * @param array|mixed $columns + * @param mixed $columns * @return $this */ public function select($columns = ['*']) @@ -432,7 +432,7 @@ protected function prependDatabaseNameIfCrossDatabaseQuery($query) /** * Add a new select column to the query. * - * @param array|mixed $column + * @param mixed $column * @return $this */ public function addSelect($column) @@ -3019,7 +3019,7 @@ public function toRawSql() * Execute a query for a single record by ID. * * @param int|string $id - * @param array|string $columns + * @param string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @return object|null */ public function find($id, $columns = ['*']) @@ -3033,7 +3033,7 @@ public function find($id, $columns = ['*']) * @template TValue * * @param mixed $id - * @param (\Closure(): TValue)|list|string $columns + * @param (\Closure(): TValue)|string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @param (\Closure(): TValue)|null $callback * @return object|TValue */ @@ -3096,7 +3096,7 @@ public function soleValue($column) /** * Execute the query as a "select" statement. * - * @param array|string $columns + * @param string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @return \Illuminate\Support\Collection */ public function get($columns = ['*']) @@ -3152,7 +3152,7 @@ protected function withoutGroupLimitKeys($items) * Paginate the given query into a simple paginator. * * @param int|\Closure $perPage - * @param array|string $columns + * @param string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @param string $pageName * @param int|null $page * @param \Closure|int|null $total @@ -3180,7 +3180,7 @@ public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $p * This is more efficient on larger data-sets, etc. * * @param int $perPage - * @param array|string $columns + * @param string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @param string $pageName * @param int|null $page * @return \Illuminate\Contracts\Pagination\Paginator @@ -3203,7 +3203,7 @@ public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'pag * This is more efficient on larger data-sets, etc. * * @param int|null $perPage - * @param array|string $columns + * @param string|\Illuminate\Contracts\Database\Query\Expression|array $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator @@ -3250,7 +3250,7 @@ protected function ensureOrderForCursorPagination($shouldReverse = false) /** * Get the count of the total records for the paginator. * - * @param array $columns + * @param array $columns * @return int */ public function getCountForPagination($columns = ['*']) @@ -3272,8 +3272,8 @@ public function getCountForPagination($columns = ['*']) /** * Run a pagination count query. * - * @param array $columns - * @return array + * @param array $columns + * @return array */ protected function runPaginationCountQuery($columns = ['*']) { @@ -3313,7 +3313,8 @@ protected function cloneForPaginationCount() /** * Remove the column aliases since they will break count queries. * - * @return array + * @param array $columns + * @return array */ protected function withoutSelectAliases(array $columns) { @@ -3677,9 +3678,11 @@ protected function setAggregate($function, $columns) * * After running the callback, the columns are reset to the original value. * - * @param array $columns - * @param callable $callback - * @return mixed + * @template TResult + * + * @param array $columns + * @param callable(): TResult $callback + * @return TResult */ protected function onceWithColumns($columns, $callback) { @@ -4081,7 +4084,7 @@ protected function forSubQuery() /** * Get all of the query builder's columns in a text-only array with all expressions evaluated. * - * @return array + * @return list */ public function getColumns() { From 52b588bcd8efc6d01bc1493d2d67848f8065f269 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 7 May 2025 17:29:01 +0000 Subject: [PATCH 106/158] Update version to v12.13.0 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0fed290349c5..91897ea0dd52 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.12.0'; + const VERSION = '12.13.0'; /** * The base path for the Laravel installation. From 9dae5547915401d68c167c6a31c68a4902b7c0ef Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 7 May 2025 17:30:46 +0000 Subject: [PATCH 107/158] Update CHANGELOG --- CHANGELOG.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55abba5ea7ce..0198d02e6505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.12.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.13.0...12.x) + +## [v12.13.0](https://github.com/laravel/framework/compare/v12.12.0...v12.13.0) - 2025-05-07 + +* [12.x] fix no arguments return type in request class by [@olivernybroe](https://github.com/olivernybroe) in https://github.com/laravel/framework/pull/55631 +* [12.x] Add support for callback evaluation in containsOneItem method by [@fernandokbs](https://github.com/fernandokbs) in https://github.com/laravel/framework/pull/55622 +* [12.x] add generics to aggregate related methods and properties by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55628 +* [12.x] Fix typo in PHPDoc by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/55636 +* [12.x] Allow naming queued closures by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/55634 +* [12.x] Add `assertRedirectBack` assertion method by [@ryangjchandler](https://github.com/ryangjchandler) in https://github.com/laravel/framework/pull/55635 +* [12.x] Typehints for bindings by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55633 +* [12.x] add PHP Doc types to arrays for methods in Database\Grammar by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55629 +* fix trim null arg deprecation by [@apreiml](https://github.com/apreiml) in https://github.com/laravel/framework/pull/55649 +* [12.x] Support predis/predis 3.x by [@gabrielrbarbosa](https://github.com/gabrielrbarbosa) in https://github.com/laravel/framework/pull/55641 +* Bump vite from 5.4.18 to 5.4.19 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/55655 +* [12.x] Fix predis versions by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/55654 +* [12.x] Bump minimum league/commonmark by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/55659 +* [12.x] Fix typo in MemoizedStoreTest by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/55662 +* [12.x] Queue event listeners with enum values by [@wgriffioen](https://github.com/wgriffioen) in https://github.com/laravel/framework/pull/55656 +* [12.x] Implement releaseAfter method in RateLimited middleware by [@adamjgriffith](https://github.com/adamjgriffith) in https://github.com/laravel/framework/pull/55671 +* [12.x] Improve Cache Tests by [@nuernbergerA](https://github.com/nuernbergerA) in https://github.com/laravel/framework/pull/55670 +* [12.x] Only pass model IDs to Eloquent `whereAttachedTo` method by [@ashleyshenton](https://github.com/ashleyshenton) in https://github.com/laravel/framework/pull/55666 +* feat(bus): allow adding multiple jobs to chain by [@dallyger](https://github.com/dallyger) in https://github.com/laravel/framework/pull/55668 +* [12.x] add generics to QueryBuilder’s column related methods by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55663 ## [v12.12.0](https://github.com/laravel/framework/compare/v12.11.1...v12.12.0) - 2025-05-01 From d100d1cee3916c21cf341e11dbb2619b2940d454 Mon Sep 17 00:00:00 2001 From: Nicholas Brantley Date: Wed, 7 May 2025 13:08:59 -0500 Subject: [PATCH 108/158] [12.x] Support `useCurrent` on date and year column types (#55619) * Support useCurrent on date and year column types Support useCurrent on date and year column types * formatting --------- Co-authored-by: Taylor Otwell --- .../Database/Schema/Grammars/MySqlGrammar.php | 20 ++++++ .../Schema/Grammars/PostgresGrammar.php | 8 +++ .../Schema/Grammars/SQLiteGrammar.php | 8 +++ .../Schema/Grammars/SqlServerGrammar.php | 8 +++ .../DatabaseMariaDbSchemaGrammarTest.php | 40 ++++++++++- .../DatabaseMySqlSchemaGrammarTest.php | 68 ++++++++++++++++++- .../DatabasePostgresSchemaGrammarTest.php | 19 ++++++ .../DatabaseSQLiteSchemaGrammarTest.php | 19 ++++++ .../Database/DatabaseSchemaBlueprintTest.php | 50 ++++++++++++++ .../DatabaseSqlServerSchemaGrammarTest.php | 19 ++++++ 10 files changed, 255 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index db992eac43ab..16e8634d3e6b 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -927,6 +927,16 @@ protected function typeJsonb(Fluent $column) */ protected function typeDate(Fluent $column) { + $isMaria = $this->connection->isMaria(); + $version = $this->connection->getServerVersion(); + + if ($isMaria || + (! $isMaria && version_compare($version, '8.0.13', '>='))) { + if ($column->useCurrent) { + $column->default(new Expression('(CURDATE())')); + } + } + return 'date'; } @@ -1024,6 +1034,16 @@ protected function typeTimestampTz(Fluent $column) */ protected function typeYear(Fluent $column) { + $isMaria = $this->connection->isMaria(); + $version = $this->connection->getServerVersion(); + + if ($isMaria || + (! $isMaria && version_compare($version, '8.0.13', '>='))) { + if ($column->useCurrent) { + $column->default(new Expression('(YEAR(CURDATE()))')); + } + } + return 'year'; } diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index a40f7a62e153..7e1e6a1d2fa8 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -922,6 +922,10 @@ protected function typeJsonb(Fluent $column) */ protected function typeDate(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_DATE')); + } + return 'date'; } @@ -1007,6 +1011,10 @@ protected function typeTimestampTz(Fluent $column) */ protected function typeYear(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression('EXTRACT(YEAR FROM CURRENT_DATE)')); + } + return $this->typeInteger($column); } diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 91222b7e83eb..8908836dd9c7 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -907,6 +907,10 @@ protected function typeJsonb(Fluent $column) */ protected function typeDate(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_DATE')); + } + return 'date'; } @@ -992,6 +996,10 @@ protected function typeTimestampTz(Fluent $column) */ protected function typeYear(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression("(CAST(strftime('%Y', 'now') AS INTEGER))")); + } + return $this->typeInteger($column); } diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 5e183a5dce76..f0608eb2e4dc 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -769,6 +769,10 @@ protected function typeJsonb(Fluent $column) */ protected function typeDate(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression('CAST(GETDATE() AS DATE)')); + } + return 'date'; } @@ -856,6 +860,10 @@ protected function typeTimestampTz(Fluent $column) */ protected function typeYear(Fluent $column) { + if ($column->useCurrent) { + $column->default(new Expression('CAST(YEAR(GETDATE()) AS INTEGER)')); + } + return $this->typeInteger($column); } diff --git a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php index 1224247e20de..dff05674d9ff 100755 --- a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php +++ b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php @@ -873,7 +873,11 @@ public function testAddingJsonb() public function testAddingDate() { - $blueprint = new Blueprint($this->getConnection(), 'users'); + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(true); + $conn->shouldReceive('getServerVersion')->andReturn('10.3.0'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->date('foo'); $statements = $blueprint->toSql(); @@ -881,15 +885,47 @@ public function testAddingDate() $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); } + public function testAddingDateWithDefaultCurrent() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(true); + $conn->shouldReceive('getServerVersion')->andReturn('10.3.0'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` date not null default (CURDATE())', $statements[0]); + } + public function testAddingYear() { - $blueprint = new Blueprint($this->getConnection(), 'users'); + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(true); + $conn->shouldReceive('getServerVersion')->andReturn('10.3.0'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->year('birth_year'); $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); } + public function testAddingYearWithDefaultCurrent() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(true); + $conn->shouldReceive('getServerVersion')->andReturn('10.3.0'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `birth_year` year not null default (YEAR(CURDATE()))', $statements[0]); + } + public function testAddingDateTime() { $blueprint = new Blueprint($this->getConnection(), 'users'); diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 09082dab62df..cfc6bb02df3b 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -872,7 +872,11 @@ public function testAddingJsonb() public function testAddingDate() { - $blueprint = new Blueprint($this->getConnection(), 'users'); + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('8.0.13'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->date('foo'); $statements = $blueprint->toSql(); @@ -880,15 +884,75 @@ public function testAddingDate() $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); } + public function testAddingDateWithDefaultCurrent() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('8.0.13'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` date not null default (CURDATE())', $statements[0]); + } + + public function testAddingDateWithDefaultCurrentOn57() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('5.7'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); + } + public function testAddingYear() { - $blueprint = new Blueprint($this->getConnection(), 'users'); + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('8.0.13'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->year('birth_year'); $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); } + public function testAddingYearWithDefaultCurrent() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('8.0.13'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `birth_year` year not null default (YEAR(CURDATE()))', $statements[0]); + } + + public function testAddingYearWithDefaultCurrentOn57() + { + $conn = $this->getConnection(); + $conn->shouldReceive('isMaria')->andReturn(false); + $conn->shouldReceive('getServerVersion')->andReturn('5.7'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); + } + public function testAddingDateTime() { $blueprint = new Blueprint($this->getConnection(), 'users'); diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index 0c31a6d2d06a..f3402250245f 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -714,6 +714,16 @@ public function testAddingDate() $this->assertSame('alter table "users" add column "foo" date not null', $statements[0]); } + public function testAddingDateWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "foo" date not null default CURRENT_DATE', $statements[0]); + } + public function testAddingYear() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -723,6 +733,15 @@ public function testAddingYear() $this->assertSame('alter table "users" add column "birth_year" integer not null', $statements[0]); } + public function testAddingYearWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "birth_year" integer not null default EXTRACT(YEAR FROM CURRENT_DATE)', $statements[0]); + } + public function testAddingJson() { $blueprint = new Blueprint($this->getConnection(), 'users'); diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index d9233f548fa9..26de4bcf0bf8 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -640,6 +640,16 @@ public function testAddingDate() $this->assertSame('alter table "users" add column "foo" date not null', $statements[0]); } + public function testAddingDateWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "foo" date not null default CURRENT_DATE', $statements[0]); + } + public function testAddingYear() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -649,6 +659,15 @@ public function testAddingYear() $this->assertSame('alter table "users" add column "birth_year" integer not null', $statements[0]); } + public function testAddingYearWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "birth_year" integer not null default (CAST(strftime(\'%Y\', \'now\') AS INTEGER))', $statements[0]); + } + public function testAddingDateTime() { $blueprint = new Blueprint($this->getConnection(), 'users'); diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index e8546270597b..e5f26379caa6 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -102,6 +102,31 @@ public function testDropIndexDefaultNamesWhenPrefixSupplied() $this->assertSame('prefix_geo_coordinates_spatialindex', $commands[0]->index); } + public function testDefaultCurrentDate() + { + $getSql = function ($grammar, $mysql57 = false) { + if ($grammar == 'MySql') { + $connection = $this->getConnection($grammar); + $mysql57 ? $connection->shouldReceive('getServerVersion')->andReturn('5.7') : $connection->shouldReceive('getServerVersion')->andReturn('8.0.13'); + $connection->shouldReceive('isMaria')->andReturn(false); + + return (new Blueprint($connection, 'users', function ($table) { + $table->date('created')->useCurrent(); + }))->toSql(); + } else { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->date('created')->useCurrent(); + })->toSql(); + } + }; + + $this->assertEquals(['alter table `users` add `created` date not null default (CURDATE())'], $getSql('MySql')); + $this->assertEquals(['alter table `users` add `created` date not null'], $getSql('MySql', mysql57: true)); + $this->assertEquals(['alter table "users" add column "created" date not null default CURRENT_DATE'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" add column "created" date not null default CURRENT_DATE'], $getSql('SQLite')); + $this->assertEquals(['alter table "users" add "created" date not null default CAST(GETDATE() AS DATE)'], $getSql('SqlServer')); + } + public function testDefaultCurrentDateTime() { $getSql = function ($grammar) { @@ -130,6 +155,31 @@ public function testDefaultCurrentTimestamp() $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $getSql('SqlServer')); } + public function testDefaultCurrentYear() + { + $getSql = function ($grammar, $mysql57 = false) { + if ($grammar == 'MySql') { + $connection = $this->getConnection($grammar); + $mysql57 ? $connection->shouldReceive('getServerVersion')->andReturn('5.7') : $connection->shouldReceive('getServerVersion')->andReturn('8.0.13'); + $connection->shouldReceive('isMaria')->andReturn(false); + + return (new Blueprint($connection, 'users', function ($table) { + $table->year('birth_year')->useCurrent(); + }))->toSql(); + } else { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->year('birth_year')->useCurrent(); + })->toSql(); + } + }; + + $this->assertEquals(['alter table `users` add `birth_year` year not null default (YEAR(CURDATE()))'], $getSql('MySql')); + $this->assertEquals(['alter table `users` add `birth_year` year not null'], $getSql('MySql', mysql57: true)); + $this->assertEquals(['alter table "users" add column "birth_year" integer not null default EXTRACT(YEAR FROM CURRENT_DATE)'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" add column "birth_year" integer not null default (CAST(strftime(\'%Y\', \'now\') AS INTEGER))'], $getSql('SQLite')); + $this->assertEquals(['alter table "users" add "birth_year" int not null default CAST(YEAR(GETDATE()) AS INTEGER)'], $getSql('SqlServer')); + } + public function testRemoveColumn() { $getSql = function ($grammar) { diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index d06003473498..c31cf83f5576 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -614,6 +614,16 @@ public function testAddingDate() $this->assertSame('alter table "users" add "foo" date not null', $statements[0]); } + public function testAddingDateWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->date('foo')->useCurrent(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add "foo" date not null default CAST(GETDATE() AS DATE)', $statements[0]); + } + public function testAddingYear() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -623,6 +633,15 @@ public function testAddingYear() $this->assertSame('alter table "users" add "birth_year" int not null', $statements[0]); } + public function testAddingYearWithDefaultCurrent() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->year('birth_year')->useCurrent(); + $statements = $blueprint->toSql(); + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add "birth_year" int not null default CAST(YEAR(GETDATE()) AS INTEGER)', $statements[0]); + } + public function testAddingDateTime() { $blueprint = new Blueprint($this->getConnection(), 'users'); From 58ce619a249243772017899c266624bc646f1cff Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 8 May 2025 16:28:50 +0200 Subject: [PATCH 109/158] [12.x] Update "Number::fileSize" to use correct prefix and add prefix param (#55678) * update "Number::fileSize" to use correct prefix and add prefix param * fix style * fix tests * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Number.php | 13 ++++++--- tests/Support/SupportNumberTest.php | 44 +++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index ec5d6342d416..7ebf7916cb32 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -160,14 +160,19 @@ public static function currency(int|float $number, string $in = '', ?string $loc * @param int|float $bytes * @param int $precision * @param int|null $maxPrecision + * @param bool $useBinaryPrefix * @return string */ - public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxPrecision = null) + public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxPrecision = null, bool $useBinaryPrefix = false) { - $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + $base = $useBinaryPrefix ? 1024 : 1000; - for ($i = 0; ($bytes / 1024) > 0.9 && ($i < count($units) - 1); $i++) { - $bytes /= 1024; + $units = $useBinaryPrefix + ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'RiB', 'QiB'] + : ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB']; + + for ($i = 0; ($bytes / $base) > 0.9 && ($i < count($units) - 1); $i++) { + $bytes /= $base; } return sprintf('%s %s', static::format($bytes, $precision, $maxPrecision), $units[$i]); diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index 8f7567433baa..8d7ba346cf99 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -174,18 +174,38 @@ public function testBytesToHuman() $this->assertSame('0 B', Number::fileSize(0)); $this->assertSame('0.00 B', Number::fileSize(0, precision: 2)); $this->assertSame('1 B', Number::fileSize(1)); - $this->assertSame('1 KB', Number::fileSize(1024)); - $this->assertSame('2 KB', Number::fileSize(2048)); - $this->assertSame('2.00 KB', Number::fileSize(2048, precision: 2)); - $this->assertSame('1.23 KB', Number::fileSize(1264, precision: 2)); - $this->assertSame('1.234 KB', Number::fileSize(1264.12345, maxPrecision: 3)); - $this->assertSame('1.234 KB', Number::fileSize(1264, 3)); - $this->assertSame('5 GB', Number::fileSize(1024 * 1024 * 1024 * 5)); - $this->assertSame('10 TB', Number::fileSize((1024 ** 4) * 10)); - $this->assertSame('10 PB', Number::fileSize((1024 ** 5) * 10)); - $this->assertSame('1 ZB', Number::fileSize(1024 ** 7)); - $this->assertSame('1 YB', Number::fileSize(1024 ** 8)); - $this->assertSame('1,024 YB', Number::fileSize(1024 ** 9)); + $this->assertSame('1 KB', Number::fileSize(1000)); + $this->assertSame('2 KB', Number::fileSize(2000)); + $this->assertSame('2.00 KB', Number::fileSize(2000, precision: 2)); + $this->assertSame('1.23 KB', Number::fileSize(1234, precision: 2)); + $this->assertSame('1.234 KB', Number::fileSize(1234, maxPrecision: 3)); + $this->assertSame('1.234 KB', Number::fileSize(1234, 3)); + $this->assertSame('5 GB', Number::fileSize(1000 * 1000 * 1000 * 5)); + $this->assertSame('10 TB', Number::fileSize((1000 ** 4) * 10)); + $this->assertSame('10 PB', Number::fileSize((1000 ** 5) * 10)); + $this->assertSame('1 ZB', Number::fileSize(1000 ** 7)); + $this->assertSame('1 YB', Number::fileSize(1000 ** 8)); + $this->assertSame('1 RB', Number::fileSize(1000 ** 9)); + $this->assertSame('1 QB', Number::fileSize(1000 ** 10)); + $this->assertSame('1,000 QB', Number::fileSize(1000 ** 11)); + + $this->assertSame('0 B', Number::fileSize(0, useBinaryPrefix: true)); + $this->assertSame('0.00 B', Number::fileSize(0, precision: 2, useBinaryPrefix: true)); + $this->assertSame('1 B', Number::fileSize(1, useBinaryPrefix: true)); + $this->assertSame('1 KiB', Number::fileSize(1024, useBinaryPrefix: true)); + $this->assertSame('2 KiB', Number::fileSize(2048, useBinaryPrefix: true)); + $this->assertSame('2.00 KiB', Number::fileSize(2048, precision: 2, useBinaryPrefix: true)); + $this->assertSame('1.23 KiB', Number::fileSize(1264, precision: 2, useBinaryPrefix: true)); + $this->assertSame('1.234 KiB', Number::fileSize(1264.12345, maxPrecision: 3, useBinaryPrefix: true)); + $this->assertSame('1.234 KiB', Number::fileSize(1264, 3, useBinaryPrefix: true)); + $this->assertSame('5 GiB', Number::fileSize(1024 * 1024 * 1024 * 5, useBinaryPrefix: true)); + $this->assertSame('10 TiB', Number::fileSize((1024 ** 4) * 10, useBinaryPrefix: true)); + $this->assertSame('10 PiB', Number::fileSize((1024 ** 5) * 10, useBinaryPrefix: true)); + $this->assertSame('1 ZiB', Number::fileSize(1024 ** 7, useBinaryPrefix: true)); + $this->assertSame('1 YiB', Number::fileSize(1024 ** 8, useBinaryPrefix: true)); + $this->assertSame('1 RiB', Number::fileSize(1024 ** 9, useBinaryPrefix: true)); + $this->assertSame('1 QiB', Number::fileSize(1024 ** 10, useBinaryPrefix: true)); + $this->assertSame('1,024 QiB', Number::fileSize(1024 ** 11, useBinaryPrefix: true)); } public function testClamp() From 81cd4d6e52eb21637ae87d0398296da7c90756dd Mon Sep 17 00:00:00 2001 From: mitoop Date: Thu, 8 May 2025 22:30:32 +0800 Subject: [PATCH 110/158] Update PHPDoc for whereRaw to allow Expression as $sql (#55674) Signed-off-by: mitoop --- src/Illuminate/Database/Query/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index ed13026725ce..d2b97d5d121e 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -1101,7 +1101,7 @@ public function orWhereColumn($first, $operator = null, $second = null) /** * Add a raw where clause to the query. * - * @param string $sql + * @param \Illuminate\Contracts\Database\Query\Expression|string $sql * @param mixed $bindings * @param string $boolean * @return $this From 44cbd4bc6ee109365ea5f32dd1976cf01d4d7c62 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 9 May 2025 07:37:38 -0700 Subject: [PATCH 111/158] Revert "Make Blueprint Resolver Statically (#55607)" (#55690) This reverts commit ea9c4813bd278d9e172d46f73d295dfb9cd7d372. --- src/Illuminate/Database/Schema/Builder.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index d70cb9314231..109932a27d12 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -9,9 +9,6 @@ use InvalidArgumentException; use LogicException; -/** - * @template TResolver of \Closure(string, \Closure, string): \Illuminate\Database\Schema\Blueprint - */ class Builder { use Macroable; @@ -33,9 +30,9 @@ class Builder /** * The Blueprint resolver callback. * - * @var TResolver|null + * @var \Closure */ - protected static $resolver = null; + protected $resolver; /** * The default string length for migrations. @@ -632,8 +629,8 @@ protected function createBlueprint($table, ?Closure $callback = null) { $connection = $this->connection; - if (static::$resolver !== null) { - return call_user_func(static::$resolver, $connection, $table, $callback); + if (isset($this->resolver)) { + return call_user_func($this->resolver, $connection, $table, $callback); } return Container::getInstance()->make(Blueprint::class, compact('connection', 'table', 'callback')); @@ -701,11 +698,11 @@ public function getConnection() /** * Set the Schema Blueprint resolver callback. * - * @param TResolver|null $resolver + * @param \Closure $resolver * @return void */ - public function blueprintResolver(?Closure $resolver) + public function blueprintResolver(Closure $resolver) { - static::$resolver = $resolver; + $this->resolver = $resolver; } } From f0100678f9d9d8cf1a3dd76de5510d08854232c0 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 9 May 2025 14:38:32 +0000 Subject: [PATCH 112/158] Update facade docblocks --- src/Illuminate/Support/Facades/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index d0e3e5f84bf1..09d0844c8610 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -44,7 +44,7 @@ * @method static string|null getCurrentSchemaName() * @method static array parseSchemaAndTable(string $reference, string|bool|null $withDefaultSchema = null) * @method static \Illuminate\Database\Connection getConnection() - * @method static void blueprintResolver(\Closure|null $resolver) + * @method static void blueprintResolver(\Closure $resolver) * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) From 98043a85ffe3a5e391c83144d177f092aa9cd5d3 Mon Sep 17 00:00:00 2001 From: Kevin Richter <1887585+beschoenen@users.noreply.github.com> Date: Fri, 9 May 2025 16:42:34 +0200 Subject: [PATCH 113/158] feat: support php 8.4 virtual properties when serializing models (#55691) --- src/Illuminate/Queue/SerializesModels.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Queue/SerializesModels.php b/src/Illuminate/Queue/SerializesModels.php index db17b98755af..388c1d745561 100644 --- a/src/Illuminate/Queue/SerializesModels.php +++ b/src/Illuminate/Queue/SerializesModels.php @@ -36,6 +36,10 @@ public function __serialize() continue; } + if (method_exists($property, 'isVirtual') && $property->isVirtual()) { + continue; + } + $value = $this->getPropertyValue($property); if ($property->hasDefaultValue() && $value === $property->getDefaultValue()) { From 76cee6bc9274328b3bb766c62bbf408d8f083f95 Mon Sep 17 00:00:00 2001 From: Martin Saldinger <51637671+LeTamanoir@users.noreply.github.com> Date: Fri, 9 May 2025 16:43:40 +0200 Subject: [PATCH 114/158] [12.X] Fix `Http::preventStrayRequests` error propagation when using `Http::pool` (#55689) * add a dedicated error for stray request exception * re-throw the error if it happens in a pool context * update tests * fix test * fix * fix --- src/Illuminate/Http/Client/PendingRequest.php | 9 ++++-- .../Http/Client/StrayRequestException.php | 13 +++++++++ tests/Http/HttpClientTest.php | 29 ++++++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 src/Illuminate/Http/Client/StrayRequestException.php diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 117319de5029..7abcb3a459b8 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -26,7 +26,6 @@ use OutOfBoundsException; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; -use RuntimeException; use Symfony\Component\VarDumper\VarDumper; class PendingRequest @@ -1033,7 +1032,11 @@ protected function makePromise(string $method, string $url, array $options = [], $this->dispatchResponseReceivedEvent($response); }); }) - ->otherwise(function (OutOfBoundsException|TransferException $e) { + ->otherwise(function (OutOfBoundsException|TransferException|StrayRequestException $e) { + if ($e instanceof StrayRequestException) { + throw $e; + } + if ($e instanceof ConnectException || ($e instanceof RequestException && ! $e->hasResponse())) { $exception = new ConnectionException($e->getMessage(), 0, $e); @@ -1334,7 +1337,7 @@ public function buildStubHandler() if (is_null($response)) { if ($this->preventStrayRequests) { - throw new RuntimeException('Attempted request to ['.(string) $request->getUri().'] without a matching fake.'); + throw new StrayRequestException((string) $request->getUri()); } return $handler($request, $options); diff --git a/src/Illuminate/Http/Client/StrayRequestException.php b/src/Illuminate/Http/Client/StrayRequestException.php new file mode 100644 index 000000000000..1392233bbadc --- /dev/null +++ b/src/Illuminate/Http/Client/StrayRequestException.php @@ -0,0 +1,13 @@ +factory->get('https://forge.laravel.com')->body(); $this->assertSame(['ok', 'ok'], $responses); - $this->expectException(RuntimeException::class); + $this->expectException(StrayRequestException::class); $this->expectExceptionMessage('Attempted request to [https://laravel.com] without a matching fake.'); $this->factory->get('https://laravel.com'); } + public function testItCanEnforceFakingInThePool() + { + $this->factory->preventStrayRequests(); + $this->factory->fake(['https://vapor.laravel.com' => Factory::response('ok', 200)]); + $this->factory->fake(['https://forge.laravel.com' => Factory::response('ok', 200)]); + + $responses = $this->factory->pool(function (Pool $pool) { + return [ + $pool->get('https://vapor.laravel.com'), + $pool->get('https://forge.laravel.com'), + ]; + }); + + $this->assertSame(200, $responses[0]->status()); + $this->assertSame(200, $responses[1]->status()); + + $this->expectException(StrayRequestException::class); + $this->expectExceptionMessage('Attempted request to [https://laravel.com] without a matching fake.'); + + $this->factory->pool(function (Pool $pool) { + return [ + $pool->get('https://laravel.com'), + ]; + }); + } + public function testPreventingStrayRequests() { $this->assertFalse($this->factory->preventingStrayRequests()); From 678f8b6d6f43d2e0568532f04962078cbf94e71c Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Fri, 9 May 2025 23:48:27 +0900 Subject: [PATCH 115/158] fix: incorrect use of generics in Schema\Builder (#55687) Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Schema/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 109932a27d12..c22019536e7c 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -30,7 +30,7 @@ class Builder /** * The Blueprint resolver callback. * - * @var \Closure + * @var \Closure(string, \Closure, string): \Illuminate\Database\Schema\Blueprint|null */ protected $resolver; @@ -698,7 +698,7 @@ public function getConnection() /** * Set the Schema Blueprint resolver callback. * - * @param \Closure $resolver + * @param \Closure(string, \Closure, string): \Illuminate\Database\Schema\Blueprint|null $resolver * @return void */ public function blueprintResolver(Closure $resolver) From c089e30e406d4fc57a1af0fac1fefe7043e65ac9 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 9 May 2025 14:49:00 +0000 Subject: [PATCH 116/158] Update facade docblocks --- src/Illuminate/Support/Facades/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 09d0844c8610..d0e3e5f84bf1 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -44,7 +44,7 @@ * @method static string|null getCurrentSchemaName() * @method static array parseSchemaAndTable(string $reference, string|bool|null $withDefaultSchema = null) * @method static \Illuminate\Database\Connection getConnection() - * @method static void blueprintResolver(\Closure $resolver) + * @method static void blueprintResolver(\Closure|null $resolver) * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) From e0b397b7198b93e2feac52277ce1af15681ba3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anderson=20Luiz=20Silv=C3=A9rio?= Date: Fri, 9 May 2025 11:54:17 -0300 Subject: [PATCH 117/158] [12.x] Add option to disable MySQL ssl when restoring or squashing migrations (#55683) * Add option to disable mysql ssl when loading or squashing migrations * Update MySqlSchemaState.php --------- Co-authored-by: Taylor Otwell --- .../Database/Schema/MySqlSchemaState.php | 5 +++++ .../Database/DatabaseMySqlSchemaStateTest.php | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index 30729f1ef2e8..1ea480448e17 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -115,6 +115,11 @@ protected function connectionString() $value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"'; } + if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT]) && + $config['options'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] === false) { + $value .= ' --ssl=off'; + } + return $value; } diff --git a/tests/Database/DatabaseMySqlSchemaStateTest.php b/tests/Database/DatabaseMySqlSchemaStateTest.php index 3a9c7db3896c..18985114e509 100644 --- a/tests/Database/DatabaseMySqlSchemaStateTest.php +++ b/tests/Database/DatabaseMySqlSchemaStateTest.php @@ -70,6 +70,24 @@ public static function provider(): Generator ], ]; + yield 'no_ssl' => [ + ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --ssl=off', [ + 'LARAVEL_LOAD_SOCKET' => '', + 'LARAVEL_LOAD_HOST' => '', + 'LARAVEL_LOAD_PORT' => '', + 'LARAVEL_LOAD_USER' => 'root', + 'LARAVEL_LOAD_PASSWORD' => '', + 'LARAVEL_LOAD_DATABASE' => 'forge', + 'LARAVEL_LOAD_SSL_CA' => '', + ], [ + 'username' => 'root', + 'database' => 'forge', + 'options' => [ + \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false, + ], + ], + ]; + yield 'unix socket' => [ ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}" --socket="${:LARAVEL_LOAD_SOCKET}"', [ 'LARAVEL_LOAD_SOCKET' => '/tmp/mysql.sock', From f033c4a412b1d91bdbedb69adba1b75af7157172 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 9 May 2025 14:54:41 +0000 Subject: [PATCH 118/158] Apply fixes from StyleCI --- src/Illuminate/Database/Schema/MySqlSchemaState.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index 1ea480448e17..1635de7742e5 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -115,7 +115,7 @@ protected function connectionString() $value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"'; } - if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT]) && + if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT]) && $config['options'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] === false) { $value .= ' --ssl=off'; } From 6faf8de908121dab113c469d9e715532299914a5 Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Fri, 9 May 2025 19:02:40 +0400 Subject: [PATCH 119/158] Add except and exceptHidden methods to Context class (#55692) Co-authored-by: Xurshudyan --- src/Illuminate/Log/Context/Repository.php | 22 ++++++++++++++++++ tests/Log/ContextTest.php | 28 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/Illuminate/Log/Context/Repository.php b/src/Illuminate/Log/Context/Repository.php index 01e861925020..5fcfc2710e25 100644 --- a/src/Illuminate/Log/Context/Repository.php +++ b/src/Illuminate/Log/Context/Repository.php @@ -193,6 +193,28 @@ public function onlyHidden($keys) return array_intersect_key($this->hidden, array_flip($keys)); } + /** + * Retrieve all values except those with the given keys. + * + * @param array $keys + * @return array + */ + public function except($keys) + { + return array_diff_key($this->data, array_flip($keys)); + } + + /** + * Retrieve all hidden values except those with the given keys. + * + * @param array $keys + * @return array + */ + public function exceptHidden($keys) + { + return array_diff_key($this->hidden, array_flip($keys)); + } + /** * Add a context value. * diff --git a/tests/Log/ContextTest.php b/tests/Log/ContextTest.php index dfb76df9194b..ef9d61f0bccc 100644 --- a/tests/Log/ContextTest.php +++ b/tests/Log/ContextTest.php @@ -364,6 +364,34 @@ public function test_it_can_retrieve_subset_of_context() ])); } + public function test_it_can_exclude_subset_of_context() + { + Context::add('parent.child.1', 5); + Context::add('parent.child.2', 6); + Context::add('another', 7); + + $this->assertSame([ + 'another' => 7, + ], Context::except([ + 'parent.child.1', + 'parent.child.2', + ])); + } + + public function test_it_can_exclude_subset_of_hidden_context() + { + Context::addHidden('parent.child.1', 5); + Context::addHidden('parent.child.2', 6); + Context::addHidden('another', 7); + + $this->assertSame([ + 'another' => 7, + ], Context::exceptHidden([ + 'parent.child.1', + 'parent.child.2', + ])); + } + public function test_it_adds_context_to_logging() { $path = storage_path('logs/laravel.log'); From b0f622757ed1649d05e3a0aa2168855d0c2aae6b Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 9 May 2025 15:03:13 +0000 Subject: [PATCH 120/158] Update facade docblocks --- src/Illuminate/Support/Facades/Context.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Support/Facades/Context.php b/src/Illuminate/Support/Facades/Context.php index 6b894fad39b7..aaac04d8ed00 100644 --- a/src/Illuminate/Support/Facades/Context.php +++ b/src/Illuminate/Support/Facades/Context.php @@ -15,6 +15,8 @@ * @method static mixed pullHidden(string $key, mixed $default = null) * @method static array only(array $keys) * @method static array onlyHidden(array $keys) + * @method static array except(array $keys) + * @method static array exceptHidden(array $keys) * @method static \Illuminate\Log\Context\Repository add(string|array $key, mixed $value = null) * @method static \Illuminate\Log\Context\Repository addHidden(string|array $key, mixed $value = null) * @method static \Illuminate\Log\Context\Repository forget(string|array $key) From 7771d14fa7201770edc92c810373accc9f535e53 Mon Sep 17 00:00:00 2001 From: Justin Seliga Date: Fri, 9 May 2025 11:05:41 -0400 Subject: [PATCH 121/158] [12.x] Container `currentlyResolving` utility (#55684) * container `currentlyResolving` utility * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Container/Container.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 32ecaeabe998..5c401d465e7f 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -1496,6 +1496,16 @@ protected function fireCallbackArray($object, array $callbacks) } } + /** + * Get the name of the binding the container is currently resolving. + * + * @return class-string|string|null + */ + public function currentlyResolving() + { + return end($this->buildStack) ?: null; + } + /** * Get the container's bindings. * From 6c184ac871164fed417db382d4c1627c8ca5f32e Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Fri, 9 May 2025 15:06:15 +0000 Subject: [PATCH 122/158] Update facade docblocks --- src/Illuminate/Support/Facades/App.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/App.php b/src/Illuminate/Support/Facades/App.php index ed8a9d6dce10..9dd93084dad4 100755 --- a/src/Illuminate/Support/Facades/App.php +++ b/src/Illuminate/Support/Facades/App.php @@ -130,6 +130,7 @@ * @method static void afterResolving(\Closure|string $abstract, \Closure|null $callback = null) * @method static void afterResolvingAttribute(string $attribute, \Closure $callback) * @method static void fireAfterResolvingAttributeCallbacks(\ReflectionAttribute[] $attributes, mixed $object) + * @method static string|null currentlyResolving() * @method static array getBindings() * @method static string getAlias(string $abstract) * @method static void forgetExtenders(string $abstract) From b5d81f5cdfd45bfde105570434675ef72c5feadb Mon Sep 17 00:00:00 2001 From: Justin Seliga Date: Fri, 9 May 2025 13:02:34 -0400 Subject: [PATCH 123/158] container `currentlyResolving` test (#55694) --- tests/Container/ContainerTest.php | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 5522b8af9b2f..2c15f3682785 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -2,9 +2,11 @@ namespace Illuminate\Tests\Container; +use Attribute; use Illuminate\Container\Container; use Illuminate\Container\EntryNotFoundException; use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Contracts\Container\ContextualAttribute; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerExceptionInterface; use stdClass; @@ -522,6 +524,23 @@ public function testGetAlias() $this->assertSame('ConcreteStub', $container->getAlias('foo')); } + public function testCurrentlyResolving() + { + $container = new Container; + + $container->afterResolvingAttribute(ContainerCurrentResolvingAttribute::class, function ($attr, $instance, $container) { + $this->assertEquals(ContainerCurrentResolvingConcrete::class, $container->currentlyResolving()); + }); + + $container->when(ContainerCurrentResolvingConcrete::class) + ->needs('$currentlyResolving') + ->give(fn ($container) => $container->currentlyResolving()); + + $resolved = $container->make(ContainerCurrentResolvingConcrete::class); + + $this->assertEquals(ContainerCurrentResolvingConcrete::class, $resolved->currentlyResolving); +} + public function testGetAliasRecursive() { $container = new Container; @@ -860,3 +879,23 @@ public function work(IContainerContractStub $stub) return $stub; } } + +#[Attribute(Attribute::TARGET_PARAMETER)] +class ContainerCurrentResolvingAttribute implements ContextualAttribute +{ + public function resolve() + { + } +} + +class ContainerCurrentResolvingConcrete +{ + public $currentlyResolving; + + public function __construct( + #[ContainerCurrentResolvingAttribute] + string $currentlyResolving + ) { + $this->currentlyResolving = $currentlyResolving; + } +} From 96b554c5a6ab3fb6c7dc57fd5b47870a005a6fac Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 9 May 2025 17:02:51 +0000 Subject: [PATCH 124/158] Apply fixes from StyleCI --- tests/Container/ContainerTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 2c15f3682785..0302729bc970 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -526,20 +526,20 @@ public function testGetAlias() public function testCurrentlyResolving() { - $container = new Container; + $container = new Container; - $container->afterResolvingAttribute(ContainerCurrentResolvingAttribute::class, function ($attr, $instance, $container) { - $this->assertEquals(ContainerCurrentResolvingConcrete::class, $container->currentlyResolving()); - }); + $container->afterResolvingAttribute(ContainerCurrentResolvingAttribute::class, function ($attr, $instance, $container) { + $this->assertEquals(ContainerCurrentResolvingConcrete::class, $container->currentlyResolving()); + }); - $container->when(ContainerCurrentResolvingConcrete::class) - ->needs('$currentlyResolving') - ->give(fn ($container) => $container->currentlyResolving()); + $container->when(ContainerCurrentResolvingConcrete::class) + ->needs('$currentlyResolving') + ->give(fn ($container) => $container->currentlyResolving()); - $resolved = $container->make(ContainerCurrentResolvingConcrete::class); + $resolved = $container->make(ContainerCurrentResolvingConcrete::class); - $this->assertEquals(ContainerCurrentResolvingConcrete::class, $resolved->currentlyResolving); -} + $this->assertEquals(ContainerCurrentResolvingConcrete::class, $resolved->currentlyResolving); + } public function testGetAliasRecursive() { From aa6538e56200c99f42b94301b2a8e49813a61416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 May 2025 19:56:46 +0200 Subject: [PATCH 125/158] Fix handling of default values for route parameters with a binding field, where a default is not present for that same parameter without a binding field (#55697) --- src/Illuminate/Routing/RouteUrlGenerator.php | 11 ++++++++--- tests/Routing/RoutingUrlGeneratorTest.php | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Routing/RouteUrlGenerator.php b/src/Illuminate/Routing/RouteUrlGenerator.php index cd23162c015f..52fcec1e4237 100644 --- a/src/Illuminate/Routing/RouteUrlGenerator.php +++ b/src/Illuminate/Routing/RouteUrlGenerator.php @@ -197,9 +197,14 @@ protected function formatParameters(Route $route, $parameters) unset($parameters[$name]); continue; - } elseif (! isset($this->defaultParameters[$name]) && ! isset($optionalParameters[$name])) { - // No named parameter or default value for a required parameter, try to match to positional parameter below... - array_push($requiredRouteParametersWithoutDefaultsOrNamedParameters, $name); + } else { + $bindingField = $route->bindingFieldFor($name); + $defaultParameterKey = $bindingField ? "$name:$bindingField" : $name; + + if (! isset($this->defaultParameters[$defaultParameterKey]) && ! isset($optionalParameters[$name])) { + // No named parameter or default value for a required parameter, try to match to positional parameter below... + array_push($requiredRouteParametersWithoutDefaultsOrNamedParameters, $name); + } } $namedParameters[$name] = ''; diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index f03e4b10bb8c..54eab01a1cc8 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -1066,6 +1066,24 @@ public function testComplexRouteGenerationWithDefaultsAndBindingFields() $url->route('tenantSlugPost', ['post' => $keyParam('concretePost')]), ); + // Repeat the two assertions above without the 'tenant' default (without slug) + $url->defaults(['tenant' => null]); + + // tenantSlugPost: Tenant (with default) omitted, post passed positionally, with the default value for 'tenant' (without slug) removed + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/defaultTenantSlug/concretePost', + $url->route('tenantSlugPost', [$keyParam('concretePost')]), + ); + + // tenantSlugPost: Tenant (with default) omitted, post passed using key, with the default value for 'tenant' (without slug) removed + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/defaultTenantSlug/concretePost', + $url->route('tenantSlugPost', ['post' => $keyParam('concretePost')]), + ); + + // Revert the default value for the tenant parameter back + $url->defaults(['tenant' => 'defaultTenant']); + /** * One parameter with a default value, one without a default value. * From df70ad9801f1311adfb1669f3047b23c25bdf16e Mon Sep 17 00:00:00 2001 From: Stephen Rees-Carter Date: Sun, 11 May 2025 04:00:05 +1000 Subject: [PATCH 126/158] Move Timebox for Authentication and add to password resets (#55701) * Shift Timebox up a level to encapsulate the query too * Add Timebox Duration into config * Add Timebox to Password Broker * Add early return inside reset Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> * formatting --------- Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> Co-authored-by: Taylor Otwell --- src/Illuminate/Auth/AuthManager.php | 1 + .../Auth/Passwords/PasswordBroker.php | 112 ++++++++++++------ .../Auth/Passwords/PasswordBrokerManager.php | 1 + src/Illuminate/Auth/SessionGuard.php | 98 +++++++++------ 4 files changed, 138 insertions(+), 74 deletions(-) diff --git a/src/Illuminate/Auth/AuthManager.php b/src/Illuminate/Auth/AuthManager.php index 70723558886e..131959148b06 100755 --- a/src/Illuminate/Auth/AuthManager.php +++ b/src/Illuminate/Auth/AuthManager.php @@ -126,6 +126,7 @@ public function createSessionDriver($name, $config) $this->createUserProvider($config['provider'] ?? null), $this->app['session.store'], rehashOnLogin: $this->app['config']->get('hashing.rehash_on_login', true), + timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000), ); // When using the remember me functionality of the authentication services we diff --git a/src/Illuminate/Auth/Passwords/PasswordBroker.php b/src/Illuminate/Auth/Passwords/PasswordBroker.php index 89565eb77b3a..d955f3e42e1d 100755 --- a/src/Illuminate/Auth/Passwords/PasswordBroker.php +++ b/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\Arr; +use Illuminate\Support\Timebox; use UnexpectedValueException; class PasswordBroker implements PasswordBrokerContract @@ -34,18 +35,41 @@ class PasswordBroker implements PasswordBrokerContract */ protected $events; + /** + * The timebox instance. + * + * @var \Illuminate\Support\Timebox + */ + protected $timebox; + + /** + * The number of microseconds that the timebox should wait for. + * + * @var int + */ + protected $timeboxDuration; + /** * Create a new password broker instance. * * @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens * @param \Illuminate\Contracts\Auth\UserProvider $users * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher + * @param \Illuminate\Support\Timebox|null $timebox + * @param int $timeboxDuration */ - public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tokens, UserProvider $users, ?Dispatcher $dispatcher = null) - { + public function __construct( + #[\SensitiveParameter] TokenRepositoryInterface $tokens, + UserProvider $users, + ?Dispatcher $dispatcher = null, + ?Timebox $timebox = null, + int $timeboxDuration = 200000, + ) { $this->users = $users; $this->tokens = $tokens; $this->events = $dispatcher; + $this->timebox = $timebox ?: new Timebox; + $this->timeboxDuration = $timeboxDuration; } /** @@ -57,33 +81,35 @@ public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tok */ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closure $callback = null) { - // First we will check to see if we found a user at the given credentials and - // if we did not we will redirect back to this current URI with a piece of - // "flash" data in the session to indicate to the developers the errors. - $user = $this->getUser($credentials); + return $this->timebox->call(function () use ($credentials, $callback) { + // First we will check to see if we found a user at the given credentials and + // if we did not we will redirect back to this current URI with a piece of + // "flash" data in the session to indicate to the developers the errors. + $user = $this->getUser($credentials); - if (is_null($user)) { - return static::INVALID_USER; - } + if (is_null($user)) { + return static::INVALID_USER; + } - if ($this->tokens->recentlyCreatedToken($user)) { - return static::RESET_THROTTLED; - } + if ($this->tokens->recentlyCreatedToken($user)) { + return static::RESET_THROTTLED; + } - $token = $this->tokens->create($user); + $token = $this->tokens->create($user); - if ($callback) { - return $callback($user, $token) ?? static::RESET_LINK_SENT; - } + if ($callback) { + return $callback($user, $token) ?? static::RESET_LINK_SENT; + } - // Once we have the reset token, we are ready to send the message out to this - // user with a link to reset their password. We will then redirect back to - // the current URI having nothing set in the session to indicate errors. - $user->sendPasswordResetNotification($token); + // Once we have the reset token, we are ready to send the message out to this + // user with a link to reset their password. We will then redirect back to + // the current URI having nothing set in the session to indicate errors. + $user->sendPasswordResetNotification($token); - $this->events?->dispatch(new PasswordResetLinkSent($user)); + $this->events?->dispatch(new PasswordResetLinkSent($user)); - return static::RESET_LINK_SENT; + return static::RESET_LINK_SENT; + }, $this->timeboxDuration); } /** @@ -95,25 +121,29 @@ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closur */ public function reset(#[\SensitiveParameter] array $credentials, Closure $callback) { - $user = $this->validateReset($credentials); + return $this->timebox->call(function ($timebox) use ($credentials, $callback) { + $user = $this->validateReset($credentials); - // If the responses from the validate method is not a user instance, we will - // assume that it is a redirect and simply return it from this method and - // the user is properly redirected having an error message on the post. - if (! $user instanceof CanResetPasswordContract) { - return $user; - } + // If the responses from the validate method is not a user instance, we will + // assume that it is a redirect and simply return it from this method and + // the user is properly redirected having an error message on the post. + if (! $user instanceof CanResetPasswordContract) { + return $user; + } - $password = $credentials['password']; + $password = $credentials['password']; - // Once the reset has been validated, we'll call the given callback with the - // new password. This gives the user an opportunity to store the password - // in their persistent storage. Then we'll delete the token and return. - $callback($user, $password); + // Once the reset has been validated, we'll call the given callback with the + // new password. This gives the user an opportunity to store the password + // in their persistent storage. Then we'll delete the token and return. + $callback($user, $password); - $this->tokens->delete($user); + $this->tokens->delete($user); + + $timebox->returnEarly(); - return static::PASSWORD_RESET; + return static::PASSWORD_RESET; + }, $this->timeboxDuration); } /** @@ -199,4 +229,14 @@ public function getRepository() { return $this->tokens; } + + /** + * Get the timebox instance used by the guard. + * + * @return \Illuminate\Support\Timebox + */ + public function getTimebox() + { + return $this->timebox; + } } diff --git a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php index 516638b17f5f..3946e596ef9f 100644 --- a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php @@ -70,6 +70,7 @@ protected function resolve($name) $this->createTokenRepository($config), $this->app['auth']->createUserProvider($config['provider'] ?? null), $this->app['events'] ?? null, + timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000), ); } diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 13bd15f46c5a..985b0bb4407c 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -96,6 +96,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth */ protected $timebox; + /** + * The number of microseconds that the timebox should wait for. + * + * @var int + */ + protected $timeboxDuration; + /** * Indicates if passwords should be rehashed on login if needed. * @@ -126,6 +133,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth * @param \Symfony\Component\HttpFoundation\Request|null $request * @param \Illuminate\Support\Timebox|null $timebox * @param bool $rehashOnLogin + * @param int $timeboxDuration */ public function __construct( $name, @@ -134,6 +142,7 @@ public function __construct( ?Request $request = null, ?Timebox $timebox = null, bool $rehashOnLogin = true, + int $timeboxDuration = 200000, ) { $this->name = $name; $this->session = $session; @@ -141,6 +150,7 @@ public function __construct( $this->provider = $provider; $this->timebox = $timebox ?: new Timebox; $this->rehashOnLogin = $rehashOnLogin; + $this->timeboxDuration = $timeboxDuration; } /** @@ -290,9 +300,17 @@ public function onceUsingId($id) */ public function validate(array $credentials = []) { - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + return $this->timebox->call(function ($timebox) use ($credentials) { + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - return $this->hasValidCredentials($user, $credentials); + $validated = $this->hasValidCredentials($user, $credentials); + + if ($validated) { + $timebox->returnEarly(); + } + + return $validated; + }, $this->timeboxDuration); } /** @@ -390,27 +408,31 @@ protected function failedBasicResponse() */ public function attempt(array $credentials = [], $remember = false) { - $this->fireAttemptEvent($credentials, $remember); + return $this->timebox->call(function ($timebox) use ($credentials, $remember) { + $this->fireAttemptEvent($credentials, $remember); - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - // If an implementation of UserInterface was returned, we'll ask the provider - // to validate the user against the given credentials, and if they are in - // fact valid we'll log the users into the application and return true. - if ($this->hasValidCredentials($user, $credentials)) { - $this->rehashPasswordIfRequired($user, $credentials); + // If an implementation of UserInterface was returned, we'll ask the provider + // to validate the user against the given credentials, and if they are in + // fact valid we'll log the users into the application and return true. + if ($this->hasValidCredentials($user, $credentials)) { + $this->rehashPasswordIfRequired($user, $credentials); - $this->login($user, $remember); + $this->login($user, $remember); - return true; - } + $timebox->returnEarly(); - // If the authentication attempt fails we will fire an event so that the user - // may be notified of any suspicious attempts to access their account from - // an unrecognized user. A developer may listen to this event as needed. - $this->fireFailedEvent($user, $credentials); + return true; + } - return false; + // If the authentication attempt fails we will fire an event so that the user + // may be notified of any suspicious attempts to access their account from + // an unrecognized user. A developer may listen to this event as needed. + $this->fireFailedEvent($user, $credentials); + + return false; + }, $this->timeboxDuration); } /** @@ -423,24 +445,28 @@ public function attempt(array $credentials = [], $remember = false) */ public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false) { - $this->fireAttemptEvent($credentials, $remember); + return $this->timebox->call(function ($timebox) use ($credentials, $callbacks, $remember) { + $this->fireAttemptEvent($credentials, $remember); - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - // This method does the exact same thing as attempt, but also executes callbacks after - // the user is retrieved and validated. If one of the callbacks returns falsy we do - // not login the user. Instead, we will fail the specific authentication attempt. - if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) { - $this->rehashPasswordIfRequired($user, $credentials); + // This method does the exact same thing as attempt, but also executes callbacks after + // the user is retrieved and validated. If one of the callbacks returns falsy we do + // not login the user. Instead, we will fail the specific authentication attempt. + if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) { + $this->rehashPasswordIfRequired($user, $credentials); - $this->login($user, $remember); + $this->login($user, $remember); - return true; - } + $timebox->returnEarly(); - $this->fireFailedEvent($user, $credentials); + return true; + } - return false; + $this->fireFailedEvent($user, $credentials); + + return false; + }, $this->timeboxDuration); } /** @@ -452,17 +478,13 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe */ protected function hasValidCredentials($user, $credentials) { - return $this->timebox->call(function ($timebox) use ($user, $credentials) { - $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); - - if ($validated) { - $timebox->returnEarly(); + $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); - $this->fireValidatedEvent($user); - } + if ($validated) { + $this->fireValidatedEvent($user); + } - return $validated; - }, 200 * 1000); + return $validated; } /** From f201b8e4d1d0233cae8df79394a9da0d185bf732 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Sat, 10 May 2025 18:00:44 +0000 Subject: [PATCH 127/158] Update facade docblocks --- src/Illuminate/Support/Facades/Password.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Password.php b/src/Illuminate/Support/Facades/Password.php index 7099e3971fd8..ac6f226aa251 100755 --- a/src/Illuminate/Support/Facades/Password.php +++ b/src/Illuminate/Support/Facades/Password.php @@ -15,6 +15,7 @@ * @method static void deleteToken(\Illuminate\Contracts\Auth\CanResetPassword $user) * @method static bool tokenExists(\Illuminate\Contracts\Auth\CanResetPassword $user, string $token) * @method static \Illuminate\Auth\Passwords\TokenRepositoryInterface getRepository() + * @method static \Illuminate\Support\Timebox getTimebox() * * @see \Illuminate\Auth\Passwords\PasswordBrokerManager * @see \Illuminate\Auth\Passwords\PasswordBroker From ea17178c626d3e7d58a40db79d236a7060437d87 Mon Sep 17 00:00:00 2001 From: Razvan Aurariu <38325118+rzv-me@users.noreply.github.com> Date: Sat, 10 May 2025 21:09:12 +0300 Subject: [PATCH 128/158] [12.x] perf: Optimize BladeCompiler (#55703) * Speed up `getOpenAndClosingPhpTokens()` up to 30x * add empty line before return statement * Update BladeCompiler.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/View/Compilers/BladeCompiler.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php index 883eb16c4582..0f8b6722fa6c 100644 --- a/src/Illuminate/View/Compilers/BladeCompiler.php +++ b/src/Illuminate/View/Compilers/BladeCompiler.php @@ -232,11 +232,15 @@ protected function appendFilePath($contents) */ protected function getOpenAndClosingPhpTokens($contents) { - return (new Collection(token_get_all($contents))) - ->pluck(0) - ->filter(function ($token) { - return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]); - }); + $tokens = []; + + foreach (token_get_all($contents) as $token) { + if ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO || $token[0] === T_CLOSE_TAG) { + $tokens[] = $token[0]; + } + } + + return new Collection($tokens); } /** From bcfa7c32acc17a7e49892efc5040fcaae3f207da Mon Sep 17 00:00:00 2001 From: Caleb White Date: Sat, 10 May 2025 14:11:58 -0500 Subject: [PATCH 129/158] feat: support iterables for event discovery paths (#55699) --- .../Configuration/ApplicationBuilder.php | 6 ++-- .../Foundation/Events/DiscoverEvents.php | 15 ++++++--- .../Providers/EventServiceProvider.php | 33 +++++++++---------- .../Foundation/DiscoverEventsTest.php | 27 +++++++++++++++ 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index f8506bc07dd4..e0ffdd534495 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -92,12 +92,12 @@ public function withProviders(array $providers = [], bool $withBootstrapProvider /** * Register the core event service provider for the application. * - * @param array|bool $discover + * @param iterable|bool $discover * @return $this */ - public function withEvents(array|bool $discover = []) + public function withEvents(iterable|bool $discover = true) { - if (is_array($discover) && count($discover) > 0) { + if (is_iterable($discover)) { AppEventServiceProvider::setEventDiscoveryPaths($discover); } diff --git a/src/Illuminate/Foundation/Events/DiscoverEvents.php b/src/Illuminate/Foundation/Events/DiscoverEvents.php index e2933f937872..35f244837bce 100644 --- a/src/Illuminate/Foundation/Events/DiscoverEvents.php +++ b/src/Illuminate/Foundation/Events/DiscoverEvents.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Events; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Reflector; use Illuminate\Support\Str; @@ -16,19 +17,23 @@ class DiscoverEvents /** * The callback to be used to guess class names. * - * @var callable(SplFileInfo, string): string|null + * @var (callable(SplFileInfo, string): class-string)|null */ public static $guessClassNamesUsingCallback; /** * Get all of the events and listeners by searching the given listener directory. * - * @param string $listenerPath + * @param array|string $listenerPath * @param string $basePath * @return array */ public static function within($listenerPath, $basePath) { + if (Arr::wrap($listenerPath) === []) { + return []; + } + $listeners = new Collection(static::getListenerEvents( Finder::create()->files()->in($listenerPath), $basePath )); @@ -51,7 +56,7 @@ public static function within($listenerPath, $basePath) /** * Get all of the listeners and their corresponding events. * - * @param iterable $listeners + * @param iterable $listeners * @param string $basePath * @return array */ @@ -91,7 +96,7 @@ protected static function getListenerEvents($listeners, $basePath) * * @param \SplFileInfo $file * @param string $basePath - * @return string + * @return class-string */ protected static function classFromFile(SplFileInfo $file, $basePath) { @@ -111,7 +116,7 @@ protected static function classFromFile(SplFileInfo $file, $basePath) /** * Specify a callback to be used to guess class names. * - * @param callable(SplFileInfo, string): string $callback + * @param callable(SplFileInfo, string): class-string $callback * @return void */ public static function guessClassNamesUsing(callable $callback) diff --git a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php index d5074505302d..ac7b3ec7838b 100644 --- a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php @@ -6,8 +6,8 @@ use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Events\DiscoverEvents; use Illuminate\Support\Arr; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Event; +use Illuminate\Support\LazyCollection; use Illuminate\Support\ServiceProvider; class EventServiceProvider extends ServiceProvider @@ -43,7 +43,7 @@ class EventServiceProvider extends ServiceProvider /** * The configured event discovery paths. * - * @var array|null + * @var iterable|null */ protected static $eventDiscoveryPaths; @@ -145,25 +145,23 @@ public function shouldDiscoverEvents() */ public function discoverEvents() { - return (new Collection($this->discoverEventsWithin())) + return (new LazyCollection($this->discoverEventsWithin())) ->flatMap(function ($directory) { return glob($directory, GLOB_ONLYDIR); }) ->reject(function ($directory) { return ! is_dir($directory); }) - ->reduce(function ($discovered, $directory) { - return array_merge_recursive( - $discovered, - DiscoverEvents::within($directory, $this->eventDiscoveryBasePath()) - ); - }, []); + ->pipe(fn ($directories) => DiscoverEvents::within( + $directories->all(), + $this->eventDiscoveryBasePath(), + )); } /** * Get the listener directories that should be used to discover events. * - * @return array + * @return iterable */ protected function discoverEventsWithin() { @@ -175,23 +173,24 @@ protected function discoverEventsWithin() /** * Add the given event discovery paths to the application's event discovery paths. * - * @param string|array $paths + * @param string|iterable $paths * @return void */ - public static function addEventDiscoveryPaths(array|string $paths) + public static function addEventDiscoveryPaths(iterable|string $paths) { - static::$eventDiscoveryPaths = array_values(array_unique( - array_merge(static::$eventDiscoveryPaths, Arr::wrap($paths)) - )); + static::$eventDiscoveryPaths = (new LazyCollection(static::$eventDiscoveryPaths)) + ->merge(is_string($paths) ? [$paths] : $paths) + ->unique() + ->values(); } /** * Set the globally configured event discovery paths. * - * @param array $paths + * @param iterable $paths * @return void */ - public static function setEventDiscoveryPaths(array $paths) + public static function setEventDiscoveryPaths(iterable $paths) { static::$eventDiscoveryPaths = $paths; } diff --git a/tests/Integration/Foundation/DiscoverEventsTest.php b/tests/Integration/Foundation/DiscoverEventsTest.php index 116f2374afba..417358539e24 100644 --- a/tests/Integration/Foundation/DiscoverEventsTest.php +++ b/tests/Integration/Foundation/DiscoverEventsTest.php @@ -57,6 +57,33 @@ class_alias(UnionListener::class, 'Tests\Integration\Foundation\Fixtures\EventDi ], $events); } + public function testMultipleDirectoriesCanBeDiscovered(): void + { + $events = DiscoverEvents::within([ + __DIR__.'/Fixtures/EventDiscovery/Listeners', + __DIR__.'/Fixtures/EventDiscovery/UnionListeners', + ], getcwd()); + + $this->assertEquals([ + EventOne::class => [ + Listener::class.'@handle', + Listener::class.'@handleEventOne', + UnionListener::class.'@handle', + ], + EventTwo::class => [ + Listener::class.'@handleEventTwo', + UnionListener::class.'@handle', + ], + ], $events); + } + + public function testNoExceptionForEmptyDirectories(): void + { + $events = DiscoverEvents::within([], getcwd()); + + $this->assertEquals([], $events); + } + public function testEventsCanBeDiscoveredUsingCustomClassNameGuessing() { DiscoverEvents::guessClassNamesUsing(function (SplFileInfo $file, $basePath) { From 776653a688d0ff1009604051485abfb3b75eb42c Mon Sep 17 00:00:00 2001 From: Stephen Rees-Carter Date: Mon, 12 May 2025 06:46:35 +1000 Subject: [PATCH 130/158] Backporting Timebox fixes to 11.x (#55705) * Shift Timebox up a level to encapsulate the query too * Add Timebox Duration into config * Add Timebox to Password Broker * Add early return inside reset Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> * formatting --------- Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com> Co-authored-by: Taylor Otwell --- src/Illuminate/Auth/AuthManager.php | 1 + .../Auth/Passwords/PasswordBroker.php | 112 ++++++++++++------ .../Auth/Passwords/PasswordBrokerManager.php | 1 + src/Illuminate/Auth/SessionGuard.php | 98 +++++++++------ 4 files changed, 138 insertions(+), 74 deletions(-) diff --git a/src/Illuminate/Auth/AuthManager.php b/src/Illuminate/Auth/AuthManager.php index a4bc8e8a4b3f..f7e350e2daab 100755 --- a/src/Illuminate/Auth/AuthManager.php +++ b/src/Illuminate/Auth/AuthManager.php @@ -127,6 +127,7 @@ public function createSessionDriver($name, $config) $this->createUserProvider($config['provider'] ?? null), $this->app['session.store'], rehashOnLogin: $this->app['config']->get('hashing.rehash_on_login', true), + timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000), ); // When using the remember me functionality of the authentication services we diff --git a/src/Illuminate/Auth/Passwords/PasswordBroker.php b/src/Illuminate/Auth/Passwords/PasswordBroker.php index 29ef2f9cbce6..17b771586183 100755 --- a/src/Illuminate/Auth/Passwords/PasswordBroker.php +++ b/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\Arr; +use Illuminate\Support\Timebox; use UnexpectedValueException; class PasswordBroker implements PasswordBrokerContract @@ -34,19 +35,42 @@ class PasswordBroker implements PasswordBrokerContract */ protected $events; + /** + * The timebox instance. + * + * @var \Illuminate\Support\Timebox + */ + protected $timebox; + + /** + * The number of microseconds that the timebox should wait for. + * + * @var int + */ + protected $timeboxDuration; + /** * Create a new password broker instance. * * @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens * @param \Illuminate\Contracts\Auth\UserProvider $users * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher + * @param \Illuminate\Support\Timebox|null $timebox + * @param int $timeboxDuration * @return void */ - public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tokens, UserProvider $users, ?Dispatcher $dispatcher = null) - { + public function __construct( + #[\SensitiveParameter] TokenRepositoryInterface $tokens, + UserProvider $users, + ?Dispatcher $dispatcher = null, + ?Timebox $timebox = null, + int $timeboxDuration = 200000, + ) { $this->users = $users; $this->tokens = $tokens; $this->events = $dispatcher; + $this->timebox = $timebox ?: new Timebox; + $this->timeboxDuration = $timeboxDuration; } /** @@ -58,33 +82,35 @@ public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tok */ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closure $callback = null) { - // First we will check to see if we found a user at the given credentials and - // if we did not we will redirect back to this current URI with a piece of - // "flash" data in the session to indicate to the developers the errors. - $user = $this->getUser($credentials); + return $this->timebox->call(function () use ($credentials, $callback) { + // First we will check to see if we found a user at the given credentials and + // if we did not we will redirect back to this current URI with a piece of + // "flash" data in the session to indicate to the developers the errors. + $user = $this->getUser($credentials); - if (is_null($user)) { - return static::INVALID_USER; - } + if (is_null($user)) { + return static::INVALID_USER; + } - if ($this->tokens->recentlyCreatedToken($user)) { - return static::RESET_THROTTLED; - } + if ($this->tokens->recentlyCreatedToken($user)) { + return static::RESET_THROTTLED; + } - $token = $this->tokens->create($user); + $token = $this->tokens->create($user); - if ($callback) { - return $callback($user, $token) ?? static::RESET_LINK_SENT; - } + if ($callback) { + return $callback($user, $token) ?? static::RESET_LINK_SENT; + } - // Once we have the reset token, we are ready to send the message out to this - // user with a link to reset their password. We will then redirect back to - // the current URI having nothing set in the session to indicate errors. - $user->sendPasswordResetNotification($token); + // Once we have the reset token, we are ready to send the message out to this + // user with a link to reset their password. We will then redirect back to + // the current URI having nothing set in the session to indicate errors. + $user->sendPasswordResetNotification($token); - $this->events?->dispatch(new PasswordResetLinkSent($user)); + $this->events?->dispatch(new PasswordResetLinkSent($user)); - return static::RESET_LINK_SENT; + return static::RESET_LINK_SENT; + }, $this->timeboxDuration); } /** @@ -96,25 +122,29 @@ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closur */ public function reset(#[\SensitiveParameter] array $credentials, Closure $callback) { - $user = $this->validateReset($credentials); + return $this->timebox->call(function ($timebox) use ($credentials, $callback) { + $user = $this->validateReset($credentials); - // If the responses from the validate method is not a user instance, we will - // assume that it is a redirect and simply return it from this method and - // the user is properly redirected having an error message on the post. - if (! $user instanceof CanResetPasswordContract) { - return $user; - } + // If the responses from the validate method is not a user instance, we will + // assume that it is a redirect and simply return it from this method and + // the user is properly redirected having an error message on the post. + if (! $user instanceof CanResetPasswordContract) { + return $user; + } - $password = $credentials['password']; + $password = $credentials['password']; - // Once the reset has been validated, we'll call the given callback with the - // new password. This gives the user an opportunity to store the password - // in their persistent storage. Then we'll delete the token and return. - $callback($user, $password); + // Once the reset has been validated, we'll call the given callback with the + // new password. This gives the user an opportunity to store the password + // in their persistent storage. Then we'll delete the token and return. + $callback($user, $password); - $this->tokens->delete($user); + $this->tokens->delete($user); + + $timebox->returnEarly(); - return static::PASSWORD_RESET; + return static::PASSWORD_RESET; + }, $this->timeboxDuration); } /** @@ -200,4 +230,14 @@ public function getRepository() { return $this->tokens; } + + /** + * Get the timebox instance used by the guard. + * + * @return \Illuminate\Support\Timebox + */ + public function getTimebox() + { + return $this->timebox; + } } diff --git a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php index c388c693df79..0c98ee1fcb6b 100644 --- a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php @@ -71,6 +71,7 @@ protected function resolve($name) $this->createTokenRepository($config), $this->app['auth']->createUserProvider($config['provider'] ?? null), $this->app['events'] ?? null, + timeboxDuration: $this->app['config']->get('auth.timebox_duration', 200000), ); } diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 4aede1ae6767..86dde27d1c93 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -96,6 +96,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth */ protected $timebox; + /** + * The number of microseconds that the timebox should wait for. + * + * @var int + */ + protected $timeboxDuration; + /** * Indicates if passwords should be rehashed on login if needed. * @@ -126,6 +133,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth * @param \Symfony\Component\HttpFoundation\Request|null $request * @param \Illuminate\Support\Timebox|null $timebox * @param bool $rehashOnLogin + * @param int $timeboxDuration * @return void */ public function __construct( @@ -135,6 +143,7 @@ public function __construct( ?Request $request = null, ?Timebox $timebox = null, bool $rehashOnLogin = true, + int $timeboxDuration = 200000, ) { $this->name = $name; $this->session = $session; @@ -142,6 +151,7 @@ public function __construct( $this->provider = $provider; $this->timebox = $timebox ?: new Timebox; $this->rehashOnLogin = $rehashOnLogin; + $this->timeboxDuration = $timeboxDuration; } /** @@ -291,9 +301,17 @@ public function onceUsingId($id) */ public function validate(array $credentials = []) { - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + return $this->timebox->call(function ($timebox) use ($credentials) { + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - return $this->hasValidCredentials($user, $credentials); + $validated = $this->hasValidCredentials($user, $credentials); + + if ($validated) { + $timebox->returnEarly(); + } + + return $validated; + }, $this->timeboxDuration); } /** @@ -391,27 +409,31 @@ protected function failedBasicResponse() */ public function attempt(array $credentials = [], $remember = false) { - $this->fireAttemptEvent($credentials, $remember); + return $this->timebox->call(function ($timebox) use ($credentials, $remember) { + $this->fireAttemptEvent($credentials, $remember); - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - // If an implementation of UserInterface was returned, we'll ask the provider - // to validate the user against the given credentials, and if they are in - // fact valid we'll log the users into the application and return true. - if ($this->hasValidCredentials($user, $credentials)) { - $this->rehashPasswordIfRequired($user, $credentials); + // If an implementation of UserInterface was returned, we'll ask the provider + // to validate the user against the given credentials, and if they are in + // fact valid we'll log the users into the application and return true. + if ($this->hasValidCredentials($user, $credentials)) { + $this->rehashPasswordIfRequired($user, $credentials); - $this->login($user, $remember); + $this->login($user, $remember); - return true; - } + $timebox->returnEarly(); - // If the authentication attempt fails we will fire an event so that the user - // may be notified of any suspicious attempts to access their account from - // an unrecognized user. A developer may listen to this event as needed. - $this->fireFailedEvent($user, $credentials); + return true; + } - return false; + // If the authentication attempt fails we will fire an event so that the user + // may be notified of any suspicious attempts to access their account from + // an unrecognized user. A developer may listen to this event as needed. + $this->fireFailedEvent($user, $credentials); + + return false; + }, $this->timeboxDuration); } /** @@ -424,24 +446,28 @@ public function attempt(array $credentials = [], $remember = false) */ public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false) { - $this->fireAttemptEvent($credentials, $remember); + return $this->timebox->call(function ($timebox) use ($credentials, $callbacks, $remember) { + $this->fireAttemptEvent($credentials, $remember); - $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); - // This method does the exact same thing as attempt, but also executes callbacks after - // the user is retrieved and validated. If one of the callbacks returns falsy we do - // not login the user. Instead, we will fail the specific authentication attempt. - if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) { - $this->rehashPasswordIfRequired($user, $credentials); + // This method does the exact same thing as attempt, but also executes callbacks after + // the user is retrieved and validated. If one of the callbacks returns falsy we do + // not login the user. Instead, we will fail the specific authentication attempt. + if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) { + $this->rehashPasswordIfRequired($user, $credentials); - $this->login($user, $remember); + $this->login($user, $remember); - return true; - } + $timebox->returnEarly(); - $this->fireFailedEvent($user, $credentials); + return true; + } - return false; + $this->fireFailedEvent($user, $credentials); + + return false; + }, $this->timeboxDuration); } /** @@ -453,17 +479,13 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe */ protected function hasValidCredentials($user, $credentials) { - return $this->timebox->call(function ($timebox) use ($user, $credentials) { - $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); - - if ($validated) { - $timebox->returnEarly(); + $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); - $this->fireValidatedEvent($user); - } + if ($validated) { + $this->fireValidatedEvent($user); + } - return $validated; - }, 200 * 1000); + return $validated; } /** From 2661ac87d4186c8b28e51ae078fedf8b3f70917a Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Sun, 11 May 2025 20:47:08 +0000 Subject: [PATCH 131/158] Update facade docblocks --- src/Illuminate/Support/Facades/Password.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Password.php b/src/Illuminate/Support/Facades/Password.php index 7099e3971fd8..ac6f226aa251 100755 --- a/src/Illuminate/Support/Facades/Password.php +++ b/src/Illuminate/Support/Facades/Password.php @@ -15,6 +15,7 @@ * @method static void deleteToken(\Illuminate\Contracts\Auth\CanResetPassword $user) * @method static bool tokenExists(\Illuminate\Contracts\Auth\CanResetPassword $user, string $token) * @method static \Illuminate\Auth\Passwords\TokenRepositoryInterface getRepository() + * @method static \Illuminate\Support\Timebox getTimebox() * * @see \Illuminate\Auth\Passwords\PasswordBrokerManager * @see \Illuminate\Auth\Passwords\PasswordBroker From 28704dda0ba1df9e49b85b3bb9d9610b57089c7d Mon Sep 17 00:00:00 2001 From: Liam Duckett Date: Sun, 11 May 2025 21:47:32 +0100 Subject: [PATCH 132/158] improve typehints on authorizes requests resource methods (#55706) --- src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php index adabdcec0576..8574401b5d3e 100644 --- a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php +++ b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php @@ -108,7 +108,7 @@ public function authorizeResource($model, $parameter = null, array $options = [] /** * Get the map of resource methods to ability names. * - * @return array + * @return array */ protected function resourceAbilityMap() { @@ -126,7 +126,7 @@ protected function resourceAbilityMap() /** * Get the list of resource methods which do not have model parameters. * - * @return array + * @return list */ protected function resourceMethodsWithoutModels() { From 14c49a749372460660d59988304954b949502ccd Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Mon, 12 May 2025 11:56:23 +1000 Subject: [PATCH 133/158] [12.x] Add flexible support to memoized cache store (#55709) * Add flexible support to memoized cache store * Update MemoizedStore.php * Update MemoizedStore.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Cache/MemoizedStore.php | 37 +++++++- tests/Integration/Cache/MemoizedStoreTest.php | 85 +++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/MemoizedStore.php b/src/Illuminate/Cache/MemoizedStore.php index d899ef09d609..fc6313db2a1a 100644 --- a/src/Illuminate/Cache/MemoizedStore.php +++ b/src/Illuminate/Cache/MemoizedStore.php @@ -2,9 +2,11 @@ namespace Illuminate\Cache; +use BadMethodCallException; +use Illuminate\Contracts\Cache\LockProvider; use Illuminate\Contracts\Cache\Store; -class MemoizedStore implements Store +class MemoizedStore implements LockProvider, Store { /** * The memoized cache values. @@ -160,6 +162,39 @@ public function forever($key, $value) return $this->repository->forever($key, $value); } + /** + * Get a lock instance. + * + * @param string $name + * @param int $seconds + * @param string|null $owner + * @return \Illuminate\Contracts\Cache\Lock + */ + public function lock($name, $seconds = 0, $owner = null) + { + if (! $this->repository->getStore() instanceof LockProvider) { + throw new BadMethodCallException('This cache store does not support locks.'); + } + + return $this->repository->getStore()->lock(...func_get_args()); + } + + /** + * Restore a lock instance using the owner identifier. + * + * @param string $name + * @param string $owner + * @return \Illuminate\Contracts\Cache\Lock + */ + public function restoreLock($name, $owner) + { + if (! $this->repository instanceof LockProvider) { + throw new BadMethodCallException('This cache store does not support locks.'); + } + + return $this->repository->resoreLock(...func_get_args()); + } + /** * Remove an item from the cache. * diff --git a/tests/Integration/Cache/MemoizedStoreTest.php b/tests/Integration/Cache/MemoizedStoreTest.php index 8ca80eda5135..e9cd17ff94ce 100644 --- a/tests/Integration/Cache/MemoizedStoreTest.php +++ b/tests/Integration/Cache/MemoizedStoreTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Cache; +use BadMethodCallException; use Illuminate\Cache\Events\CacheEvent; use Illuminate\Cache\Events\CacheMissed; use Illuminate\Cache\Events\ForgettingKey; @@ -10,12 +11,16 @@ use Illuminate\Cache\Events\RetrievingKey; use Illuminate\Cache\Events\RetrievingManyKeys; use Illuminate\Cache\Events\WritingKey; +use Illuminate\Contracts\Cache\Store; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Exceptions; use Illuminate\Support\Facades\Redis; +use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; +use Throwable; class MemoizedStoreTest extends TestCase { @@ -406,4 +411,84 @@ public function test_it_resets_cache_store_with_scoped_instances() $this->assertSame('Taylor', $live); $this->assertSame('Taylor', $memoized); } + + public function test_it_throws_when_underlying_store_does_not_support_locks() + { + $this->freezeTime(); + $exceptions = []; + Exceptions::reportable(function (Throwable $e) use (&$exceptions) { + $exceptions[] = $e; + }); + Config::set('cache.stores.no-lock', ['driver' => 'no-lock']); + Cache::extend('no-lock', fn () => Cache::repository(new class implements Store { + public function get($key) + { + return Cache::get(...func_get_args()); + } + + public function many(array $keys) + { + return Cache::many(...func_get_args()); + } + + public function put($key, $value, $seconds) { + + return Cache::put(...func_get_args()); + } + + public function putMany(array $values, $seconds) { + return Cache::putMany(...func_get_args()); + } + + public function increment($key, $value = 1) { + + return Cache::increment(...func_get_args()); + } + + public function decrement($key, $value = 1) { + return Cache::decrement(...func_get_args()); + } + + public function forever($key, $value) { + return Cache::forever(...func_get_args()); + + } + + public function forget($key) { + return Cache::forget(...func_get_args()); + } + + public function flush() { + return Cache::flush(...func_get_args()); + + } + + public function getPrefix() { + return Cache::getPrefix(...func_get_args()); + } + })); + Cache::flexible('key', [10, 20], 'value-1'); + + $this->travel(11)->seconds(); + Cache::memo('no-lock')->flexible('key', [10, 20], 'value-2'); + defer()->invoke(); + $value = Cache::get('key'); + + $this->assertCount(1, $exceptions); + $this->assertInstanceOf(BadMethodCallException::class, $exceptions[0]); + $this->assertSame('This cache store does not support locks.', $exceptions[0]->getMessage()); + } + + public function test_it_supports_with_flexible() + { + $this->freezeTime(); + Cache::flexible('key', [10, 20], 'value-1'); + + $this->travel(11)->seconds(); + Cache::memo()->flexible('key', [10, 20], 'value-2'); + defer()->invoke(); + $value = Cache::get('key'); + + $this->assertSame('value-2', $value); + } } From 98ae8ed50ed37f74f99e3096d95ab1424bfa8907 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 12 May 2025 01:56:44 +0000 Subject: [PATCH 134/158] Apply fixes from StyleCI --- tests/Integration/Cache/MemoizedStoreTest.php | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/Integration/Cache/MemoizedStoreTest.php b/tests/Integration/Cache/MemoizedStoreTest.php index e9cd17ff94ce..009906f1555f 100644 --- a/tests/Integration/Cache/MemoizedStoreTest.php +++ b/tests/Integration/Cache/MemoizedStoreTest.php @@ -18,7 +18,6 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Exceptions; use Illuminate\Support\Facades\Redis; -use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; use Throwable; @@ -420,50 +419,55 @@ public function test_it_throws_when_underlying_store_does_not_support_locks() $exceptions[] = $e; }); Config::set('cache.stores.no-lock', ['driver' => 'no-lock']); - Cache::extend('no-lock', fn () => Cache::repository(new class implements Store { + Cache::extend('no-lock', fn () => Cache::repository(new class implements Store + { public function get($key) { return Cache::get(...func_get_args()); } - public function many(array $keys) + public function many(array $keys) { return Cache::many(...func_get_args()); } - public function put($key, $value, $seconds) { - + public function put($key, $value, $seconds) + { return Cache::put(...func_get_args()); } - public function putMany(array $values, $seconds) { + public function putMany(array $values, $seconds) + { return Cache::putMany(...func_get_args()); } - public function increment($key, $value = 1) { - + public function increment($key, $value = 1) + { return Cache::increment(...func_get_args()); } - public function decrement($key, $value = 1) { + public function decrement($key, $value = 1) + { return Cache::decrement(...func_get_args()); } - public function forever($key, $value) { + public function forever($key, $value) + { return Cache::forever(...func_get_args()); - } - public function forget($key) { + public function forget($key) + { return Cache::forget(...func_get_args()); } - public function flush() { + public function flush() + { return Cache::flush(...func_get_args()); - } - public function getPrefix() { + public function getPrefix() + { return Cache::getPrefix(...func_get_args()); } })); From 3c86472eed69117e836f4d72d0623c54a16a46bb Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 12 May 2025 21:24:30 +0800 Subject: [PATCH 135/158] Test SQLServer 2017 on Ubuntu 22.04 (#55716) Signed-off-by: Mior Muhammad Zaki --- .github/workflows/databases.yml | 94 ++++++++++++++++----------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 1b950622fefb..f1098fad5fee 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -293,53 +293,53 @@ jobs: DB_USERNAME: SA DB_PASSWORD: Forge123 - # mssql_2017: - # runs-on: ubuntu-20.04 - # timeout-minutes: 5 - - # services: - # sqlsrv: - # image: mcr.microsoft.com/mssql/server:2017-latest - # env: - # ACCEPT_EULA: Y - # SA_PASSWORD: Forge123 - # ports: - # - 1433:1433 - - # strategy: - # fail-fast: true - - # name: SQL Server 2017 - - # steps: - # - name: Checkout code - # uses: actions/checkout@v4 - - # - name: Setup PHP - # uses: shivammathur/setup-php@v2 - # with: - # php-version: 8.3 - # extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr - # tools: composer:v2 - # coverage: none - - # - name: Set Framework version - # run: composer config version "11.x-dev" - - # - name: Install dependencies - # uses: nick-fields/retry@v3 - # with: - # timeout_minutes: 5 - # max_attempts: 5 - # command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - # - name: Execute tests - # run: vendor/bin/phpunit tests/Integration/Database - # env: - # DB_CONNECTION: sqlsrv - # DB_DATABASE: master - # DB_USERNAME: SA - # DB_PASSWORD: Forge123 + mssql_2017: + runs-on: ubuntu-22.04 + timeout-minutes: 5 + + services: + sqlsrv: + image: mcr.microsoft.com/mssql/server:2017-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: Forge123 + ports: + - 1433:1433 + + strategy: + fail-fast: true + + name: SQL Server 2017 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr + tools: composer:v2 + coverage: none + + - name: Set Framework version + run: composer config version "11.x-dev" + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Database + env: + DB_CONNECTION: sqlsrv + DB_DATABASE: master + DB_USERNAME: SA + DB_PASSWORD: Forge123 sqlite: runs-on: ubuntu-24.04 From 43bcb81c412ef988dd818891a50d859d577a2df6 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 12 May 2025 21:30:23 +0800 Subject: [PATCH 136/158] Fix Symfony 7.3 deprecations (#55711) Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/Application.php | 18 +++++++++++++----- tests/Console/CommandMutexTest.php | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 63e364e2d57d..c6bcaec37c67 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -8,7 +8,9 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\ProcessUtils; +use ReflectionClass; use Symfony\Component\Console\Application as SymfonyApplication; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Input\ArrayInput; @@ -239,12 +241,18 @@ protected function addToParent(SymfonyCommand $command) */ public function resolve($command) { - if (is_subclass_of($command, SymfonyCommand::class) && ($commandName = $command::getDefaultName())) { - foreach (explode('|', $commandName) as $name) { - $this->commandMap[$name] = $command; - } + if (is_subclass_of($command, SymfonyCommand::class)) { + $attribute = (new ReflectionClass($command))->getAttributes(AsCommand::class); + + $commandName = ! empty($attribute) ? $attribute[0]->newInstance()->name : null; - return null; + if (! is_null($commandName)) { + foreach (explode('|', $commandName) as $name) { + $this->commandMap[$name] = $command; + } + + return null; + } } if ($command instanceof Command) { diff --git a/tests/Console/CommandMutexTest.php b/tests/Console/CommandMutexTest.php index 0743e6f43e7c..528af2fc2057 100644 --- a/tests/Console/CommandMutexTest.php +++ b/tests/Console/CommandMutexTest.php @@ -7,12 +7,15 @@ use Illuminate\Contracts\Console\Isolatable; use Illuminate\Foundation\Application; use Mockery as m; +use Orchestra\Testbench\Concerns\InteractsWithMockery; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; class CommandMutexTest extends TestCase { + use InteractsWithMockery; + /** * @var Command */ @@ -23,6 +26,8 @@ class CommandMutexTest extends TestCase */ protected $commandMutex; + /** {@inheritdoc} */ + #[\Override] protected function setUp(): void { $this->command = new class extends Command implements Isolatable @@ -42,6 +47,13 @@ public function __invoke() $this->command->setLaravel($app); } + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + $this->tearDownTheTestEnvironmentUsingMockery(); + } + public function testCanRunIsolatedCommandIfNotBlocked() { $this->commandMutex->shouldReceive('create') From 64c440fe5bd30109691a73e3929b90277ad9c834 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 12 May 2025 15:59:24 +0200 Subject: [PATCH 137/158] [12.x] Introduce Arr::from() (#55715) * [12.x] Introduce Arr::from() * Arr::from should not wrap scalar values * Arr::arrayable helper function * Arr::from can be used with raw objects except enums; handle UnitEnum instances as scalars * Add tests * Replace implicit `getArrayableItems` usage with `Arr::from` * Handle enums as regular objects in `Arr::from`; wrap enums in `getArrayableItems` * Remove useless `Arr::arrayable` checks * Revert to `instanceof Traversable` checks --------- Co-authored-by: Sergey Danilchenko --- src/Illuminate/Collections/Arr.php | 46 ++++++++++++++ .../Collections/Traits/EnumeratesValues.php | 17 +---- src/Illuminate/Collections/helpers.php | 4 +- .../Foundation/Testing/DatabaseTruncation.php | 3 +- src/Illuminate/Support/Str.php | 10 +-- tests/Support/Common.php | 62 +++++++++++++++++++ tests/Support/SupportArrTest.php | 49 +++++++++++++++ tests/Support/SupportCollectionTest.php | 60 +----------------- 8 files changed, 171 insertions(+), 80 deletions(-) create mode 100644 tests/Support/Common.php diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index d9b7561db2cf..bea43ce76c26 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -4,9 +4,14 @@ use ArgumentCountError; use ArrayAccess; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; +use JsonSerializable; use Random\Randomizer; +use Traversable; +use WeakMap; class Arr { @@ -23,6 +28,21 @@ public static function accessible($value) return is_array($value) || $value instanceof ArrayAccess; } + /** + * Determine whether the given value is arrayable. + * + * @param mixed $value + * @return bool + */ + public static function arrayable($value) + { + return is_array($value) + || $value instanceof Arrayable + || $value instanceof Traversable + || $value instanceof Jsonable + || $value instanceof JsonSerializable; + } + /** * Add an element to an array using "dot" notation if it doesn't exist. * @@ -378,6 +398,32 @@ public static function forget(&$array, $keys) } } + /** + * Get the underlying array of items from the given argument. + * + * @template TKey of array-key = array-key + * @template TValue = mixed + * + * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|object $items + * @return ($items is WeakMap ? list : array) + * + * @throws \InvalidArgumentException + */ + public static function from($items) + { + return match (true) { + is_array($items) => $items, + $items instanceof Enumerable => $items->all(), + $items instanceof Arrayable => $items->toArray(), + $items instanceof WeakMap => iterator_to_array($items, false), + $items instanceof Traversable => iterator_to_array($items), + $items instanceof Jsonable => json_decode($items->toJson(), true), + $items instanceof JsonSerializable => (array) $items->jsonSerialize(), + is_object($items) => (array) $items, + default => throw new InvalidArgumentException('Items cannot be represented by a scalar value.'), + }; + } + /** * Get an item from an array using "dot" notation. * diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index d2894529ed6e..c11c9c434d89 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -12,12 +12,9 @@ use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; -use InvalidArgumentException; use JsonSerializable; -use Traversable; use UnexpectedValueException; use UnitEnum; -use WeakMap; use function Illuminate\Support\enum_value; @@ -1059,17 +1056,9 @@ public function __get($key) */ protected function getArrayableItems($items) { - return match (true) { - is_array($items) => $items, - $items instanceof WeakMap => throw new InvalidArgumentException('Collections can not be created using instances of WeakMap.'), - $items instanceof Enumerable => $items->all(), - $items instanceof Arrayable => $items->toArray(), - $items instanceof Traversable => iterator_to_array($items), - $items instanceof Jsonable => json_decode($items->toJson(), true), - $items instanceof JsonSerializable => (array) $items->jsonSerialize(), - $items instanceof UnitEnum => [$items], - default => (array) $items, - }; + return is_null($items) || is_scalar($items) || $items instanceof UnitEnum + ? Arr::wrap($items) + : Arr::from($items); } /** diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 55844559e711..16c8f0118993 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -77,9 +77,9 @@ function data_get($target, $key, $default = null) $segment = match ($segment) { '\*' => '*', '\{first}' => '{first}', - '{first}' => array_key_first(is_array($target) ? $target : (new Collection($target))->all()), + '{first}' => array_key_first(Arr::from($target)), '\{last}' => '{last}', - '{last}' => array_key_last(is_array($target) ? $target : (new Collection($target))->all()), + '{last}' => array_key_last(Arr::from($target)), default => $segment, }; diff --git a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index 9ed063241a8f..a84c082343b7 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\ConnectionInterface; use Illuminate\Foundation\Testing\Traits\CanConfigureMigrationCommands; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; trait DatabaseTruncation @@ -120,7 +121,7 @@ protected function getAllTablesForConnection(ConnectionInterface $connection, ?s $schema = $connection->getSchemaBuilder(); - return static::$allTables[$name] = (new Collection($schema->getTables($schema->getCurrentSchemaListing())))->all(); + return static::$allTables[$name] = Arr::from($schema->getTables($schema->getCurrentSchemaListing())); } /** diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 3bb47011d654..27bdb6bf0189 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1180,7 +1180,7 @@ public static function repeat(string $string, int $times) public static function replaceArray($search, $replace, $subject) { if ($replace instanceof Traversable) { - $replace = (new Collection($replace))->all(); + $replace = Arr::from($replace); } $segments = explode($search, $subject); @@ -1222,15 +1222,15 @@ private static function toStringOr($value, $fallback) public static function replace($search, $replace, $subject, $caseSensitive = true) { if ($search instanceof Traversable) { - $search = (new Collection($search))->all(); + $search = Arr::from($search); } if ($replace instanceof Traversable) { - $replace = (new Collection($replace))->all(); + $replace = Arr::from($replace); } if ($subject instanceof Traversable) { - $subject = (new Collection($subject))->all(); + $subject = Arr::from($subject); } return $caseSensitive @@ -1363,7 +1363,7 @@ public static function replaceMatches($pattern, $replace, $subject, $limit = -1) public static function remove($search, $subject, $caseSensitive = true) { if ($search instanceof Traversable) { - $search = (new Collection($search))->all(); + $search = Arr::from($search); } return $caseSensitive diff --git a/tests/Support/Common.php b/tests/Support/Common.php new file mode 100644 index 000000000000..7928b8705624 --- /dev/null +++ b/tests/Support/Common.php @@ -0,0 +1,62 @@ + 'bar']; + } +} + +class TestJsonableObject implements Jsonable +{ + public function toJson($options = 0) + { + return '{"foo":"bar"}'; + } +} + +class TestJsonSerializeObject implements JsonSerializable +{ + public function jsonSerialize(): array + { + return ['foo' => 'bar']; + } +} + +class TestJsonSerializeWithScalarValueObject implements JsonSerializable +{ + public function jsonSerialize(): string + { + return 'foo'; + } +} + +class TestTraversableAndJsonSerializableObject implements IteratorAggregate, JsonSerializable +{ + public $items; + + public function __construct($items = []) + { + $this->items = $items; + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + public function jsonSerialize(): array + { + return json_decode(json_encode($this->items), true); + } +} diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index e5d15a06362f..a81e6eb79bee 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -11,6 +11,10 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use stdClass; +use WeakMap; + +include_once 'Common.php'; +include_once 'Enums.php'; class SupportArrTest extends TestCase { @@ -32,6 +36,25 @@ public function testAccessible(): void $this->assertFalse(Arr::accessible(static fn () => null)); } + public function testArrayable(): void + { + $this->assertTrue(Arr::arrayable([])); + $this->assertTrue(Arr::arrayable(new TestArrayableObject)); + $this->assertTrue(Arr::arrayable(new TestJsonableObject)); + $this->assertTrue(Arr::arrayable(new TestJsonSerializeObject)); + $this->assertTrue(Arr::arrayable(new TestTraversableAndJsonSerializableObject)); + + $this->assertFalse(Arr::arrayable(null)); + $this->assertFalse(Arr::arrayable('abc')); + $this->assertFalse(Arr::arrayable(new stdClass)); + $this->assertFalse(Arr::arrayable((object) ['a' => 1, 'b' => 2])); + $this->assertFalse(Arr::arrayable(123)); + $this->assertFalse(Arr::arrayable(12.34)); + $this->assertFalse(Arr::arrayable(true)); + $this->assertFalse(Arr::arrayable(new \DateTime)); + $this->assertFalse(Arr::arrayable(static fn () => null)); + } + public function testAdd() { $array = Arr::add(['name' => 'Desk'], 'price', 100); @@ -1485,6 +1508,32 @@ public function testForget() $this->assertEquals([2 => [1 => 'products']], $array); } + public function testFrom() + { + $this->assertSame(['foo' => 'bar'], Arr::from(['foo' => 'bar'])); + $this->assertSame(['foo' => 'bar'], Arr::from((object) ['foo' => 'bar'])); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestArrayableObject)); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonableObject)); + $this->assertSame(['foo' => 'bar'], Arr::from(new TestJsonSerializeObject)); + $this->assertSame(['foo'], Arr::from(new TestJsonSerializeWithScalarValueObject)); + + $this->assertSame(['name' => 'A'], Arr::from(TestEnum::A)); + $this->assertSame(['name' => 'A', 'value' => 1], Arr::from(TestBackedEnum::A)); + $this->assertSame(['name' => 'A', 'value' => 'A'], Arr::from(TestStringBackedEnum::A)); + + $subject = [new stdClass, new stdClass]; + $items = new TestTraversableAndJsonSerializableObject($subject); + $this->assertSame($subject, Arr::from($items)); + + $items = new WeakMap; + $items[$temp = new class {}] = 'bar'; + $this->assertSame(['bar'], Arr::from($items)); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Items cannot be represented by a scalar value.'); + Arr::from(123); + } + public function testWrap() { $string = 'a'; diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 7c8104b570ae..513e1fa4187a 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -8,7 +8,6 @@ use CachingIterator; use Exception; use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Contracts\Support\Jsonable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Illuminate\Support\HtmlString; @@ -18,7 +17,6 @@ use Illuminate\Support\Str; use Illuminate\Support\Stringable; use InvalidArgumentException; -use IteratorAggregate; use JsonSerializable; use Mockery as m; use PHPUnit\Framework\Attributes\DataProvider; @@ -26,10 +24,10 @@ use ReflectionClass; use stdClass; use Symfony\Component\VarDumper\VarDumper; -use Traversable; use UnexpectedValueException; use WeakMap; +include_once 'Common.php'; include_once 'Enums.php'; class SupportCollectionTest extends TestCase @@ -2915,14 +2913,12 @@ public function testConstructMethodFromObject($collection) #[DataProvider('collectionClassProvider')] public function testConstructMethodFromWeakMap($collection) { - $this->expectException('InvalidArgumentException'); - $map = new WeakMap(); $object = new stdClass; $object->foo = 'bar'; $map[$object] = 3; - $data = new $collection($map); + $this->assertEquals([3], $data->all()); } public function testSplice() @@ -5787,30 +5783,6 @@ public function offsetUnset($offset): void } } -class TestArrayableObject implements Arrayable -{ - public function toArray() - { - return ['foo' => 'bar']; - } -} - -class TestJsonableObject implements Jsonable -{ - public function toJson($options = 0) - { - return '{"foo":"bar"}'; - } -} - -class TestJsonSerializeObject implements JsonSerializable -{ - public function jsonSerialize(): array - { - return ['foo' => 'bar']; - } -} - class TestJsonSerializeToStringObject implements JsonSerializable { public function jsonSerialize(): string @@ -5819,34 +5791,6 @@ public function jsonSerialize(): string } } -class TestJsonSerializeWithScalarValueObject implements JsonSerializable -{ - public function jsonSerialize(): string - { - return 'foo'; - } -} - -class TestTraversableAndJsonSerializableObject implements IteratorAggregate, JsonSerializable -{ - public $items; - - public function __construct($items) - { - $this->items = $items; - } - - public function getIterator(): Traversable - { - return new ArrayIterator($this->items); - } - - public function jsonSerialize(): array - { - return json_decode(json_encode($this->items), true); - } -} - class TestCollectionMapIntoObject { public $value; From ea78060275595e4d92435890577e85e058c3fa6e Mon Sep 17 00:00:00 2001 From: Amir Alizadeh Date: Tue, 13 May 2025 17:13:49 +0330 Subject: [PATCH 138/158] [12.x] Fix the `getCurrentlyAttachedPivots` wrong `morphClass` for morph to many relationships (#55721) * Fix the `getCurrentlyAttachedPivots` wrong morph class for morph to many relationships * Update the `getCurrentlyAttachedPivotsForIds` return type --- .../Eloquent/Relations/MorphToMany.php | 7 ++- .../Database/EloquentPivotEventsTest.php | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 162ebec1777b..91bbb4b72d3d 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -121,13 +121,14 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, } /** - * Get the pivot models that are currently attached. + * Get the pivot models that are currently attached, filtered by related model keys. * + * @param mixed $ids * @return \Illuminate\Support\Collection */ - protected function getCurrentlyAttachedPivots() + protected function getCurrentlyAttachedPivotsForIds($ids = null) { - return parent::getCurrentlyAttachedPivots()->map(function ($record) { + return parent::getCurrentlyAttachedPivotsForIds($ids)->map(function ($record) { return $record instanceof MorphPivot ? $record->setMorphType($this->morphType) ->setMorphClass($this->morphClass) diff --git a/tests/Integration/Database/EloquentPivotEventsTest.php b/tests/Integration/Database/EloquentPivotEventsTest.php index e94fe5cce005..8521b948e8e9 100644 --- a/tests/Integration/Database/EloquentPivotEventsTest.php +++ b/tests/Integration/Database/EloquentPivotEventsTest.php @@ -179,6 +179,16 @@ public function testCustomMorphPivotClassDetachAttributes() $project->equipments()->save($equipment); $equipment->projects()->sync([]); + + $this->assertEquals( + [PivotEventsTestProject::class, PivotEventsTestProject::class, PivotEventsTestProject::class, PivotEventsTestProject::class, PivotEventsTestProject::class, PivotEventsTestProject::class], + PivotEventsTestModelEquipment::$eventsMorphClasses + ); + + $this->assertEquals( + ['equipmentable_type', 'equipmentable_type', 'equipmentable_type', 'equipmentable_type', 'equipmentable_type', 'equipmentable_type'], + PivotEventsTestModelEquipment::$eventsMorphTypes + ); } } @@ -237,6 +247,55 @@ class PivotEventsTestModelEquipment extends MorphPivot { public $table = 'equipmentables'; + public static $eventsMorphClasses = []; + + public static $eventsMorphTypes = []; + + public static function boot() + { + parent::boot(); + + static::creating(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::created(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::updating(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::updated(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::saving(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::saved(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::deleting(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + + static::deleted(function ($model) { + static::$eventsMorphClasses[] = $model->morphClass; + static::$eventsMorphTypes[] = $model->morphType; + }); + } + public function equipment() { return $this->belongsTo(PivotEventsTestEquipment::class); From b7f80ef807a1d0942301be85fda04d17cf71dc99 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Tue, 13 May 2025 09:46:54 -0400 Subject: [PATCH 139/158] [12.x] Improve typehints for Http classes (#54783) * typehints for Illuminate\Http * remove * remove phpstan-type --- src/Illuminate/Http/Client/Factory.php | 20 ++++++++++---------- src/Illuminate/Http/Client/Pool.php | 4 ++-- src/Illuminate/Http/Client/Response.php | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index c38932ef33d5..49391a4fa1bb 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -62,14 +62,14 @@ class Factory /** * The recorded response array. * - * @var array + * @var list */ protected $recorded = []; /** * All created response sequences. * - * @var array + * @var list<\Illuminate\Http\Client\ResponseSequence> */ protected $responseSequences = []; @@ -195,7 +195,7 @@ public static function failedRequest($body = null, $status = 200, $headers = []) * Create a new connection exception for use during stubbing. * * @param string|null $message - * @return \GuzzleHttp\Promise\PromiseInterface + * @return \Closure(\Illuminate\Http\Client\Request): \GuzzleHttp\Promise\PromiseInterface */ public static function failedConnection($message = null) { @@ -221,7 +221,7 @@ public function sequence(array $responses = []) /** * Register a stub callable that will intercept requests and be able to return stub responses. * - * @param callable|array|null $callback + * @param callable|array|null $callback * @return $this */ public function fake($callback = null) @@ -283,7 +283,7 @@ public function fakeSequence($url = '*') * Stub the given URL using the given callback. * * @param string $url - * @param \Illuminate\Http\Client\Response|\GuzzleHttp\Promise\PromiseInterface|callable|int|string|array $callback + * @param \Illuminate\Http\Client\Response|\GuzzleHttp\Promise\PromiseInterface|callable|int|string|array|\Illuminate\Http\Client\ResponseSequence $callback * @return $this */ public function stubUrl($url, $callback) @@ -371,7 +371,7 @@ public function recordRequestResponsePair($request, $response) /** * Assert that a request / response pair was recorded matching a given truth test. * - * @param callable $callback + * @param callable|(\Closure(\Illuminate\Http\Client\Request, \Illuminate\Http\Client\Response|null): bool) $callback * @return void */ public function assertSent($callback) @@ -385,7 +385,7 @@ public function assertSent($callback) /** * Assert that the given request was sent in the given order. * - * @param array $callbacks + * @param list $callbacks * @return void */ public function assertSentInOrder($callbacks) @@ -407,7 +407,7 @@ public function assertSentInOrder($callbacks) /** * Assert that a request / response pair was not recorded matching a given truth test. * - * @param callable $callback + * @param callable|(\Closure(\Illuminate\Http\Client\Request, \Illuminate\Http\Client\Response|null): bool) $callback * @return void */ public function assertNotSent($callback) @@ -460,8 +460,8 @@ public function assertSequencesAreEmpty() /** * Get a collection of the request / response pairs matching the given truth test. * - * @param callable $callback - * @return \Illuminate\Support\Collection + * @param (\Closure(\Illuminate\Http\Client\Request, \Illuminate\Http\Client\Response|null): bool)|callable $callback + * @return \Illuminate\Support\Collection */ public function recorded($callback = null) { diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php index aba741f4bf75..e9716be08571 100644 --- a/src/Illuminate/Http/Client/Pool.php +++ b/src/Illuminate/Http/Client/Pool.php @@ -26,7 +26,7 @@ class Pool /** * The pool of requests. * - * @var array + * @var array */ protected $pool = []; @@ -65,7 +65,7 @@ protected function asyncRequest() /** * Retrieve the requests in the pool. * - * @return array + * @return array */ public function getRequests() { diff --git a/src/Illuminate/Http/Client/Response.php b/src/Illuminate/Http/Client/Response.php index c2352bb01f81..e69647bfb2bc 100644 --- a/src/Illuminate/Http/Client/Response.php +++ b/src/Illuminate/Http/Client/Response.php @@ -235,7 +235,7 @@ public function serverError() /** * Execute the given callback if there was a server or client error. * - * @param callable $callback + * @param callable|(\Closure(\Illuminate\Http\Client\Response): mixed) $callback * @return $this */ public function onError(callable $callback) @@ -339,7 +339,7 @@ public function throwIf($condition) /** * Throw an exception if the response status code matches the given code. * - * @param callable|int $statusCode + * @param int|(\Closure(int, \Illuminate\Http\Client\Response): bool)|callable $statusCode * @return $this * * @throws \Illuminate\Http\Client\RequestException @@ -357,7 +357,7 @@ public function throwIfStatus($statusCode) /** * Throw an exception unless the response status code matches the given code. * - * @param callable|int $statusCode + * @param int|(\Closure(int, \Illuminate\Http\Client\Response): bool)|callable $statusCode * @return $this * * @throws \Illuminate\Http\Client\RequestException From 111df610fda6c7c08f414efbdcc00e9b36a84579 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 13 May 2025 13:47:26 +0000 Subject: [PATCH 140/158] Update facade docblocks --- src/Illuminate/Support/Facades/Http.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index ecbcca5ba49c..823056859572 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -12,19 +12,19 @@ * @method static \GuzzleHttp\Promise\PromiseInterface response(array|string|null $body = null, int $status = 200, array $headers = []) * @method static \GuzzleHttp\Psr7\Response psr7Response(array|string|null $body = null, int $status = 200, array $headers = []) * @method static \Illuminate\Http\Client\RequestException failedRequest(array|string|null $body = null, int $status = 200, array $headers = []) - * @method static \GuzzleHttp\Promise\PromiseInterface failedConnection(string|null $message = null) + * @method static \Closure failedConnection(string|null $message = null) * @method static \Illuminate\Http\Client\ResponseSequence sequence(array $responses = []) * @method static bool preventingStrayRequests() * @method static \Illuminate\Http\Client\Factory allowStrayRequests() * @method static \Illuminate\Http\Client\Factory record() * @method static void recordRequestResponsePair(\Illuminate\Http\Client\Request $request, \Illuminate\Http\Client\Response|null $response) - * @method static void assertSent(callable $callback) + * @method static void assertSent(callable|\Closure $callback) * @method static void assertSentInOrder(array $callbacks) - * @method static void assertNotSent(callable $callback) + * @method static void assertNotSent(callable|\Closure $callback) * @method static void assertNothingSent() * @method static void assertSentCount(int $count) * @method static void assertSequencesAreEmpty() - * @method static \Illuminate\Support\Collection recorded(callable $callback = null) + * @method static \Illuminate\Support\Collection recorded(\Closure|callable $callback = null) * @method static \Illuminate\Http\Client\PendingRequest createPendingRequest() * @method static \Illuminate\Contracts\Events\Dispatcher|null getDispatcher() * @method static array getGlobalMiddleware() From e72d741a49aea708d70447838db9e48a837ac1ca Mon Sep 17 00:00:00 2001 From: Moshe Brodsky <44633930+moshe-autoleadstar@users.noreply.github.com> Date: Tue, 13 May 2025 16:49:36 +0300 Subject: [PATCH 141/158] Add deleteWhen for throttle exceptions job middleware (#55718) * Add deleteWhen for throttle exceptions job middleware * adding for redis and adding a test * attempt to fix test * add expectation for isReleased check * ok so its called twice * change terminology from delete to skip * formatting --------- Co-authored-by: Taylor Otwell --- .../Queue/Middleware/ThrottlesExceptions.php | 43 ++++++++++++++++++ .../ThrottlesExceptionsWithRedis.php | 4 ++ .../Queue/ThrottlesExceptionsTest.php | 45 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php index 68017795655c..5c5c7d04a7ed 100644 --- a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php +++ b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php @@ -57,6 +57,13 @@ class ThrottlesExceptions */ protected $whenCallback; + /** + * The callbacks that determine if the job should be deleted. + * + * @var callable[] + */ + protected array $deleteWhenCallbacks = []; + /** * The prefix of the rate limiter key. * @@ -111,6 +118,10 @@ public function handle($job, $next) report($throwable); } + if ($this->shouldDelete($throwable)) { + return $job->delete(); + } + $this->limiter->hit($jobKey, $this->decaySeconds); return $job->release($this->retryAfterMinutes * 60); @@ -130,6 +141,38 @@ public function when(callable $callback) return $this; } + /** + * Add a callback that should determine if the job should be deleted. + * + * @param callable|string $callback + * @return $this + */ + public function deleteWhen(callable|string $callback) + { + $this->deleteWhenCallbacks[] = is_string($callback) + ? fn (Throwable $e) => $e instanceof $callback + : $callback; + + return $this; + } + + /** + * Run the skip / delete callbacks to determine if the job should be deleted for the given exception. + * + * @param Throwable $throwable + * @return bool + */ + protected function shouldDelete(Throwable $throwable): bool + { + foreach ($this->deleteWhenCallbacks as $callback) { + if (call_user_func($callback, $throwable)) { + return true; + } + } + + return false; + } + /** * Set the prefix of the rate limiter key. * diff --git a/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php b/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php index e5b79a7d67ed..8c6b78912b0d 100644 --- a/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php +++ b/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php @@ -58,6 +58,10 @@ public function handle($job, $next) report($throwable); } + if ($this->shouldDelete($throwable)) { + return $job->delete(); + } + $this->limiter->acquire(); return $job->release($this->retryAfterMinutes * 60); diff --git a/tests/Integration/Queue/ThrottlesExceptionsTest.php b/tests/Integration/Queue/ThrottlesExceptionsTest.php index e559c41b7f8f..19f525afcc8d 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsTest.php @@ -40,6 +40,11 @@ public function testCircuitResetsAfterSuccess() $this->assertJobWasReleasedWithDelay(CircuitBreakerTestJob::class); } + public function testCircuitCanSkipJob() + { + $this->assertJobWasDeleted(CircuitBreakerSkipJob::class); + } + protected function assertJobWasReleasedImmediately($class) { $class::$handled = false; @@ -82,6 +87,27 @@ protected function assertJobWasReleasedWithDelay($class) $this->assertFalse($class::$handled); } + protected function assertJobWasDeleted($class) + { + $class::$handled = false; + $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app); + + $job = m::mock(Job::class); + + $job->shouldReceive('hasFailed')->once()->andReturn(false); + $job->shouldReceive('delete')->once(); + $job->shouldReceive('isDeleted')->andReturn(true); + $job->shouldReceive('isReleased')->twice()->andReturn(false); + $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true); + $job->shouldReceive('uuid')->andReturn('simple-test-uuid'); + + $instance->call($job, [ + 'command' => serialize($command = new $class), + ]); + + $this->assertTrue($class::$handled); + } + protected function assertJobRanSuccessfully($class) { $class::$handled = false; @@ -314,6 +340,25 @@ public function middleware() } } +class CircuitBreakerSkipJob +{ + use InteractsWithQueue, Queueable; + + public static $handled = false; + + public function handle() + { + static::$handled = true; + + throw new Exception; + } + + public function middleware() + { + return [(new ThrottlesExceptions(2, 10 * 60))->deleteWhen(Exception::class)]; + } +} + class CircuitBreakerSuccessfulJob { use InteractsWithQueue, Queueable; From d0a0132a3c84c3c7d5737214dd824dd8fff3cf16 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 13 May 2025 14:27:37 +0000 Subject: [PATCH 142/158] Update version to v12.14.0 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 91897ea0dd52..b9faeb152595 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.13.0'; + const VERSION = '12.14.0'; /** * The base path for the Laravel installation. From 18b75334ab08d6f26c7694cdf16de4ea36eda7e4 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 13 May 2025 14:29:18 +0000 Subject: [PATCH 143/158] Update CHANGELOG --- CHANGELOG.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0198d02e6505..6102e3295697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.13.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.14.0...12.x) + +## [v12.14.0](https://github.com/laravel/framework/compare/v12.13.0...v12.14.0) - 2025-05-13 + +* [12.x] Support `useCurrent` on date and year column types by [@nicholasbrantley](https://github.com/nicholasbrantley) in https://github.com/laravel/framework/pull/55619 +* [12.x] Update "Number::fileSize" to use correct prefix and add prefix param by [@Boy132](https://github.com/Boy132) in https://github.com/laravel/framework/pull/55678 +* [12.x] Update PHPDoc for whereRaw to allow Expression as $sql by [@mitoop](https://github.com/mitoop) in https://github.com/laravel/framework/pull/55674 +* Revert "[12.x] Make Blueprint Resolver Statically" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55690 +* [12.x] Support Virtual Properties When Serializing Models by [@beschoenen](https://github.com/beschoenen) in https://github.com/laravel/framework/pull/55691 +* [12.X] Fix `Http::preventStrayRequests` error propagation when using `Http::pool` by [@LeTamanoir](https://github.com/LeTamanoir) in https://github.com/laravel/framework/pull/55689 +* [12.x] incorrect use of generics in Schema\Builder by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55687 +* [12.x] Add option to disable MySQL ssl when restoring or squashing migrations by [@andersonls](https://github.com/andersonls) in https://github.com/laravel/framework/pull/55683 +* [12.x] Add `except` and `exceptHidden` methods to `Context` class by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/55692 +* [12.x] Container `currentlyResolving` utility by [@jrseliga](https://github.com/jrseliga) in https://github.com/laravel/framework/pull/55684 +* [12.x] Container `currentlyResolving` test by [@jrseliga](https://github.com/jrseliga) in https://github.com/laravel/framework/pull/55694 +* [12.x] Fix handling of default values for route parameters with a binding field by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/55697 +* Move Timebox for Authentication and add to password resets by [@valorin](https://github.com/valorin) in https://github.com/laravel/framework/pull/55701 +* [12.x] perf: Optimize BladeCompiler by [@rzv-me](https://github.com/rzv-me) in https://github.com/laravel/framework/pull/55703 +* [12.x] perf: support iterables for event discovery paths by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/55699 +* [12.x] Types: AuthorizesRequests::resourceAbilityMap by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/55706 +* [12.x] Add flexible support to memoized cache store by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55709 +* [12.x] Introduce Arr::from() by [@daniser](https://github.com/daniser) in https://github.com/laravel/framework/pull/55715 +* [12.x] Fix the `getCurrentlyAttachedPivots` wrong `morphClass` for morph to many relationships by [@amir9480](https://github.com/amir9480) in https://github.com/laravel/framework/pull/55721 +* [12.x] Improve typehints for Http classes by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54783 +* Add deleteWhen for throttle exceptions job middleware by [@moshe-autoleadstar](https://github.com/moshe-autoleadstar) in https://github.com/laravel/framework/pull/55718 ## [v12.13.0](https://github.com/laravel/framework/compare/v12.12.0...v12.13.0) - 2025-05-07 From e134fe93db9ac415cbd342f897366086dab53635 Mon Sep 17 00:00:00 2001 From: Tony Lea Date: Tue, 13 May 2025 13:49:48 -0400 Subject: [PATCH 144/158] Easily implement broadcasting in a React/Vue Typescript app (Starter Kits) (#55170) * Adding initial trial for the framework specific echo lib * few more updates * Adding functionality for vue composable * updating the react hook with updated config options * Adding the configure code injection step * Getting styleCI to pass * removing the useEcho stubs, instead will be added to laravel-echo npm package * fix spacing * fix spacing * fix spacing * making methods more efficient * making methods more efficient * updates to utilize the new packages * Update BroadcastingInstallCommand.php * better value detection for .env * Update BroadcastingInstallCommand.php * Update BroadcastingInstallCommand.php * Update BroadcastingInstallCommand.php * Update BroadcastingInstallCommand.php * Update BroadcastingInstallCommand.php * formatting * Update BroadcastingInstallCommand.php * formatting * writeVariable(s) env helpers * handle blank values cleanly * use the env variable writer * unhandle match case * no need to ask for public key * warn about pusher protocol support * move the ably warning up so that it's visible longer * enable * driver specific stubs * hopefully fix line endings --------- Co-authored-by: Joe Tannenbaum Co-authored-by: Taylor Otwell --- .../Console/BroadcastingInstallCommand.php | 327 +++++++++++++++++- .../Console/stubs/echo-js-ably.stub | 13 + .../Console/stubs/echo-js-pusher.stub | 15 + .../{echo-js.stub => echo-js-reverb.stub} | 0 src/Illuminate/Support/Env.php | 129 +++++++ tests/Support/SupportHelpersTest.php | 250 ++++++++++++- 6 files changed, 711 insertions(+), 23 deletions(-) create mode 100644 src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub rename src/Illuminate/Foundation/Console/stubs/{echo-js.stub => echo-js-reverb.stub} (100%) diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php index b23689671d18..7b2e43c0c5be 100644 --- a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -5,12 +5,16 @@ use Composer\InstalledVersions; use Illuminate\Console\Command; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Env; use Illuminate\Support\Facades\Process; use Symfony\Component\Console\Attribute\AsCommand; use function Illuminate\Support\artisan_binary; use function Illuminate\Support\php_binary; use function Laravel\Prompts\confirm; +use function Laravel\Prompts\password; +use function Laravel\Prompts\select; +use function Laravel\Prompts\text; #[AsCommand(name: 'install:broadcasting')] class BroadcastingInstallCommand extends Command @@ -26,6 +30,9 @@ class BroadcastingInstallCommand extends Command {--composer=global : Absolute path to the Composer binary which should be used to install packages} {--force : Overwrite any existing broadcasting routes file} {--without-reverb : Do not prompt to install Laravel Reverb} + {--reverb : Install Laravel Reverb as the default broadcaster} + {--pusher : Install Pusher as the default broadcaster} + {--ably : Install Ably as the default broadcaster} {--without-node : Do not prompt to install Node dependencies}'; /** @@ -35,6 +42,23 @@ class BroadcastingInstallCommand extends Command */ protected $description = 'Create a broadcasting channel routes file'; + /** + * The broadcasting driver to use. + * + * @var string|null + */ + protected $driver = null; + + /** + * The framework packages to install. + * + * @var array + */ + protected $frameworkPackages = [ + 'react' => '@laravel/echo-react', + 'vue' => '@laravel/echo-vue', + ]; + /** * Execute the console command. * @@ -54,25 +78,44 @@ public function handle() $this->uncommentChannelsRoutesFile(); $this->enableBroadcastServiceProvider(); - // Install bootstrapping... - if (! file_exists($echoScriptPath = $this->laravel->resourcePath('js/echo.js'))) { - if (! is_dir($directory = $this->laravel->resourcePath('js'))) { - mkdir($directory, 0755, true); - } + $this->driver = $this->resolveDriver(); - copy(__DIR__.'/stubs/echo-js.stub', $echoScriptPath); - } + Env::writeVariable('BROADCAST_CONNECTION', $this->driver, $this->laravel->basePath('.env'), true); - if (file_exists($bootstrapScriptPath = $this->laravel->resourcePath('js/bootstrap.js'))) { - $bootstrapScript = file_get_contents( - $bootstrapScriptPath - ); + $this->collectDriverConfig(); + $this->installDriverPackages(); + + if ($this->isUsingSupportedFramework()) { + // If this is a supported framework, we will use the framework-specific Echo helpers... + $this->injectFrameworkSpecificConfiguration(); + } else { + // Standard JavaScript implementation... + if (! file_exists($echoScriptPath = $this->laravel->resourcePath('js/echo.js'))) { + if (! is_dir($directory = $this->laravel->resourcePath('js'))) { + mkdir($directory, 0755, true); + } + + $stubPath = __DIR__.'/stubs/echo-js-'.$this->driver.'.stub'; + + if (! file_exists($stubPath)) { + $stubPath = __DIR__.'/stubs/echo-js-reverb.stub'; + } + + copy($stubPath, $echoScriptPath); + } - if (! str_contains($bootstrapScript, './echo')) { - file_put_contents( - $bootstrapScriptPath, - trim($bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub')).PHP_EOL, + // Only add the bootstrap import for the standard JS implementation... + if (file_exists($bootstrapScriptPath = $this->laravel->resourcePath('js/bootstrap.js'))) { + $bootstrapScript = file_get_contents( + $bootstrapScriptPath ); + + if (! str_contains($bootstrapScript, './echo')) { + file_put_contents( + $bootstrapScriptPath, + trim($bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub')).PHP_EOL, + ); + } } } @@ -118,8 +161,10 @@ protected function enableBroadcastServiceProvider() { $filesystem = new Filesystem; - if (! $filesystem->exists(app()->configPath('app.php')) || - ! $filesystem->exists('app/Providers/BroadcastServiceProvider.php')) { + if ( + ! $filesystem->exists(app()->configPath('app.php')) || + ! $filesystem->exists('app/Providers/BroadcastServiceProvider.php') + ) { return; } @@ -134,6 +179,171 @@ protected function enableBroadcastServiceProvider() } } + /** + * Collect the driver configuration. + * + * @return void + */ + protected function collectDriverConfig() + { + $envPath = $this->laravel->basePath('.env'); + + if (! file_exists($envPath)) { + return; + } + + match ($this->driver) { + 'pusher' => $this->collectPusherConfig(), + 'ably' => $this->collectAblyConfig(), + default => null, + }; + } + + /** + * Install the driver packages. + * + * @return void + */ + protected function installDriverPackages() + { + $package = match ($this->driver) { + 'pusher' => 'pusher/pusher-php-server', + 'ably' => 'ably/ably-php', + default => null, + }; + + if (! $package || InstalledVersions::isInstalled($package)) { + return; + } + + $this->requireComposerPackages($this->option('composer'), [$package]); + } + + /** + * Collect the Pusher configuration. + * + * @return void + */ + protected function collectPusherConfig() + { + $appId = text('Pusher App ID', 'Enter your Pusher app ID'); + $key = password('Pusher App Key', 'Enter your Pusher app key'); + $secret = password('Pusher App Secret', 'Enter your Pusher app secret'); + + $cluster = select('Pusher App Cluster', [ + 'mt1', + 'us2', + 'us3', + 'eu', + 'ap1', + 'ap2', + 'ap3', + 'ap4', + 'sa1', + ]); + + Env::writeVariables([ + 'PUSHER_APP_ID' => $appId, + 'PUSHER_APP_KEY' => $key, + 'PUSHER_APP_SECRET' => $secret, + 'PUSHER_APP_CLUSTER' => $cluster, + 'PUSHER_PORT' => 443, + 'PUSHER_SCHEME' => 'https', + 'VITE_PUSHER_APP_KEY' => '${PUSHER_APP_KEY}', + 'VITE_PUSHER_APP_CLUSTER' => '${PUSHER_APP_CLUSTER}', + 'VITE_PUSHER_HOST' => '${PUSHER_HOST}', + 'VITE_PUSHER_PORT' => '${PUSHER_PORT}', + 'VITE_PUSHER_SCHEME' => '${PUSHER_SCHEME}', + ], $this->laravel->basePath('.env')); + } + + /** + * Collect the Ably configuration. + * + * @return void + */ + protected function collectAblyConfig() + { + $this->components->warn('Make sure to enable "Pusher protocol support" in your Ably app settings.'); + + $key = password('Ably Key', 'Enter your Ably key'); + + $publicKey = explode(':', $key)[0] ?? $key; + + Env::writeVariables([ + 'ABLY_KEY' => $key, + 'ABLY_PUBLIC_KEY' => $publicKey, + 'VITE_ABLY_PUBLIC_KEY' => '${ABLY_PUBLIC_KEY}', + ], $this->laravel->basePath('.env')); + } + + /** + * Inject Echo configuration into the application's main file. + * + * @return void + */ + protected function injectFrameworkSpecificConfiguration() + { + if ($this->appUsesVue()) { + $importPath = $this->frameworkPackages['vue']; + + $filePaths = [ + $this->laravel->resourcePath('js/app.ts'), + $this->laravel->resourcePath('js/app.js'), + ]; + } else { + $importPath = $this->frameworkPackages['react']; + + $filePaths = [ + $this->laravel->resourcePath('js/app.tsx'), + $this->laravel->resourcePath('js/app.jsx'), + ]; + } + + $filePath = array_filter($filePaths, function ($path) { + return file_exists($path); + })[0] ?? null; + + if (! $filePath) { + $this->components->warn("Could not find file [{$filePaths[0]}]. Skipping automatic Echo configuration."); + + return; + } + + $contents = file_get_contents($filePath); + + $echoCode = <<driver}', + }); + JS; + + preg_match_all('/^import .+;$/m', $contents, $matches); + + if (empty($matches[0])) { + // Add the Echo configuration to the top of the file if no import statements are found... + $newContents = $echoCode.PHP_EOL.$contents; + + file_put_contents($filePath, $newContents); + } else { + // Add Echo configuration after the last import... + $lastImport = end($matches[0]); + + $positionOfLastImport = strrpos($contents, $lastImport); + + if ($positionOfLastImport !== false) { + $insertPosition = $positionOfLastImport + strlen($lastImport); + $newContents = substr($contents, 0, $insertPosition).PHP_EOL.$echoCode.substr($contents, $insertPosition); + + file_put_contents($filePath, $newContents); + } + } + + $this->components->info('Echo configuration added to ['.basename($filePath).'].'); + } + /** * Install Laravel Reverb into the application if desired. * @@ -141,7 +351,7 @@ protected function enableBroadcastServiceProvider() */ protected function installReverb() { - if ($this->option('without-reverb') || InstalledVersions::isInstalled('laravel/reverb')) { + if ($this->driver !== 'reverb' || $this->option('without-reverb') || InstalledVersions::isInstalled('laravel/reverb')) { return; } @@ -199,6 +409,12 @@ protected function installNodeDependencies() ]; } + if ($this->appUsesVue()) { + $commands[0] .= ' '.$this->frameworkPackages['vue']; + } elseif ($this->appUsesReact()) { + $commands[0] .= ' '.$this->frameworkPackages['react']; + } + $command = Process::command(implode(' && ', $commands)) ->path(base_path()); @@ -212,4 +428,79 @@ protected function installNodeDependencies() $this->components->info('Node dependencies installed successfully.'); } } + + /** + * Resolve the provider to use based on the user's choice. + * + * @return string + */ + protected function resolveDriver(): string + { + if ($this->option('reverb')) { + return 'reverb'; + } + + if ($this->option('pusher')) { + return 'pusher'; + } + + if ($this->option('ably')) { + return 'ably'; + } + + return select('Which broadcasting driver would you like to use?', [ + 'reverb' => 'Laravel Reverb', + 'pusher' => 'Pusher', + 'ably' => 'Ably', + ]); + } + + /** + * Detect if the user is using a supported framework (React or Vue). + * + * @return bool + */ + protected function isUsingSupportedFramework(): bool + { + return $this->appUsesReact() || $this->appUsesVue(); + } + + /** + * Detect if the user is using React. + * + * @return bool + */ + protected function appUsesReact(): bool + { + return $this->packageDependenciesInclude('react'); + } + + /** + * Detect if the user is using Vue. + * + * @return bool + */ + protected function appUsesVue(): bool + { + return $this->packageDependenciesInclude('vue'); + } + + /** + * Detect if the package is installed. + * + * @return bool + */ + protected function packageDependenciesInclude(string $package): bool + { + $packageJsonPath = $this->laravel->basePath('package.json'); + + if (! file_exists($packageJsonPath)) { + return false; + } + + $packageJson = json_decode(file_get_contents($packageJsonPath), true); + + return isset($packageJson['dependencies'][$package]) || + isset($packageJson['devDependencies'][$package]); + } } diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub new file mode 100644 index 000000000000..ec518d214668 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub @@ -0,0 +1,13 @@ +import Echo from 'laravel-echo'; + +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: "pusher", + key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + wsHost: "realtime-pusher.ably.io", + wsPort: 443, + disableStats: true, + encrypted: true, +}); diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub new file mode 100644 index 000000000000..5a8a7f7e31ef --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub @@ -0,0 +1,15 @@ +import Echo from 'laravel-echo'; + +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: "pusher", + key: import.meta.env.VITE_PUSHER_APP_KEY, + cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + forceTLS: true, + wsHost: import.meta.env.VITE_PUSHER_HOST, + wsPort: import.meta.env.VITE_PUSHER_PORT, + wssPort: import.meta.env.VITE_PUSHER_PORT, + enabledTransports: ["ws", "wss"], +}); diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-reverb.stub similarity index 100% rename from src/Illuminate/Foundation/Console/stubs/echo-js.stub rename to src/Illuminate/Foundation/Console/stubs/echo-js-reverb.stub diff --git a/src/Illuminate/Support/Env.php b/src/Illuminate/Support/Env.php index 702f61d44f4c..a52c10bd4f2a 100644 --- a/src/Illuminate/Support/Env.php +++ b/src/Illuminate/Support/Env.php @@ -5,6 +5,8 @@ use Closure; use Dotenv\Repository\Adapter\PutenvAdapter; use Dotenv\Repository\RepositoryBuilder; +use Illuminate\Contracts\Filesystem\FileNotFoundException; +use Illuminate\Filesystem\Filesystem; use PhpOption\Option; use RuntimeException; @@ -114,6 +116,133 @@ public static function getOrFail($key) return self::getOption($key)->getOrThrow(new RuntimeException("Environment variable [$key] has no value.")); } + /** + * Write an array of key-value pairs to the environment file. + * + * @param array $variables + * @param string $pathToFile + * @param bool $overwrite + * @return void + * + * @throws RuntimeException + * @throws FileNotFoundException + */ + public static function writeVariables(array $variables, string $pathToFile, bool $overwrite = false): void + { + $filesystem = new Filesystem; + + if ($filesystem->missing($pathToFile)) { + throw new RuntimeException("The file [{$pathToFile}] does not exist."); + } + + $lines = explode(PHP_EOL, $filesystem->get($pathToFile)); + + foreach ($variables as $key => $value) { + $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite); + } + + $filesystem->put($pathToFile, implode(PHP_EOL, $lines)); + } + + /** + * Write a single key-value pair to the environment file. + * + * @param string $key + * @param mixed $value + * @param string $pathToFile + * @param bool $overwrite + * @return void + * + * @throws RuntimeException + * @throws FileNotFoundException + */ + public static function writeVariable(string $key, mixed $value, string $pathToFile, bool $overwrite = false): void + { + $filesystem = new Filesystem; + + if ($filesystem->missing($pathToFile)) { + throw new RuntimeException("The file [{$pathToFile}] does not exist."); + } + + $envContent = $filesystem->get($pathToFile); + + $lines = explode(PHP_EOL, $envContent); + $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite); + + $filesystem->put($pathToFile, implode(PHP_EOL, $lines)); + } + + /** + * Add a variable to the environment file contents. + * + * @param string $key + * @param mixed $value + * @param array $envLines + * @param bool $overwrite + * @return array + */ + protected static function addVariableToEnvContents(string $key, mixed $value, array $envLines, bool $overwrite): array + { + $prefix = explode('_', $key)[0].'_'; + $lastPrefixIndex = -1; + + $shouldQuote = preg_match('/^[a-zA-z0-9]+$/', $value) === 0; + + $lineToAddVariations = [ + $key.'='.(is_string($value) ? '"'.addslashes($value).'"' : $value), + $key.'='.(is_string($value) ? "'".addslashes($value)."'" : $value), + $key.'='.$value, + ]; + + $lineToAdd = $shouldQuote ? $lineToAddVariations[0] : $lineToAddVariations[2]; + + if ($value === '') { + $lineToAdd = $key.'='; + } + + foreach ($envLines as $index => $line) { + if (str_starts_with($line, $prefix)) { + $lastPrefixIndex = $index; + } + + if (in_array($line, $lineToAddVariations)) { + // This exact line already exists, so we don't need to add it again. + return $envLines; + } + + if ($line === $key.'=') { + // If the value is empty, we can replace it with the new value. + $envLines[$index] = $lineToAdd; + + return $envLines; + } + + if (str_starts_with($line, $key.'=')) { + if (! $overwrite) { + return $envLines; + } + + $envLines[$index] = $lineToAdd; + + return $envLines; + } + } + + if ($lastPrefixIndex === -1) { + if (count($envLines) && $envLines[count($envLines) - 1] !== '') { + $envLines[] = ''; + } + + return array_merge($envLines, [$lineToAdd]); + } + + return array_merge( + array_slice($envLines, 0, $lastPrefixIndex + 1), + [$lineToAdd], + array_slice($envLines, $lastPrefixIndex + 1) + ); + } + /** * Get the possible option for this environment variable. * diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index ee67f818a3cd..1074e97dd6d9 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -8,6 +8,7 @@ use Error; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Env; use Illuminate\Support\Optional; use Illuminate\Support\Sleep; @@ -26,10 +27,21 @@ class SupportHelpersTest extends TestCase { + protected function setUp(): void + { + mkdir(__DIR__.'/tmp'); + + parent::setUp(); + } + protected function tearDown(): void { m::close(); + if (is_dir(__DIR__.'/tmp')) { + (new Filesystem)->deleteDirectory(__DIR__.'/tmp'); + } + parent::tearDown(); } @@ -747,11 +759,13 @@ class_uses_recursive(SupportTestClassThree::class) public function testTraitUsesRecursive() { - $this->assertSame([ - 'Illuminate\Tests\Support\SupportTestTraitTwo' => 'Illuminate\Tests\Support\SupportTestTraitTwo', - 'Illuminate\Tests\Support\SupportTestTraitOne' => 'Illuminate\Tests\Support\SupportTestTraitOne', - ], - trait_uses_recursive(SupportTestClassOne::class)); + $this->assertSame( + [ + 'Illuminate\Tests\Support\SupportTestTraitTwo' => 'Illuminate\Tests\Support\SupportTestTraitTwo', + 'Illuminate\Tests\Support\SupportTestTraitOne' => 'Illuminate\Tests\Support\SupportTestTraitOne', + ], + trait_uses_recursive(SupportTestClassOne::class) + ); $this->assertSame([], trait_uses_recursive(SupportTestClassTwo::class)); } @@ -1212,6 +1226,232 @@ public function testEnvEscapedString() $this->assertSame('x"null"x', env('foo')); } + public function testWriteArrayOfEnvVariablesToFile() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + 'DB_PORT' => 3306, + 'BRAND_NEW_PREFIX' => 'fresh value', + ], $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST="127:0:0:1"', + 'DB_PORT=3306', + '', + 'BRAND_NEW_PREFIX="fresh value"', + ]), + $filesystem->get($path) + ); + } + + public function testWriteArrayOfEnvVariablesToFileAndOverwrite() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + 'DB_CONNECTION' => 'sqlite', + ], $path, true); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=sqlite', + 'DB_HOST="127:0:0:1"', + ]), + $filesystem->get($path) + ); + } + + public function testWillNotOverwriteArrayOfVariables() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + ], $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST="127:0:0:1"', + ]), + $filesystem->get($path) + ); + } + + public function testWriteVariableToFile() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWillNotOverwriteVariable() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWriteVariableToFileAndOverwrite() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path, true); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWillThrowAnExceptionIfFileIsMissingWhenTryingToWriteVariables(): void + { + $this->expectExceptionObject(new RuntimeException('The file [missing-file] does not exist.')); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + ], 'missing-file'); + } + public function testGetFromSERVERFirst() { $_ENV['foo'] = 'From $_ENV'; From 84b142958d1638a7e89de94ce75c2821c601d3d7 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 13 May 2025 17:50:51 +0000 Subject: [PATCH 145/158] Update version to v12.14.1 --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index b9faeb152595..243144794d59 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.14.0'; + const VERSION = '12.14.1'; /** * The base path for the Laravel installation. From a70c6f8c4466a0dcfcbbe2d6be0b1f0447a6548f Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 13 May 2025 17:52:31 +0000 Subject: [PATCH 146/158] Update CHANGELOG --- CHANGELOG.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6102e3295697..6224987946d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.14.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.14.1...12.x) + +## [v12.14.1](https://github.com/laravel/framework/compare/v12.14.0...v12.14.1) - 2025-05-13 + +* [10.x] Refine error messages for detecting lost connections (Debian bookworm compatibility) by [@mfn](https://github.com/mfn) in https://github.com/laravel/framework/pull/53794 +* [10.x] Bump minimum `league/commonmark` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53829 +* [10.x] Backport 11.x PHP 8.4 fix for str_getcsv deprecation by [@aka-tpayne](https://github.com/aka-tpayne) in https://github.com/laravel/framework/pull/54074 +* [10.x] Fix attribute name used on `Validator` instance within certain rule classes by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54943 +* Add `Illuminate\Support\EncodedHtmlString` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54737 +* [11.x] Fix missing `return $this` for `assertOnlyJsonValidationErrors` by [@LeTamanoir](https://github.com/LeTamanoir) in https://github.com/laravel/framework/pull/55099 +* [11.x] Fix `Illuminate\Support\EncodedHtmlString` from causing breaking change by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55149 +* [11.x] Respect custom path for cached views by the `AboutCommand` by [@alies-dev](https://github.com/alies-dev) in https://github.com/laravel/framework/pull/55179 +* [11.x] Include all invisible characters in Str::trim by [@laserhybiz](https://github.com/laserhybiz) in https://github.com/laravel/framework/pull/54281 +* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55302 +* [11.x] Remove incorrect syntax from mail's `message` template by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55530 +* [11.x] Allows to toggle markdown email encoding by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55539 +* [11.x] Fix `EncodedHtmlString` to ignore instance of `HtmlString` by [@jbraband](https://github.com/jbraband) in https://github.com/laravel/framework/pull/55543 +* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55549 +* [11.x] Install Passport 13.x by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/55621 +* [11.x] Bump minimum league/commonmark by [@andrextor](https://github.com/andrextor) in https://github.com/laravel/framework/pull/55660 +* Backporting Timebox fixes to 11.x by [@valorin](https://github.com/valorin) in https://github.com/laravel/framework/pull/55705 +* Test SQLServer 2017 on Ubuntu 22.04 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55716 +* [11.x] Fix Symfony 7.3 deprecations by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55711 +* Easily implement broadcasting in a React/Vue Typescript app (Starter Kits) by [@tnylea](https://github.com/tnylea) in https://github.com/laravel/framework/pull/55170 ## [v12.14.0](https://github.com/laravel/framework/compare/v12.13.0...v12.14.0) - 2025-05-13 From 5c146fbb614586d3ca6359c21315d398d0121278 Mon Sep 17 00:00:00 2001 From: Mbungu Ngoma Date: Tue, 13 May 2025 23:27:17 +0100 Subject: [PATCH 147/158] feat(Support): Add number parsing methods to Number class (#55725) Add three new methods to the Number class for parsing numeric strings: - parse(): Parse string to number based on format type - parseInt(): Parse string to integer with locale support - parseFloat(): Parse string to float with locale support These methods leverage the PHP Intl extension's NumberFormatter to provide locale-aware number parsing capabilities. --- src/Illuminate/Support/Number.php | 41 +++++++++++++++++++++++++++++ tests/Support/SupportNumberTest.php | 35 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index 7ebf7916cb32..665ac99c2989 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -48,6 +48,47 @@ public static function format(int|float $number, ?int $precision = null, ?int $m return $formatter->format($number); } + /** + * Parse the given string according to the specified format type. + * + * @param string $string + * @param int|null $type + * @param string|null $locale + * @return int|float|false + */ + public static function parse(string $string, ?int $type = NumberFormatter::TYPE_DOUBLE, ?string $locale = null): int|float + { + static::ensureIntlExtensionIsInstalled(); + + $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::DECIMAL); + + return $formatter->parse($string, $type); + } + + /** + * Parse a string into an integer according to the specified locale. + * + * @param string $string + * @param string|null $locale + * @return int|false + */ + public static function parseInt(string $string, ?string $locale = null): int + { + return self::parse($string, NumberFormatter::TYPE_INT32, $locale); + } + + /** + * Parse a string into a float according to the specified locale. + * + * @param string $string The string to parse + * @param string|null $locale The locale to use + * @return float|false + */ + public static function parseFloat(string $string, ?string $locale = null ): float + { + return self::parse($string, NumberFormatter::TYPE_DOUBLE, $locale); + } + /** * Spell out the given number in the given locale. * diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index 8d7ba346cf99..4946885d3289 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -355,4 +355,39 @@ public function testTrim() $this->assertSame(12.3456789, Number::trim(12.3456789)); $this->assertSame(12.3456789, Number::trim(12.34567890000)); } + + #[RequiresPhpExtension('intl')] + public function testParse() + { + $this->assertSame(1234.0, Number::parse('1,234')); + $this->assertSame(1234.5, Number::parse('1,234.5')); + $this->assertSame(1234.56, Number::parse('1,234.56')); + $this->assertSame(-1234.56, Number::parse('-1,234.56')); + + $this->assertSame(1234.56, Number::parse('1.234,56', locale: 'de')); + $this->assertSame(1234.56, Number::parse('1 234,56', locale: 'fr')); + } + + #[RequiresPhpExtension('intl')] + public function testParseInt() + { + $this->assertSame(1234, Number::parseInt('1,234')); + $this->assertSame(1234, Number::parseInt('1,234.5')); + $this->assertSame(-1234, Number::parseInt('-1,234.56')); + + $this->assertSame(1234, Number::parseInt('1.234', locale: 'de')); + $this->assertSame(1234, Number::parseInt('1 234', locale: 'fr')); + } + + #[RequiresPhpExtension('intl')] + public function testParseFloat() + { + $this->assertSame(1234.0, Number::parseFloat('1,234')); + $this->assertSame(1234.5, Number::parseFloat('1,234.5')); + $this->assertSame(1234.56, Number::parseFloat('1,234.56')); + $this->assertSame(-1234.56, Number::parseFloat('-1,234.56')); + + $this->assertSame(1234.56, Number::parseFloat('1.234,56', locale: 'de')); + $this->assertSame(1234.56, Number::parseFloat('1 234,56', locale: 'fr')); + } } From 53687e4c2c33d8686705aa639f3eb2049470767e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 13 May 2025 22:27:38 +0000 Subject: [PATCH 148/158] Apply fixes from StyleCI --- src/Illuminate/Support/Number.php | 2 +- tests/Support/SupportNumberTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index 665ac99c2989..a14e948ecb52 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -84,7 +84,7 @@ public static function parseInt(string $string, ?string $locale = null): int * @param string|null $locale The locale to use * @return float|false */ - public static function parseFloat(string $string, ?string $locale = null ): float + public static function parseFloat(string $string, ?string $locale = null): float { return self::parse($string, NumberFormatter::TYPE_DOUBLE, $locale); } diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index 4946885d3289..7b34e62c6439 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -363,7 +363,7 @@ public function testParse() $this->assertSame(1234.5, Number::parse('1,234.5')); $this->assertSame(1234.56, Number::parse('1,234.56')); $this->assertSame(-1234.56, Number::parse('-1,234.56')); - + $this->assertSame(1234.56, Number::parse('1.234,56', locale: 'de')); $this->assertSame(1234.56, Number::parse('1 234,56', locale: 'fr')); } @@ -374,7 +374,7 @@ public function testParseInt() $this->assertSame(1234, Number::parseInt('1,234')); $this->assertSame(1234, Number::parseInt('1,234.5')); $this->assertSame(-1234, Number::parseInt('-1,234.56')); - + $this->assertSame(1234, Number::parseInt('1.234', locale: 'de')); $this->assertSame(1234, Number::parseInt('1 234', locale: 'fr')); } @@ -386,7 +386,7 @@ public function testParseFloat() $this->assertSame(1234.5, Number::parseFloat('1,234.5')); $this->assertSame(1234.56, Number::parseFloat('1,234.56')); $this->assertSame(-1234.56, Number::parseFloat('-1,234.56')); - + $this->assertSame(1234.56, Number::parseFloat('1.234,56', locale: 'de')); $this->assertSame(1234.56, Number::parseFloat('1 234,56', locale: 'fr')); } From fc0c66fa908e7a84ca033f78827cf035f1a8a040 Mon Sep 17 00:00:00 2001 From: Lucas <7912315+elbojoloco@users.noreply.github.com> Date: Wed, 14 May 2025 16:09:00 +0200 Subject: [PATCH 149/158] [12.x] Add a default option when retrieving an enum from data (#55735) * Add a default option when retrieving an enum from data * Update InteractsWithData.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Traits/InteractsWithData.php | 7 ++++--- tests/Http/HttpRequestTest.php | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Traits/InteractsWithData.php b/src/Illuminate/Support/Traits/InteractsWithData.php index bd39205d942d..5647570eb577 100644 --- a/src/Illuminate/Support/Traits/InteractsWithData.php +++ b/src/Illuminate/Support/Traits/InteractsWithData.php @@ -312,15 +312,16 @@ public function date($key, $format = null, $tz = null) * * @param string $key * @param class-string $enumClass + * @param TEnum|null $default * @return TEnum|null */ - public function enum($key, $enumClass) + public function enum($key, $enumClass, $default = null) { if ($this->isNotFilled($key) || ! $this->isBackedEnum($enumClass)) { - return null; + return value($default); } - return $enumClass::tryFrom($this->data($key)); + return $enumClass::tryFrom($this->data($key)) ?: value($default); } /** diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 249b3043691e..08ab80bc6d80 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -804,6 +804,9 @@ public function testEnumMethod() $this->assertNull($request->enum('doesnt_exist', TestEnumBacked::class)); + $this->assertEquals(TestEnumBacked::test, $request->enum('invalid_enum_value', TestEnumBacked::class, TestEnumBacked::test)); + $this->assertEquals(TestEnumBacked::test, $request->enum('missing_key', TestEnumBacked::class, TestEnumBacked::test)); + $this->assertEquals(TestEnumBacked::test, $request->enum('valid_enum_value', TestEnumBacked::class)); $this->assertNull($request->enum('invalid_enum_value', TestEnumBacked::class)); From df12a087731b37708cfbba72172a4c381363fed2 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Wed, 14 May 2025 14:09:28 +0000 Subject: [PATCH 150/158] Update facade docblocks --- src/Illuminate/Support/Facades/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 8200fcec7b34..f2dde82dc1c2 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -170,7 +170,7 @@ * @method static int integer(string $key, int $default = 0) * @method static float float(string $key, float $default = 0) * @method static \Illuminate\Support\Carbon|null date(string $key, string|null $format = null, string|null $tz = null) - * @method static \BackedEnum|null enum(string $key, string $enumClass) + * @method static \BackedEnum|null enum(string $key, string $enumClass, \BackedEnum|null $default = null) * @method static \BackedEnum[] enums(string $key, string $enumClass) * @method static array array(array|string|null $key = null) * @method static \Illuminate\Support\Collection collect(array|string|null $key = null) From fbb65d2645d4ab26b615e430caa9e50a4a943010 Mon Sep 17 00:00:00 2001 From: Jamie York Date: Thu, 15 May 2025 16:10:17 +0100 Subject: [PATCH 151/158] =?UTF-8?q?Revert=20"[12.x]=20Update=20"Number::fi?= =?UTF-8?q?leSize"=20to=20use=20correct=20prefix=20and=20add=20prefix?= =?UTF-8?q?=E2=80=A6"=20(#55741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 58ce619a249243772017899c266624bc646f1cff. --- src/Illuminate/Support/Number.php | 13 +++------ tests/Support/SupportNumberTest.php | 44 ++++++++--------------------- 2 files changed, 16 insertions(+), 41 deletions(-) diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index a14e948ecb52..f4a642f7df6c 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -201,19 +201,14 @@ public static function currency(int|float $number, string $in = '', ?string $loc * @param int|float $bytes * @param int $precision * @param int|null $maxPrecision - * @param bool $useBinaryPrefix * @return string */ - public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxPrecision = null, bool $useBinaryPrefix = false) + public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxPrecision = null) { - $base = $useBinaryPrefix ? 1024 : 1000; + $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - $units = $useBinaryPrefix - ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'RiB', 'QiB'] - : ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB']; - - for ($i = 0; ($bytes / $base) > 0.9 && ($i < count($units) - 1); $i++) { - $bytes /= $base; + for ($i = 0; ($bytes / 1024) > 0.9 && ($i < count($units) - 1); $i++) { + $bytes /= 1024; } return sprintf('%s %s', static::format($bytes, $precision, $maxPrecision), $units[$i]); diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index 7b34e62c6439..7f7de7f3f1a2 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -174,38 +174,18 @@ public function testBytesToHuman() $this->assertSame('0 B', Number::fileSize(0)); $this->assertSame('0.00 B', Number::fileSize(0, precision: 2)); $this->assertSame('1 B', Number::fileSize(1)); - $this->assertSame('1 KB', Number::fileSize(1000)); - $this->assertSame('2 KB', Number::fileSize(2000)); - $this->assertSame('2.00 KB', Number::fileSize(2000, precision: 2)); - $this->assertSame('1.23 KB', Number::fileSize(1234, precision: 2)); - $this->assertSame('1.234 KB', Number::fileSize(1234, maxPrecision: 3)); - $this->assertSame('1.234 KB', Number::fileSize(1234, 3)); - $this->assertSame('5 GB', Number::fileSize(1000 * 1000 * 1000 * 5)); - $this->assertSame('10 TB', Number::fileSize((1000 ** 4) * 10)); - $this->assertSame('10 PB', Number::fileSize((1000 ** 5) * 10)); - $this->assertSame('1 ZB', Number::fileSize(1000 ** 7)); - $this->assertSame('1 YB', Number::fileSize(1000 ** 8)); - $this->assertSame('1 RB', Number::fileSize(1000 ** 9)); - $this->assertSame('1 QB', Number::fileSize(1000 ** 10)); - $this->assertSame('1,000 QB', Number::fileSize(1000 ** 11)); - - $this->assertSame('0 B', Number::fileSize(0, useBinaryPrefix: true)); - $this->assertSame('0.00 B', Number::fileSize(0, precision: 2, useBinaryPrefix: true)); - $this->assertSame('1 B', Number::fileSize(1, useBinaryPrefix: true)); - $this->assertSame('1 KiB', Number::fileSize(1024, useBinaryPrefix: true)); - $this->assertSame('2 KiB', Number::fileSize(2048, useBinaryPrefix: true)); - $this->assertSame('2.00 KiB', Number::fileSize(2048, precision: 2, useBinaryPrefix: true)); - $this->assertSame('1.23 KiB', Number::fileSize(1264, precision: 2, useBinaryPrefix: true)); - $this->assertSame('1.234 KiB', Number::fileSize(1264.12345, maxPrecision: 3, useBinaryPrefix: true)); - $this->assertSame('1.234 KiB', Number::fileSize(1264, 3, useBinaryPrefix: true)); - $this->assertSame('5 GiB', Number::fileSize(1024 * 1024 * 1024 * 5, useBinaryPrefix: true)); - $this->assertSame('10 TiB', Number::fileSize((1024 ** 4) * 10, useBinaryPrefix: true)); - $this->assertSame('10 PiB', Number::fileSize((1024 ** 5) * 10, useBinaryPrefix: true)); - $this->assertSame('1 ZiB', Number::fileSize(1024 ** 7, useBinaryPrefix: true)); - $this->assertSame('1 YiB', Number::fileSize(1024 ** 8, useBinaryPrefix: true)); - $this->assertSame('1 RiB', Number::fileSize(1024 ** 9, useBinaryPrefix: true)); - $this->assertSame('1 QiB', Number::fileSize(1024 ** 10, useBinaryPrefix: true)); - $this->assertSame('1,024 QiB', Number::fileSize(1024 ** 11, useBinaryPrefix: true)); + $this->assertSame('1 KB', Number::fileSize(1024)); + $this->assertSame('2 KB', Number::fileSize(2048)); + $this->assertSame('2.00 KB', Number::fileSize(2048, precision: 2)); + $this->assertSame('1.23 KB', Number::fileSize(1264, precision: 2)); + $this->assertSame('1.234 KB', Number::fileSize(1264.12345, maxPrecision: 3)); + $this->assertSame('1.234 KB', Number::fileSize(1264, 3)); + $this->assertSame('5 GB', Number::fileSize(1024 * 1024 * 1024 * 5)); + $this->assertSame('10 TB', Number::fileSize((1024 ** 4) * 10)); + $this->assertSame('10 PB', Number::fileSize((1024 ** 5) * 10)); + $this->assertSame('1 ZB', Number::fileSize(1024 ** 7)); + $this->assertSame('1 YB', Number::fileSize(1024 ** 8)); + $this->assertSame('1,024 YB', Number::fileSize(1024 ** 9)); } public function testClamp() From 9b9ec01a9c2b9ed308c21d69032f4924829c8a50 Mon Sep 17 00:00:00 2001 From: Ahmed Alaa <92916738+AhmedAlaa4611@users.noreply.github.com> Date: Thu, 15 May 2025 21:21:22 +0300 Subject: [PATCH 152/158] remove apc (#55745) --- config/session.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/session.php b/config/session.php index ba0aa60b074b..b5fa53194479 100644 --- a/config/session.php +++ b/config/session.php @@ -13,8 +13,8 @@ | incoming requests. Laravel supports a variety of storage options to | persist session data. Database storage is a great default choice. | - | Supported: "file", "cookie", "database", "apc", - | "memcached", "redis", "dynamodb", "array" + | Supported: "file", "cookie", "database", "memcached", + | "redis", "dynamodb", "array" | */ @@ -97,7 +97,7 @@ | define the cache store which should be used to store the session data | between requests. This must match one of your defined cache stores. | - | Affects: "apc", "dynamodb", "memcached", "redis" + | Affects: "dynamodb", "memcached", "redis" | */ From f43f238f35dc469bf577a493a9daa1518025739c Mon Sep 17 00:00:00 2001 From: Milwad Khosravi <98118400+milwad-dev@users.noreply.github.com> Date: Thu, 15 May 2025 21:51:48 +0330 Subject: [PATCH 153/158] Update TestResponse.php (#55743) --- src/Illuminate/Testing/TestResponse.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 2f3d2ea32f45..0e9de3d5bcf6 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -906,7 +906,7 @@ public function assertJsonMissingPath(string $path) * @param array|null $responseData * @return $this */ - public function assertJsonStructure(?array $structure = null, $responseData = null) + public function assertJsonStructure(?array $structure = null, ?array $responseData = null) { $this->decodeResponseJson()->assertStructure($structure, $responseData); @@ -920,7 +920,7 @@ public function assertJsonStructure(?array $structure = null, $responseData = nu * @param array|null $responseData * @return $this */ - public function assertExactJsonStructure(?array $structure = null, $responseData = null) + public function assertExactJsonStructure(?array $structure = null, ?array $responseData = null) { $this->decodeResponseJson()->assertStructure($structure, $responseData, true); From 1badc121d0f337d63134803e12257b8cdf06f3f4 Mon Sep 17 00:00:00 2001 From: Adam Hainsworth-Potter <99101334+adamwhp@users.noreply.github.com> Date: Thu, 15 May 2025 22:18:32 +0100 Subject: [PATCH 154/158] Fix type casting for environment variables in config files (#55737) Explicitly cast environment variables to strings to ensure consistent behaviour across all configuration files. This prevents potential type-related issues when parsing or evaluating environment variables. --- config/app.php | 2 +- config/cache.php | 2 +- config/database.php | 2 +- config/logging.php | 2 +- config/mail.php | 2 +- config/session.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/app.php b/config/app.php index 16073173f8f8..1ced8bef0a14 100644 --- a/config/app.php +++ b/config/app.php @@ -130,7 +130,7 @@ 'previous_keys' => [ ...array_filter( - explode(',', env('APP_PREVIOUS_KEYS', '')) + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) ), ], diff --git a/config/cache.php b/config/cache.php index 925f7d2ee84b..f529e1e3ec74 100644 --- a/config/cache.php +++ b/config/cache.php @@ -103,6 +103,6 @@ | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel'), '_').'_cache_'), ]; diff --git a/config/database.php b/config/database.php index 3e827c359b04..8a3b731fb52e 100644 --- a/config/database.php +++ b/config/database.php @@ -148,7 +148,7 @@ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel'), '_').'_database_'), 'persistent' => env('REDIS_PERSISTENT', false), ], diff --git a/config/logging.php b/config/logging.php index 1345f6f66c51..9e998a496c86 100644 --- a/config/logging.php +++ b/config/logging.php @@ -54,7 +54,7 @@ 'stack' => [ 'driver' => 'stack', - 'channels' => explode(',', env('LOG_STACK', 'single')), + 'channels' => explode(',', (string) env('LOG_STACK', 'single')), 'ignore_exceptions' => false, ], diff --git a/config/mail.php b/config/mail.php index ff140eb439f8..22c03b032d76 100644 --- a/config/mail.php +++ b/config/mail.php @@ -46,7 +46,7 @@ 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, - 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), ], 'ses' => [ diff --git a/config/session.php b/config/session.php index b5fa53194479..13d86a4ac63d 100644 --- a/config/session.php +++ b/config/session.php @@ -129,7 +129,7 @@ 'cookie' => env( 'SESSION_COOKIE', - Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + Str::slug((string) env('APP_NAME', 'laravel'), '_').'_session' ), /* From a0397255f43f6725de7871655a92ed73eff95575 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 16 May 2025 06:16:36 +0800 Subject: [PATCH 155/158] [12.x] Preserve "previous" model state (#55729) * Preserve previous model state Signed-off-by: Mior Muhammad Zaki * Update EloquentModelRefreshTest.php * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update HasAttributes.php * Update HasAttributes.php --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Taylor Otwell --- .../Eloquent/Concerns/HasAttributes.php | 20 ++++++++- .../Database/EloquentModelRefreshTest.php | 12 +++++ .../Database/EloquentModelTest.php | 17 +++++-- .../Database/EloquentUpdateTest.php | 44 +++++++++++++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 0d0fc454bf0b..7c301ea31276 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -68,6 +68,13 @@ trait HasAttributes */ protected $changes = []; + /** + * The previous state of the changed model attributes. + * + * @var array + */ + protected $previous = []; + /** * The attributes that should be cast. * @@ -2082,6 +2089,7 @@ public function syncOriginalAttributes($attributes) public function syncChanges() { $this->changes = $this->getDirty(); + $this->previous = array_intersect_key($this->getRawOriginal(), $this->changes); return $this; } @@ -2117,7 +2125,7 @@ public function isClean($attributes = null) */ public function discardChanges() { - [$this->attributes, $this->changes] = [$this->original, []]; + [$this->attributes, $this->changes, $this->previous] = [$this->original, [], []]; return $this; } @@ -2201,6 +2209,16 @@ public function getChanges() return $this->changes; } + /** + * Get the attributes that were previously original before the model was last saved. + * + * @return array + */ + public function getPrevious() + { + return $this->previous; + } + /** * Determine if the new and old values for a given key are equivalent. * diff --git a/tests/Integration/Database/EloquentModelRefreshTest.php b/tests/Integration/Database/EloquentModelRefreshTest.php index 4bac91dbfe32..304c04f10883 100644 --- a/tests/Integration/Database/EloquentModelRefreshTest.php +++ b/tests/Integration/Database/EloquentModelRefreshTest.php @@ -54,6 +54,18 @@ public function testItSyncsOriginalOnRefresh() $this->assertSame('patrick', $post->getOriginal('title')); } + public function testItDoesNotSyncPreviousOnRefresh() + { + $post = Post::create(['title' => 'pat']); + + Post::find($post->id)->update(['title' => 'patrick']); + + $post->refresh(); + + $this->assertEmpty($post->getDirty()); + $this->assertEmpty($post->getPrevious()); + } + public function testAsPivot() { Schema::create('post_posts', function (Blueprint $table) { diff --git a/tests/Integration/Database/EloquentModelTest.php b/tests/Integration/Database/EloquentModelTest.php index d4ad6c207437..80bc917e250c 100644 --- a/tests/Integration/Database/EloquentModelTest.php +++ b/tests/Integration/Database/EloquentModelTest.php @@ -42,25 +42,28 @@ public function testUserCanUpdateNullableDate() public function testAttributeChanges() { $user = TestModel2::create([ - 'name' => Str::random(), 'title' => Str::random(), + 'name' => $originalName = Str::random(), 'title' => Str::random(), ]); $this->assertEmpty($user->getDirty()); $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); $this->assertFalse($user->isDirty()); $this->assertFalse($user->wasChanged()); - $user->name = $name = Str::random(); + $user->name = $overrideName = Str::random(); - $this->assertEquals(['name' => $name], $user->getDirty()); + $this->assertEquals(['name' => $overrideName], $user->getDirty()); $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); $this->assertTrue($user->isDirty()); $this->assertFalse($user->wasChanged()); $user->save(); $this->assertEmpty($user->getDirty()); - $this->assertEquals(['name' => $name], $user->getChanges()); + $this->assertEquals(['name' => $overrideName], $user->getChanges()); + $this->assertEquals(['name' => $originalName], $user->getPrevious()); $this->assertTrue($user->wasChanged()); $this->assertTrue($user->wasChanged('name')); } @@ -73,6 +76,7 @@ public function testDiscardChanges() $this->assertEmpty($user->getDirty()); $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); $this->assertFalse($user->isDirty()); $this->assertFalse($user->wasChanged()); @@ -80,6 +84,7 @@ public function testDiscardChanges() $this->assertEquals(['name' => $overrideName], $user->getDirty()); $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); $this->assertTrue($user->isDirty()); $this->assertFalse($user->wasChanged()); $this->assertSame($originalName, $user->getOriginal('name')); @@ -88,11 +93,15 @@ public function testDiscardChanges() $user->discardChanges(); $this->assertEmpty($user->getDirty()); + $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); $this->assertSame($originalName, $user->getOriginal('name')); $this->assertSame($originalName, $user->getAttribute('name')); $user->save(); $this->assertFalse($user->wasChanged()); + $this->assertEmpty($user->getChanges()); + $this->assertEmpty($user->getPrevious()); } public function testInsertRecordWithReservedWordFieldName() diff --git a/tests/Integration/Database/EloquentUpdateTest.php b/tests/Integration/Database/EloquentUpdateTest.php index 68fdc26993a2..829091686abf 100644 --- a/tests/Integration/Database/EloquentUpdateTest.php +++ b/tests/Integration/Database/EloquentUpdateTest.php @@ -136,6 +136,50 @@ public function testIncrementOrDecrementIgnoresGlobalScopes() $deletedModel->decrement('counter'); $this->assertEquals(0, $deletedModel->fresh()->counter); } + + public function testUpdateSyncsPrevious() + { + $model = TestUpdateModel1::create([ + 'name' => Str::random(), + 'title' => 'Ms.', + ]); + + $model->update(['title' => 'Dr.']); + + $this->assertSame('Dr.', $model->title); + $this->assertSame('Dr.', $model->getOriginal('title')); + $this->assertSame(['title' => 'Dr.'], $model->getChanges()); + $this->assertSame(['title' => 'Ms.'], $model->getPrevious()); + } + + public function testSaveSyncsPrevious() + { + $model = TestUpdateModel1::create([ + 'name' => Str::random(), + 'title' => 'Ms.', + ]); + + $model->title = 'Dr.'; + $model->save(); + + $this->assertSame('Dr.', $model->title); + $this->assertSame('Dr.', $model->getOriginal('title')); + $this->assertSame(['title' => 'Dr.'], $model->getChanges()); + $this->assertSame(['title' => 'Ms.'], $model->getPrevious()); + } + + public function testIncrementSyncsPrevious() + { + $model = TestUpdateModel3::create([ + 'counter' => 0, + ]); + + $model->increment('counter'); + + $this->assertEquals(1, $model->counter); + $this->assertSame(['counter' => 1], $model->getChanges()); + $this->assertSame(['counter' => 0], $model->getPrevious()); + } } class TestUpdateModel1 extends Model From 718c09e1065a6f04c079ed39ee4915c46462f639 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 16 May 2025 09:27:06 +0200 Subject: [PATCH 156/158] Do not export changelog to source control Seems we forgot to update this when we moved changelogs to a single file. --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index ba7452152c0d..8382fc5c826f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,7 +17,7 @@ .gitattributes export-ignore .gitignore export-ignore .styleci.yml export-ignore -CHANGELOG-* export-ignore +CHANGELOG.md export-ignore CODE_OF_CONDUCT.md export-ignore CONTRIBUTING.md export-ignore docker-compose.yml export-ignore From 8113c82b063c901a778f65208c68c49f0a029cbf Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 16 May 2025 10:19:11 -0400 Subject: [PATCH 157/158] passthru getCountForPagination (#55752) --- src/Illuminate/Database/Eloquent/Builder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 4e22b9ae9fa2..ea5e095117c9 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -118,6 +118,7 @@ class Builder implements BuilderContract 'explain', 'getbindings', 'getconnection', + 'getcountforpagination', 'getgrammar', 'getrawbindings', 'implode', From fba3b98d1ff22f75ea4bd70f8200cfa0b6a3f43d Mon Sep 17 00:00:00 2001 From: Shane Date: Fri, 16 May 2025 22:23:30 +0800 Subject: [PATCH 158/158] [12.x] Add `assertClientError` method to `TestResponse` (#55750) --- src/Illuminate/Testing/TestResponse.php | 15 +++++++++++++++ tests/Testing/TestResponseTest.php | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 0e9de3d5bcf6..72f37339238c 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -131,6 +131,21 @@ public function assertSuccessfulPrecognition() return $this; } + /** + * Assert that the response is a client error. + * + * @return $this + */ + public function assertClientError() + { + PHPUnit::withResponse($this)->assertTrue( + $this->isClientError(), + $this->statusMessageWithDetails('>=400, < 500', $this->getStatusCode()) + ); + + return $this; + } + /** * Assert that the response is a server error. * diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 32642d0df55c..f9389a82e516 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -1086,6 +1086,18 @@ public function testAssertUnprocessable() $response->assertUnprocessable(); } + public function testAssertClientError() + { + $statusCode = 400; + + $baseResponse = tap(new Response, function ($response) use ($statusCode) { + $response->setStatusCode($statusCode); + }); + + $response = TestResponse::fromBaseResponse($baseResponse); + $response->assertClientError(); + } + public function testAssertServerError() { $statusCode = 500;