From b98d825a8fe4ff539050bb53332c3ebd333ad760 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Jan 2023 14:37:33 +0100 Subject: [PATCH 01/99] Set up Laravel v11 --- CHANGELOG.md | 8 ++++---- composer.json | 2 +- src/Illuminate/Auth/composer.json | 18 +++++++++--------- src/Illuminate/Broadcasting/composer.json | 12 ++++++------ src/Illuminate/Bus/composer.json | 8 ++++---- src/Illuminate/Cache/composer.json | 14 +++++++------- src/Illuminate/Collections/composer.json | 6 +++--- src/Illuminate/Config/composer.json | 4 ++-- src/Illuminate/Console/composer.json | 18 +++++++++--------- src/Illuminate/Container/composer.json | 2 +- src/Illuminate/Cookie/composer.json | 8 ++++---- src/Illuminate/Database/composer.json | 18 +++++++++--------- src/Illuminate/Encryption/composer.json | 4 ++-- src/Illuminate/Events/composer.json | 12 ++++++------ src/Illuminate/Filesystem/composer.json | 8 ++++---- src/Illuminate/Foundation/Application.php | 2 +- src/Illuminate/Hashing/composer.json | 4 ++-- src/Illuminate/Http/composer.json | 8 ++++---- src/Illuminate/Log/composer.json | 4 ++-- src/Illuminate/Mail/composer.json | 10 +++++----- src/Illuminate/Notifications/composer.json | 20 ++++++++++---------- src/Illuminate/Pagination/composer.json | 6 +++--- src/Illuminate/Pipeline/composer.json | 4 ++-- src/Illuminate/Queue/composer.json | 18 +++++++++--------- src/Illuminate/Redis/composer.json | 8 ++++---- src/Illuminate/Routing/composer.json | 18 +++++++++--------- src/Illuminate/Session/composer.json | 10 +++++----- src/Illuminate/Support/composer.json | 10 +++++----- src/Illuminate/Testing/composer.json | 14 +++++++------- src/Illuminate/Translation/composer.json | 10 +++++----- src/Illuminate/Validation/composer.json | 14 +++++++------- src/Illuminate/View/composer.json | 14 +++++++------- 32 files changed, 158 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 326792da853e..20ae0ac23114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -# Release Notes for 10.x +# Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.0.0..10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.0.0..master) -## [v10.0.0 (2023-??-??)](https://github.com/laravel/framework/compare/v10.0.0...10.x) +## [v11.0.0 (2023-??-??)](https://github.com/laravel/framework/compare/v11.0.0...master) -Check the upgrade guide in the [Official Laravel Upgrade Documentation](https://laravel.com/docs/10.x/upgrade). Also you can see some release notes in the [Official Laravel Release Documentation](https://laravel.com/docs/10.x/releases). +Check the upgrade guide in the [Official Laravel Upgrade Documentation](https://laravel.com/docs/11.x/upgrade). Also you can see some release notes in the [Official Laravel Release Documentation](https://laravel.com/docs/11.x/releases). diff --git a/composer.json b/composer.json index 09cbdf4a801a..91fb0e2155c8 100644 --- a/composer.json +++ b/composer.json @@ -134,7 +134,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Auth/composer.json b/src/Illuminate/Auth/composer.json index 33bf24f6f273..a9279bdaff1f 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -15,12 +15,12 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/http": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/queue": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/http": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/queue": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -33,9 +33,9 @@ } }, "suggest": { - "illuminate/console": "Required to use the auth:clear-resets command (^10.0).", - "illuminate/queue": "Required to fire login / logout events (^10.0).", - "illuminate/session": "Required to use the session based guard (^10.0)." + "illuminate/console": "Required to use the auth:clear-resets command (^11.0).", + "illuminate/queue": "Required to fire login / logout events (^11.0).", + "illuminate/session": "Required to use the session based guard (^11.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index a4b1a9d53e9f..cfac1319ccbc 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -17,12 +17,12 @@ "php": "^8.1", "ext-json": "*", "psr/log": "^1.0|^2.0|^3.0", - "illuminate/bus": "^10.0", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/queue": "^10.0", - "illuminate/support": "^10.0" + "illuminate/bus": "^11.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/queue": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index 609224f0804c..c4a7b2d69a3c 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/pipeline": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/pipeline": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index 43ee78e3adc8..defe3996a70b 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "provide": { "psr/simple-cache-implementation": "1.0|2.0|3.0" @@ -35,9 +35,9 @@ }, "suggest": { "ext-memcached": "Required to use the memcache cache driver.", - "illuminate/database": "Required to use the database cache driver (^10.0).", - "illuminate/filesystem": "Required to use the file cache driver (^10.0).", - "illuminate/redis": "Required to use the redis cache driver (^10.0).", + "illuminate/database": "Required to use the database cache driver (^11.0).", + "illuminate/filesystem": "Required to use the file cache driver (^11.0).", + "illuminate/redis": "Required to use the redis cache driver (^11.0).", "symfony/cache": "Required to use PSR-6 cache bridge (^6.2)." }, "config": { diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index 5e11a788e704..4821dd578f00 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -15,9 +15,9 @@ ], "require": { "php": "^8.1", - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0" + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Config/composer.json b/src/Illuminate/Config/composer.json index d3442b45a548..175daaa76f2f 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 16cad351c73e..e66ccd1c2d8d 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -15,11 +15,11 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "illuminate/view": "^11.0", "nunomaduro/termwind": "^1.13", "symfony/console": "^6.2", "symfony/process": "^6.2" @@ -37,10 +37,10 @@ "suggest": { "dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).", "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.5).", - "illuminate/bus": "Required to use the scheduled job dispatcher (^10.0).", - "illuminate/container": "Required to use the scheduler (^10.0).", - "illuminate/filesystem": "Required to use the generator command (^10.0).", - "illuminate/queue": "Required to use closures for scheduled jobs (^10.0)." + "illuminate/bus": "Required to use the scheduled job dispatcher (^11.0).", + "illuminate/container": "Required to use the scheduler (^11.0).", + "illuminate/filesystem": "Required to use the generator command (^11.0).", + "illuminate/queue": "Required to use closures for scheduled jobs (^11.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index 02636c73fa45..bef1d93e3694 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -15,7 +15,7 @@ ], "require": { "php": "^8.1", - "illuminate/contracts": "^10.0", + "illuminate/contracts": "^11.0", "psr/container": "^1.1.1|^2.0.1" }, "provide": { diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 6538c80738d3..59a660ac4d28 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", "symfony/http-foundation": "^6.2", "symfony/http-kernel": "^6.2" }, diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index d76faabea3d0..00b617c57379 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -17,11 +17,11 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", "symfony/console": "^6.2" }, "autoload": { @@ -37,10 +37,10 @@ "suggest": { "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.21).", - "illuminate/console": "Required to use the database commands (^10.0).", - "illuminate/events": "Required to use the observers with Eloquent (^10.0).", - "illuminate/filesystem": "Required to use the migrations (^10.0).", - "illuminate/pagination": "Required to paginate the result set (^10.0).", + "illuminate/console": "Required to use the database commands (^11.0).", + "illuminate/events": "Required to use the observers with Eloquent (^11.0).", + "illuminate/filesystem": "Required to use the migrations (^11.0).", + "illuminate/pagination": "Required to paginate the result set (^11.0).", "symfony/finder": "Required to use Eloquent model factories (^6.2)." }, "config": { diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index 5bb019dd0180..d8539b29f055 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -18,8 +18,8 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Events/composer.json b/src/Illuminate/Events/composer.json index b23bc7209f94..481534597720 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -15,12 +15,12 @@ ], "require": { "php": "^8.1", - "illuminate/bus": "^10.0", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "illuminate/bus": "^11.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index 627afe1a3c7d..83e20c9474f3 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", "symfony/finder": "^6.2" }, "autoload": { diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 63ff48d20f0e..175ff22d6180 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 = '10.x-dev'; + const VERSION = '11.x-dev'; /** * The base path for the Laravel installation. diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 1b9c02629410..5f40e153a6eb 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 39a67d5dd34b..a91261dfff78 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -17,10 +17,10 @@ "php": "^8.1", "ext-json": "*", "fruitcake/php-cors": "^1.2", - "illuminate/collections": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/session": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/session": "^11.0", + "illuminate/support": "^11.0", "symfony/http-foundation": "^6.2", "symfony/http-kernel": "^6.2", "symfony/mime": "^6.2" diff --git a/src/Illuminate/Log/composer.json b/src/Illuminate/Log/composer.json index 054aee6a5e30..7bf0941936ba 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0", + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0", "monolog/monolog": "^3.0" }, "autoload": { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 04a7ec18489e..63e557ce6311 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -16,11 +16,11 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", "league/commonmark": "^2.2", "psr/log": "^1.0|^2.0|^3.0", "symfony/mailer": "^6.2", diff --git a/src/Illuminate/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index 40a4923a30c2..ee8e1940b1c5 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -15,15 +15,15 @@ ], "require": { "php": "^8.1", - "illuminate/broadcasting": "^10.0", - "illuminate/bus": "^10.0", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/mail": "^10.0", - "illuminate/queue": "^10.0", - "illuminate/support": "^10.0" + "illuminate/broadcasting": "^11.0", + "illuminate/bus": "^11.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/mail": "^11.0", + "illuminate/queue": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -36,7 +36,7 @@ } }, "suggest": { - "illuminate/database": "Required to use the database transport (^10.0)." + "illuminate/database": "Required to use the database transport (^11.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index 16d11d6d3b70..c5798bbf12bf 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -16,9 +16,9 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index cd561194ae15..3b62abde0107 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index 5377890c47bf..710c2b2169f2 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -16,14 +16,14 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/console": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/pipeline": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/console": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/database": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/pipeline": "^11.0", + "illuminate/support": "^11.0", "laravel/serializable-closure": "^1.2.2", "ramsey/uuid": "^4.7", "symfony/process": "^6.2" @@ -42,7 +42,7 @@ "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", "aws/aws-sdk-php": "Required to use the SQS queue driver and DynamoDb failed job storage (^3.235.5).", - "illuminate/redis": "Required to use the Redis queue driver (^10.0).", + "illuminate/redis": "Required to use the Redis queue driver (^11.0).", "pda/pheanstalk": "Required to use the Beanstalk queue driver (^4.0)." }, "config": { diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index c3b849beb91d..504793b68ade 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index 6afc48b75d7a..b7e517b66b19 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -16,14 +16,14 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/http": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/pipeline": "^10.0", - "illuminate/session": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/http": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/pipeline": "^11.0", + "illuminate/session": "^11.0", + "illuminate/support": "^11.0", "symfony/http-foundation": "^6.2", "symfony/http-kernel": "^6.2", "symfony/routing": "^6.2" @@ -39,7 +39,7 @@ } }, "suggest": { - "illuminate/console": "Required to use the make commands (^10.0).", + "illuminate/console": "Required to use the make commands (^11.0).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." }, diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index b27ba1532427..353351d26339 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -16,10 +16,10 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/support": "^11.0", "symfony/finder": "^6.2", "symfony/http-foundation": "^6.2" }, @@ -34,7 +34,7 @@ } }, "suggest": { - "illuminate/console": "Required to use the session:table command (^10.0)." + "illuminate/console": "Required to use the session:table command (^11.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 81d6a89af4b8..fe02511a786c 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -18,10 +18,10 @@ "ext-json": "*", "ext-mbstring": "*", "doctrine/inflector": "^2.0", - "illuminate/collections": "^10.0", - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", "nesbot/carbon": "^2.62.1", "voku/portable-ascii": "^2.0" }, @@ -42,7 +42,7 @@ } }, "suggest": { - "illuminate/filesystem": "Required to use the composer class (^10.0).", + "illuminate/filesystem": "Required to use the composer class (^11.0).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", "symfony/process": "Required to use the composer class (^6.2).", diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index 79971b69a4ac..5bbe446f29fd 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -32,9 +32,9 @@ }, "suggest": { "brianium/paratest": "Required to run tests in parallel (^6.0).", - "illuminate/console": "Required to assert console commands (^10.0).", - "illuminate/database": "Required to assert databases (^10.0).", - "illuminate/http": "Required to assert responses (^10.0).", + "illuminate/console": "Required to assert console commands (^11.0).", + "illuminate/database": "Required to assert databases (^11.0).", + "illuminate/http": "Required to assert responses (^11.0).", "mockery/mockery": "Required to use mocking (^1.5.1).", "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8)." }, diff --git a/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index e704f2fcd6a4..2b0c013e0f1d 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -16,11 +16,11 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index e2ed9f8c0852..0c8ca1d33120 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -17,12 +17,12 @@ "php": "^8.1", "ext-json": "*", "egulias/email-validator": "^3.2.1", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", - "illuminate/translation": "^10.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "illuminate/translation": "^11.0", "symfony/http-foundation": "^6.2", "symfony/mime": "^6.2" }, @@ -38,7 +38,7 @@ }, "suggest": { "ext-bcmath": "Required to use the multiple_of validation rule.", - "illuminate/database": "Required to use the database presence verifier (^10.0)." + "illuminate/database": "Required to use the database presence verifier (^11.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index b3fcd9793fa6..6b26d6a101f2 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -16,13 +16,13 @@ "require": { "php": "^8.1", "ext-json": "*", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/events": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/events": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { From 7b680dd5b42aa507735e03bd3385de4f0bfc5627 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Jan 2023 15:06:19 +0100 Subject: [PATCH 02/99] splits --- bin/split.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/split.sh b/bin/split.sh index b22ec7aa7224..806fee08bf74 100755 --- a/bin/split.sh +++ b/bin/split.sh @@ -3,7 +3,7 @@ set -e set -x -CURRENT_BRANCH="10.x" +CURRENT_BRANCH="master" function split() { From d24da7ce841bd87f94b27dae632bf3f9a9e0fee5 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Jan 2023 15:17:21 +0100 Subject: [PATCH 03/99] wip --- bin/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/release.sh b/bin/release.sh index eb047b308a8d..e5ea612d3ab6 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -10,7 +10,7 @@ then exit 1 fi -RELEASE_BRANCH="10.x" +RELEASE_BRANCH="11.x" CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) VERSION=$1 From fcc3e5b81ea65744e05a3a5798566330ab428088 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Jan 2023 15:18:11 +0100 Subject: [PATCH 04/99] Branch aliases --- src/Illuminate/Auth/composer.json | 2 +- src/Illuminate/Broadcasting/composer.json | 2 +- src/Illuminate/Bus/composer.json | 2 +- src/Illuminate/Cache/composer.json | 2 +- src/Illuminate/Collections/composer.json | 2 +- src/Illuminate/Conditionable/composer.json | 2 +- src/Illuminate/Config/composer.json | 2 +- src/Illuminate/Console/composer.json | 2 +- src/Illuminate/Container/composer.json | 2 +- src/Illuminate/Contracts/composer.json | 2 +- src/Illuminate/Cookie/composer.json | 2 +- src/Illuminate/Database/composer.json | 2 +- src/Illuminate/Encryption/composer.json | 2 +- src/Illuminate/Events/composer.json | 2 +- src/Illuminate/Filesystem/composer.json | 2 +- src/Illuminate/Hashing/composer.json | 2 +- src/Illuminate/Http/composer.json | 2 +- src/Illuminate/Log/composer.json | 2 +- src/Illuminate/Macroable/composer.json | 2 +- src/Illuminate/Mail/composer.json | 2 +- src/Illuminate/Notifications/composer.json | 2 +- src/Illuminate/Pagination/composer.json | 2 +- src/Illuminate/Pipeline/composer.json | 2 +- src/Illuminate/Queue/composer.json | 2 +- src/Illuminate/Redis/composer.json | 2 +- src/Illuminate/Routing/composer.json | 2 +- src/Illuminate/Session/composer.json | 2 +- src/Illuminate/Support/composer.json | 2 +- src/Illuminate/Testing/composer.json | 2 +- src/Illuminate/Translation/composer.json | 2 +- src/Illuminate/Validation/composer.json | 2 +- src/Illuminate/View/composer.json | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Illuminate/Auth/composer.json b/src/Illuminate/Auth/composer.json index a9279bdaff1f..308edf3f4d64 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index cfac1319ccbc..ab295469dee5 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index c4a7b2d69a3c..b1fd8b47b84f 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -27,7 +27,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index defe3996a70b..9026088b8b9e 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index 4821dd578f00..d7af97f26369 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Conditionable/composer.json b/src/Illuminate/Conditionable/composer.json index 40b0808060c3..eb1d71eb18ab 100644 --- a/src/Illuminate/Conditionable/composer.json +++ b/src/Illuminate/Conditionable/composer.json @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Config/composer.json b/src/Illuminate/Config/composer.json index 175daaa76f2f..0a24b18a2c57 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index e66ccd1c2d8d..4e211d4d1b07 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index bef1d93e3694..3e74b30ffe40 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Contracts/composer.json b/src/Illuminate/Contracts/composer.json index acae4bef8d69..e3bfc17851a7 100644 --- a/src/Illuminate/Contracts/composer.json +++ b/src/Illuminate/Contracts/composer.json @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 59a660ac4d28..723f473b2d2d 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index 00b617c57379..5698f944cdeb 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index d8539b29f055..570fc11594d2 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Events/composer.json b/src/Illuminate/Events/composer.json index 481534597720..8874e704d58b 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index 83e20c9474f3..c91b60cbfca7 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 5f40e153a6eb..2468e7a211a6 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index a91261dfff78..e701b81f7ae5 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -36,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Log/composer.json b/src/Illuminate/Log/composer.json index 7bf0941936ba..3efd7e9b99c4 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -26,7 +26,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Macroable/composer.json b/src/Illuminate/Macroable/composer.json index 231d2333a44f..859a68427cb9 100644 --- a/src/Illuminate/Macroable/composer.json +++ b/src/Illuminate/Macroable/composer.json @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 63e557ce6311..b9f1de46a978 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index ee8e1940b1c5..a93bd509cd14 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index c5798bbf12bf..6077e531bd38 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -27,7 +27,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index 3b62abde0107..0acf5ca7779e 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index 710c2b2169f2..46668c01bc39 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index 504793b68ade..3ac43517c47b 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index b7e517b66b19..e1e123056d3d 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index 353351d26339..d6d2439610d4 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index fe02511a786c..4b9bb30bb2ca 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index 5bbe446f29fd..5b60162e1f15 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -27,7 +27,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index 2b0c013e0f1d..f4198d67ab04 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 0c8ca1d33120..b728140c6035 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index 6b26d6a101f2..1cbfb7ca36c8 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { From 26a0924576060d9eb1682282597d6ba68eeace44 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Jan 2023 15:27:44 +0100 Subject: [PATCH 05/99] [11.x] Drop PHP 8.1 support (#45526) * Drop PHP 8.1 support * wip --- .github/workflows/databases.yml | 10 +++---- .github/workflows/facades.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 32 +++------------------- bin/test.sh | 2 +- composer.json | 2 +- src/Illuminate/Auth/composer.json | 2 +- src/Illuminate/Broadcasting/composer.json | 2 +- src/Illuminate/Bus/composer.json | 2 +- src/Illuminate/Cache/composer.json | 2 +- src/Illuminate/Collections/composer.json | 2 +- src/Illuminate/Config/composer.json | 2 +- src/Illuminate/Console/composer.json | 2 +- src/Illuminate/Container/composer.json | 2 +- src/Illuminate/Contracts/composer.json | 2 +- src/Illuminate/Cookie/composer.json | 2 +- src/Illuminate/Database/composer.json | 2 +- src/Illuminate/Encryption/composer.json | 2 +- src/Illuminate/Events/composer.json | 2 +- src/Illuminate/Filesystem/composer.json | 2 +- src/Illuminate/Hashing/composer.json | 2 +- src/Illuminate/Http/composer.json | 2 +- src/Illuminate/Log/composer.json | 2 +- src/Illuminate/Macroable/composer.json | 2 +- src/Illuminate/Mail/composer.json | 2 +- src/Illuminate/Notifications/composer.json | 2 +- src/Illuminate/Pagination/composer.json | 2 +- src/Illuminate/Pipeline/composer.json | 2 +- src/Illuminate/Queue/composer.json | 2 +- src/Illuminate/Redis/composer.json | 2 +- src/Illuminate/Routing/composer.json | 2 +- src/Illuminate/Session/composer.json | 2 +- src/Illuminate/Support/composer.json | 2 +- src/Illuminate/Testing/composer.json | 2 +- src/Illuminate/Translation/composer.json | 2 +- src/Illuminate/Validation/composer.json | 2 +- src/Illuminate/View/composer.json | 2 +- tests/Http/HttpRequestTest.php | 3 -- tests/Routing/RoutingUrlGeneratorTest.php | 3 -- tests/Support/SupportCollectionTest.php | 2 -- tests/Support/SupportHelpersTest.php | 3 -- 41 files changed, 44 insertions(+), 79 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index b21686c826dd..4aac3ca80b1d 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -33,7 +33,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql tools: composer:v2 coverage: none @@ -76,7 +76,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql tools: composer:v2 coverage: none @@ -119,7 +119,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql tools: composer:v2 coverage: none @@ -163,7 +163,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql tools: composer:v2 coverage: none @@ -205,7 +205,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc tools: composer:v2 coverage: none diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index d77e2b509624..d6027daf2bc1 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -22,7 +22,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 tools: composer:v2 coverage: none diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 52378f6b12ba..78305599c2bf 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -25,7 +25,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 tools: composer:v2 coverage: none diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 63d32d9dfd14..ea516a33392e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2] + php: [8.2] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} @@ -60,20 +60,12 @@ jobs: REDIS_CONFIGURE_OPTS: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4 REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev - - name: Set Minimum PHP 8.1 Versions - uses: nick-fields/retry@v2 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require brick/math:^0.9.3 symfony/css-selector:^6.0 --no-interaction --no-update - if: matrix.php >= 8.1 - - name: Set Minimum PHP 8.2 Versions uses: nick-fields/retry@v2 with: timeout_minutes: 5 max_attempts: 5 - command: composer require guzzlehttp/guzzle:^7.5 guzzlehttp/psr7:^2.4 predis/predis:^2.0.2 --no-interaction --no-update + command: composer require brick/math:^0.9.3 symfony/css-selector:^6.0 guzzlehttp/guzzle:^7.5 guzzlehttp/psr7:^2.4 predis/predis:^2.0.2 --no-interaction --no-update if: matrix.php >= 8.2 - name: Install dependencies @@ -107,7 +99,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2] + php: [8.2] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows @@ -129,28 +121,12 @@ jobs: tools: composer:v2 coverage: none - - name: Set Minimum PHP 8.1 Versions - uses: nick-fields/retry@v2 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require ramsey/collection:^1.2 brick/math:^0.9.3 --no-interaction --no-update - if: matrix.php >= 8.1 - - - name: Set Minimum PHP 8.1 Versions - uses: nick-fields/retry@v2 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require brick/math:~0.9.3 symfony/css-selector:~6.0 --no-interaction --no-update - if: matrix.php >= 8.1 - - name: Set Minimum PHP 8.2 Versions uses: nick-fields/retry@v2 with: timeout_minutes: 5 max_attempts: 5 - command: composer require guzzlehttp/guzzle:~7.5 guzzlehttp/psr7:~2.4 predis/predis:~2.0.2 --no-interaction --no-update + command: composer require brick/math:~0.9.3 symfony/css-selector:~6.0 guzzlehttp/guzzle:~7.5 guzzlehttp/psr7:~2.4 predis/predis:~2.0.2 --no-interaction --no-update if: matrix.php >= 8.2 - name: Install dependencies diff --git a/bin/test.sh b/bin/test.sh index a0d24ceecd7d..42b08961acee 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash down=false -php="8.1" +php="8.2" while true; do case "$1" in diff --git a/composer.json b/composer.json index 91fb0e2155c8..afed7facf664 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-mbstring": "*", "ext-openssl": "*", "doctrine/inflector": "^2.0", diff --git a/src/Illuminate/Auth/composer.json b/src/Illuminate/Auth/composer.json index 308edf3f4d64..e66d42f0b142 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/http": "^11.0", diff --git a/src/Illuminate/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index ab295469dee5..a30f11b83413 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "psr/log": "^1.0|^2.0|^3.0", "illuminate/bus": "^11.0", diff --git a/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index b1fd8b47b84f..3acfed639cf9 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/pipeline": "^11.0", diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index 9026088b8b9e..59becf361479 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index d7af97f26369..480171bb4f07 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/conditionable": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0" diff --git a/src/Illuminate/Config/composer.json b/src/Illuminate/Config/composer.json index 0a24b18a2c57..ccccc4303e09 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0" }, diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 4e211d4d1b07..8a0873c24f38 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index 3e74b30ffe40..ab9f51c8f545 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/contracts": "^11.0", "psr/container": "^1.1.1|^2.0.1" }, diff --git a/src/Illuminate/Contracts/composer.json b/src/Illuminate/Contracts/composer.json index e3bfc17851a7..c4b054c2521a 100644 --- a/src/Illuminate/Contracts/composer.json +++ b/src/Illuminate/Contracts/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/simple-cache": "^1.0|^2.0|^3.0" }, diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 723f473b2d2d..edaf27bde521 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index 5698f944cdeb..46ab872bd144 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index 570fc11594d2..21e0bdd57842 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", diff --git a/src/Illuminate/Events/composer.json b/src/Illuminate/Events/composer.json index 8874e704d58b..df77fedb69eb 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/bus": "^11.0", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index c91b60cbfca7..098971e95f36 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 2468e7a211a6..79443fb9d020 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/contracts": "^11.0", "illuminate/support": "^11.0" }, diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index e701b81f7ae5..b31d244cb33a 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "fruitcake/php-cors": "^1.2", "illuminate/collections": "^11.0", diff --git a/src/Illuminate/Log/composer.json b/src/Illuminate/Log/composer.json index 3efd7e9b99c4..dfdcf13a4e7c 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/contracts": "^11.0", "illuminate/support": "^11.0", "monolog/monolog": "^3.0" diff --git a/src/Illuminate/Macroable/composer.json b/src/Illuminate/Macroable/composer.json index 859a68427cb9..08417d06884b 100644 --- a/src/Illuminate/Macroable/composer.json +++ b/src/Illuminate/Macroable/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1" + "php": "^8.2" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index b9f1de46a978..164bffbbf7e5 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", diff --git a/src/Illuminate/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index a93bd509cd14..18321766911a 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/broadcasting": "^11.0", "illuminate/bus": "^11.0", "illuminate/collections": "^11.0", diff --git a/src/Illuminate/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index 6077e531bd38..23ec4bd96a94 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index 0acf5ca7779e..9cd62e83f78c 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/contracts": "^11.0", "illuminate/support": "^11.0" }, diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index 46668c01bc39..d2ecd45ff89e 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/console": "^11.0", diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index 3ac43517c47b..1686aeaa13f6 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index e1e123056d3d..1fcb701f125c 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index d6d2439610d4..8060aafaada1 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 4b9bb30bb2ca..c2a12f56891a 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "ext-mbstring": "*", "doctrine/inflector": "^2.0", diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index 5b60162e1f15..cbcd55de4799 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", diff --git a/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index f4198d67ab04..887ec9919b17 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/contracts": "^11.0", diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index b728140c6035..fed8c34714b6 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "egulias/email-validator": "^3.2.1", "illuminate/collections": "^11.0", diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index 1cbfb7ca36c8..79f541ee4415 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 96c6e5082b97..e65cfc8c93aa 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -719,9 +719,6 @@ public function testDateMethodExceptionWhenFormatInvalid() $request->date('date', 'invalid_format'); } - /** - * @requires PHP >= 8.1 - */ public function testEnumMethod() { $request = Request::create('/', 'GET', [ diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index 9909f9c06392..c565963358f1 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -861,9 +861,6 @@ public function testSignedUrlParameterCannotBeNamedExpires() Request::create($url->signedRoute('foo', ['expires' => 253402300799])); } - /** - * @requires PHP >= 8.1 - */ public function testRouteGenerationWithBackedEnums() { $url = new UrlGenerator( diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 295530a7877a..23970e0ed682 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -4630,7 +4630,6 @@ public function testCollectionFromTraversableWithKeys($collection) /** * @dataProvider collectionClassProvider - * @requires PHP >= 8.1 */ public function testCollectionFromEnum($collection) { @@ -4640,7 +4639,6 @@ public function testCollectionFromEnum($collection) /** * @dataProvider collectionClassProvider - * @requires PHP >= 8.1 */ public function testCollectionFromBackedEnum($collection) { diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 1dec3cd9ad0e..da3f04aa1dfa 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -37,9 +37,6 @@ public function testE() $this->assertEquals($str, e($html)); } - /** - * @requires PHP >= 8.1 - */ public function testEWithEnums() { $enumValue = StringBackedEnum::ADMIN_LABEL; From a5c78d8930e3fa0294187d0f5fe4142f02d9707d Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 6 Jan 2023 09:49:46 +0800 Subject: [PATCH 06/99] [11.x] Update Testbench dependencies for Laravel 11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index afed7facf664..a323e3e3859b 100644 --- a/composer.json +++ b/composer.json @@ -93,7 +93,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.0", + "orchestra/testbench-core": "^9.0", "pda/pheanstalk": "^4.0", "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", From bf87fe7e1f4d9f5d3670496b9f6904312ac52df7 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 10 Jan 2023 08:41:03 +0000 Subject: [PATCH 07/99] Update facade docblocks --- src/Illuminate/Support/Facades/Storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Facades/Storage.php b/src/Illuminate/Support/Facades/Storage.php index 0d13bb507df2..fd8299c95328 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -52,8 +52,8 @@ * @method static string path(string $path) * @method static \Symfony\Component\HttpFoundation\StreamedResponse response(string $path, string|null $name = null, array $headers = [], string|null $disposition = 'inline') * @method static \Symfony\Component\HttpFoundation\StreamedResponse download(string $path, string|null $name = null, array $headers = []) - * @method static string|false putFile(string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file, mixed $options = []) - * @method static string|false putFileAs(string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file, string $name, mixed $options = []) + * @method static string|false putFile(\Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string|array|null $file = null, mixed $options = []) + * @method static string|false putFileAs(\Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string|array|null $file, string|array|null $name = null, mixed $options = []) * @method static string|false checksum(string $path, array $options = []) * @method static string|false mimeType(string $path) * @method static string url(string $path) From b4be850918efa3a726576f14cba70bdeda415c07 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 14 Feb 2023 17:10:12 +0100 Subject: [PATCH 08/99] Use minimum stability dev for 11.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 806a8df93c77..b10380d7c239 100644 --- a/composer.json +++ b/composer.json @@ -191,6 +191,6 @@ "composer/package-versions-deprecated": true } }, - "minimum-stability": "stable", + "minimum-stability": "dev", "prefer-stable": true } From 1c602f0badafaa7fc926fcbf8cf2f7dae93f6487 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 9 Mar 2023 14:20:42 +0000 Subject: [PATCH 09/99] Cranked symfony version to `^6.3` (#46408) --- composer.json | 36 ++++++++++++------------ src/Illuminate/Cache/composer.json | 2 +- src/Illuminate/Collections/composer.json | 2 +- src/Illuminate/Console/composer.json | 4 +-- src/Illuminate/Cookie/composer.json | 4 +-- src/Illuminate/Database/composer.json | 2 +- src/Illuminate/Filesystem/composer.json | 6 ++-- src/Illuminate/Http/composer.json | 6 ++-- src/Illuminate/Mail/composer.json | 8 +++--- src/Illuminate/Process/composer.json | 2 +- src/Illuminate/Queue/composer.json | 2 +- src/Illuminate/Routing/composer.json | 6 ++-- src/Illuminate/Session/composer.json | 4 +-- src/Illuminate/Support/composer.json | 6 ++-- src/Illuminate/Validation/composer.json | 4 +-- 15 files changed, 47 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index e24238126424..4eb919a629ad 100644 --- a/composer.json +++ b/composer.json @@ -40,17 +40,17 @@ "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^6.2", - "symfony/error-handler": "^6.2", - "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2", - "symfony/mailer": "^6.2", - "symfony/mime": "^6.2", - "symfony/process": "^6.2", - "symfony/routing": "^6.2", - "symfony/uid": "^6.2", - "symfony/var-dumper": "^6.2", + "symfony/console": "^6.3", + "symfony/error-handler": "^6.3", + "symfony/finder": "^6.3", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/mailer": "^6.3", + "symfony/mime": "^6.3", + "symfony/process": "^6.3", + "symfony/routing": "^6.3", + "symfony/uid": "^6.3", + "symfony/var-dumper": "^6.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.4.1", "voku/portable-ascii": "^2.0" @@ -109,8 +109,8 @@ "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", - "symfony/cache": "^6.2", - "symfony/http-client": "^6.2.4" + "symfony/cache": "^6.3", + "symfony/http-client": "^6.3.4" }, "provide": { "psr/container-implementation": "1.1|2.0", @@ -178,11 +178,11 @@ "predis/predis": "Required to use the predis connector (^2.0.2).", "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).", - "symfony/cache": "Required to PSR-6 cache bridge (^6.2).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.3).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.3).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.3).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.3).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.3).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." }, "config": { diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index e0e4190334ab..d486a4335f46 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -40,7 +40,7 @@ "illuminate/database": "Required to use the database cache driver (^11.0).", "illuminate/filesystem": "Required to use the file cache driver (^11.0).", "illuminate/redis": "Required to use the redis cache driver (^11.0).", - "symfony/cache": "Required to use PSR-6 cache bridge (^6.2)." + "symfony/cache": "Required to use PSR-6 cache bridge (^6.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index 480171bb4f07..80671fb4dade 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -33,7 +33,7 @@ } }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.2)." + "symfony/var-dumper": "Required to use the dump method (^6.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 07edc51ee5e3..5b855d8fefc2 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -22,8 +22,8 @@ "illuminate/support": "^11.0", "illuminate/view": "^11.0", "nunomaduro/termwind": "^1.13", - "symfony/console": "^6.2", - "symfony/process": "^6.2" + "symfony/console": "^6.3", + "symfony/process": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index dd1a42a5e09f..b2ffb16f88dc 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -20,8 +20,8 @@ "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2" + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index 35892d4ace28..3c76a0174268 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -42,7 +42,7 @@ "illuminate/events": "Required to use the observers with Eloquent (^11.0).", "illuminate/filesystem": "Required to use the migrations (^11.0).", "illuminate/pagination": "Required to paginate the result set (^11.0).", - "symfony/finder": "Required to use Eloquent model factories (^6.2)." + "symfony/finder": "Required to use Eloquent model factories (^6.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index 814f0bfad23a..b2657b376d75 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -19,7 +19,7 @@ "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", - "symfony/finder": "^6.2" + "symfony/finder": "^6.3" }, "autoload": { "psr-4": { @@ -41,8 +41,8 @@ "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).", - "symfony/mime": "Required to enable support for guessing extensions (^6.2)." + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.3).", + "symfony/mime": "Required to enable support for guessing extensions (^6.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 6bb8b72f08d3..4b3d66229021 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -22,9 +22,9 @@ "illuminate/macroable": "^11.0", "illuminate/session": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2", - "symfony/mime": "^6.2" + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/mime": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index be562446e884..42a64ccdac78 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -22,7 +22,7 @@ "illuminate/support": "^11.0", "league/commonmark": "^2.2", "psr/log": "^1.0|^2.0|^3.0", - "symfony/mailer": "^6.2", + "symfony/mailer": "^6.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5" }, "autoload": { @@ -37,9 +37,9 @@ }, "suggest": { "aws/aws-sdk-php": "Required to use the SES mail driver (^3.235.5).", - "symfony/http-client": "Required to use the Symfony API mail transports (^6.2).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2)." + "symfony/http-client": "Required to use the Symfony API mail transports (^6.3).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.3).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Process/composer.json b/src/Illuminate/Process/composer.json index 8ac36b6ef2d1..c056b4aac494 100644 --- a/src/Illuminate/Process/composer.json +++ b/src/Illuminate/Process/composer.json @@ -19,7 +19,7 @@ "illuminate/contracts": "^10.0", "illuminate/macroable": "^10.0", "illuminate/support": "^10.0", - "symfony/process": "^6.2" + "symfony/process": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index baa47ba2acaa..f341b6768392 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -25,7 +25,7 @@ "illuminate/support": "^11.0", "laravel/serializable-closure": "^1.2.2", "ramsey/uuid": "^4.7", - "symfony/process": "^6.2" + "symfony/process": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index 7b83dc1ef538..82825c675cab 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -25,9 +25,9 @@ "illuminate/pipeline": "^11.0", "illuminate/session": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2", - "symfony/routing": "^6.2" + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/routing": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index e4d335765281..85a08b559167 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -21,8 +21,8 @@ "illuminate/contracts": "^11.0", "illuminate/filesystem": "^11.0", "illuminate/support": "^11.0", - "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.2" + "symfony/finder": "^6.3", + "symfony/http-foundation": "^6.3" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index b73443761afd..dbaaa12bf56c 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -46,9 +46,9 @@ "illuminate/filesystem": "Required to use the composer class (^11.0).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", "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).", - "symfony/var-dumper": "Required to use the dd function (^6.2).", + "symfony/process": "Required to use the composer class (^6.3).", + "symfony/uid": "Required to use Str::ulid() (^6.3).", + "symfony/var-dumper": "Required to use the dd function (^6.3).", "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." }, "config": { diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index ca29c655a09e..4cf5d733e16b 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -25,8 +25,8 @@ "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", "illuminate/translation": "^11.0", - "symfony/http-foundation": "^6.2", - "symfony/mime": "^6.2" + "symfony/http-foundation": "^6.3", + "symfony/mime": "^6.3" }, "autoload": { "psr-4": { From 78149f7a0750edff65fe1e02e5e0cfc72a11d6c3 Mon Sep 17 00:00:00 2001 From: GrahamCampbell Date: Mon, 13 Mar 2023 01:24:18 +0000 Subject: [PATCH 10/99] Update facade docblocks --- src/Illuminate/Support/Facades/Cache.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index 5044f4d96aff..a8a84646b96c 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -53,7 +53,6 @@ * @method static \Illuminate\Contracts\Cache\Lock restoreLock(string $name, string $owner) * * @see \Illuminate\Cache\CacheManager - * * @mixin \Illuminate\Cache\Repository */ class Cache extends Facade From abe0a658b5553c307bb379a816159e03b715b1ba Mon Sep 17 00:00:00 2001 From: GrahamCampbell Date: Mon, 13 Mar 2023 01:39:20 +0000 Subject: [PATCH 11/99] Update facade docblocks --- src/Illuminate/Support/Facades/Cache.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index a8a84646b96c..5044f4d96aff 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -53,6 +53,7 @@ * @method static \Illuminate\Contracts\Cache\Lock restoreLock(string $name, string $owner) * * @see \Illuminate\Cache\CacheManager + * * @mixin \Illuminate\Cache\Repository */ class Cache extends Facade From 68caccf8411fb1a617d0a32e45481d87073588d2 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 13 Mar 2023 15:35:27 +0000 Subject: [PATCH 12/99] [11.x] Revert "[9.x] Translation of the default message of the validator" (#46378) * [9.x] Update CHANGELOG.md * Update facade docblocks * [9.x] Update CHANGELOG.md * Revert "[9.x] Translation of the default message of the validator (#43837)" This reverts commit a833a8850e58cafc0bb8a7c1f0a1588d99374597. --------- Co-authored-by: Tetiana Blindaruk Co-authored-by: TBlindaruk --- src/Illuminate/Validation/ValidationException.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Validation/ValidationException.php b/src/Illuminate/Validation/ValidationException.php index 31f6f1fa9924..053f65b20694 100644 --- a/src/Illuminate/Validation/ValidationException.php +++ b/src/Illuminate/Validation/ValidationException.php @@ -88,15 +88,15 @@ protected static function summarize($validator) $messages = $validator->errors()->all(); if (! count($messages) || ! is_string($messages[0])) { - return $validator->getTranslator()->get('The given data was invalid.'); + return 'The given data was invalid.'; } $message = array_shift($messages); - if ($count = count($messages)) { - $pluralized = $count === 1 ? 'error' : 'errors'; + if ($additional = count($messages)) { + $pluralized = $additional === 1 ? 'error' : 'errors'; - $message .= ' '.$validator->getTranslator()->get("(and :count more $pluralized)", compact('count')); + $message .= " (and {$additional} more {$pluralized})"; } return $message; From a05ce6dea553f54ad70da6adc7573ca862e9e364 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 25 Mar 2023 07:01:40 -0500 Subject: [PATCH 13/99] [11.x] Paginator total override (#46410) * allow override of the Builder `paginate()` total this allows the user to set the total number of results a query returns. If the user provides this argument, the `paginate()` method will skip running its query to determine the total row count. * use value helper --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Builder.php | 5 ++-- src/Illuminate/Database/Query/Builder.php | 5 ++-- tests/Database/DatabaseQueryBuilderTest.php | 24 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index a476b32ee8c4..c0e17ad1b072 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -889,15 +889,16 @@ public function pluck($column, $key = null) * @param array|string $columns * @param string $pageName * @param int|null $page + * @param \Closure|int|null $total * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator * * @throws \InvalidArgumentException */ - public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $total = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); - $total = $this->toBase()->getCountForPagination(); + $total = value($total) ?? $this->toBase()->getCountForPagination(); $perPage = ($perPage instanceof Closure ? $perPage($total) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index d1965d6881c3..ec169507e0fb 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -2727,13 +2727,14 @@ protected function runSelect() * @param array|string $columns * @param string $pageName * @param int|null $page + * @param \Closure|int|null $total * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) + public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null, $total = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); - $total = $this->getCountForPagination(); + $total = value($total) ?? $this->getCountForPagination(); $perPage = $perPage instanceof Closure ? $perPage($total) : $perPage; diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 25568d5ec069..f6ceb3cd6fb6 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -4450,6 +4450,30 @@ public function testPaginateWithSpecificColumns() ]), $result); } + public function testPaginateWithTotalOverride() + { + $perPage = 16; + $columns = ['id', 'name']; + $pageName = 'page-name'; + $page = 1; + $builder = $this->getMockQueryBuilder(); + $path = 'http://foo.bar?page=3'; + + $results = collect([['id' => 3, 'name' => 'Taylor'], ['id' => 5, 'name' => 'Mohamed']]); + + $builder->shouldReceive('getCountForPagination')->never(); + $builder->shouldReceive('forPage')->once()->with($page, $perPage)->andReturnSelf(); + $builder->shouldReceive('get')->once()->andReturn($results); + + Paginator::currentPathResolver(function () use ($path) { + return $path; + }); + + $result = $builder->paginate($perPage, $columns, $pageName, $page, 10); + + $this->assertEquals(10, $result->total()); + } + public function testCursorPaginate() { $perPage = 16; From c8b95c9d0e8f91ac7f140a84fd38dc0657fd90f4 Mon Sep 17 00:00:00 2001 From: Chris Bengel <128682916+CmdMumm@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:52:07 +0200 Subject: [PATCH 14/99] Update FilesystemManager.php (#46668) Do not change relative path to absolute if SFTP-Driver is created without root path - similiar to createFtpDiver() --- src/Illuminate/Filesystem/FilesystemManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index 0475c40e0363..51a5e5e94b56 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -214,7 +214,7 @@ public function createSftpDriver(array $config) { $provider = SftpConnectionProvider::fromArray($config); - $root = $config['root'] ?? '/'; + $root = $config['root'] ?? ''; $visibility = PortableVisibilityConverter::fromArray( $config['permissions'] ?? [] From f83676fd4897d9a516df59b64d163a9ac32a2adf Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Sat, 13 May 2023 22:19:04 +0330 Subject: [PATCH 15/99] [11.x] Fixes `Auth\Guard::setUser` (#47061) * fix contracts * Update facade docblocks --------- Co-authored-by: milwad-dev --- src/Illuminate/Auth/SessionGuard.php | 2 +- src/Illuminate/Contracts/Auth/Guard.php | 2 +- src/Illuminate/Support/Facades/Auth.php | 2 +- src/Illuminate/Support/Facades/Request.php | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 769edb204330..b475dbc6ca2e 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -914,7 +914,7 @@ public function getUser() * Set the current user. * * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @return $this */ public function setUser(AuthenticatableContract $user) { diff --git a/src/Illuminate/Contracts/Auth/Guard.php b/src/Illuminate/Contracts/Auth/Guard.php index 2796f1ae6a9a..a3541f42bae5 100644 --- a/src/Illuminate/Contracts/Auth/Guard.php +++ b/src/Illuminate/Contracts/Auth/Guard.php @@ -51,7 +51,7 @@ public function hasUser(); * Set the current user. * * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @return $this */ public function setUser(Authenticatable $user); } diff --git a/src/Illuminate/Support/Facades/Auth.php b/src/Illuminate/Support/Facades/Auth.php index fd5ac5138aeb..d2d44fac857d 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -28,7 +28,7 @@ * @method static int|string|null id() * @method static bool validate(array $credentials = []) * @method static bool hasUser() - * @method static void setUser(\Illuminate\Contracts\Auth\Authenticatable $user) + * @method static \Illuminate\Contracts\Auth\Guard setUser(\Illuminate\Contracts\Auth\Authenticatable $user) * @method static bool attempt(array $credentials = [], bool $remember = false) * @method static bool once(array $credentials = []) * @method static void login(\Illuminate\Contracts\Auth\Authenticatable $user, bool $remember = false) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index f28b2aa8e8f9..411663cbce42 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -106,6 +106,7 @@ * @method static bool isMethodCacheable() * @method static string|null getProtocolVersion() * @method static string|resource getContent(bool $asResource = false) + * @method static \Symfony\Component\HttpFoundation\InputBag getPayload() * @method static array getETags() * @method static bool isNoCache() * @method static string|null getPreferredFormat(string|null $default = 'html') From 893d8fc2cb2da69ee499dfe1e043e034b31bf088 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 15 May 2023 17:14:50 +0200 Subject: [PATCH 16/99] [11.x] Always set connect timeout, timeout and enforce TLS 1.2 or higher when using Guzzle (#47083) * Always set connect timeout, timeout and enforce TLS 1.2 or higher when using Guzzle * Update HttpClientTest.php --- composer.json | 4 ++-- src/Illuminate/Broadcasting/BroadcastManager.php | 15 ++++++++++++--- src/Illuminate/Console/composer.json | 2 +- src/Illuminate/Http/Client/PendingRequest.php | 1 + src/Illuminate/Http/composer.json | 2 +- tests/Http/HttpClientTest.php | 8 ++++---- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 4eb919a629ad..0e9859a7e1eb 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "aws/aws-sdk-php": "^3.235.5", "doctrine/dbal": "^3.5.1", "fakerphp/faker": "^1.21", - "guzzlehttp/guzzle": "^7.5", + "guzzlehttp/guzzle": "^7.6", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", "league/flysystem-path-prefixing": "^3.3", @@ -164,7 +164,7 @@ "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", - "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.6).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index e8bdf0bad873..7f964b078c6c 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -292,14 +292,23 @@ protected function createPusherDriver(array $config) */ public function pusher(array $config) { + $guzzleClient = new GuzzleClient( + array_merge( + [ + 'connect_timeout' => 10, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + 'timeout' => 30, + ], + $config['client_options'] ?? [], + ), + ); + $pusher = new Pusher( $config['key'], $config['secret'], $config['app_id'], $config['options'] ?? [], - isset($config['client_options']) && ! empty($config['client_options']) - ? new GuzzleClient($config['client_options']) - : null, + $guzzleClient, ); if ($config['log'] ?? false) { diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 5b855d8fefc2..e09895a86077 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -38,7 +38,7 @@ "suggest": { "ext-pcntl": "Required to use signal trapping.", "dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).", - "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.5).", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.6).", "illuminate/bus": "Required to use the scheduled job dispatcher (^11.0).", "illuminate/container": "Required to use the scheduler (^11.0).", "illuminate/filesystem": "Required to use the generator command (^11.0).", diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index ceb657736b5d..665a33e79942 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -227,6 +227,7 @@ public function __construct(Factory $factory = null) $this->options = [ 'connect_timeout' => 10, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, 'http_errors' => false, 'timeout' => 30, ]; diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 4b3d66229021..05c6b9d00eba 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -33,7 +33,7 @@ }, "suggest": { "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", - "guzzlehttp/guzzle": "Required to use the HTTP Client (^7.5)." + "guzzlehttp/guzzle": "Required to use the HTTP Client (^7.6)." }, "extra": { "branch-alias": { diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index f15b0ae71d93..1dae93fe881a 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1260,11 +1260,11 @@ public function testRequestsCanReplaceOptions() $request = $request->withOptions(['http_errors' => true, 'connect_timeout' => 10]); - $this->assertSame(['connect_timeout' => 10, 'http_errors' => true, 'timeout' => 30], $request->getOptions()); + $this->assertSame(['connect_timeout' => 10, 'crypto_method' => 33, 'http_errors' => true, 'timeout' => 30], $request->getOptions()); $request = $request->withOptions(['connect_timeout' => 20]); - $this->assertSame(['connect_timeout' => 20, 'http_errors' => true, 'timeout' => 30], $request->getOptions()); + $this->assertSame(['connect_timeout' => 20, 'crypto_method' => 33, 'http_errors' => true, 'timeout' => 30], $request->getOptions()); } public function testMultipleRequestsAreSentInThePool() @@ -2167,11 +2167,11 @@ public function testItCanSetAllowMaxRedirects(): void $request = $request->withOptions(['allow_redirects' => ['max' => 5]]); - $this->assertSame(['connect_timeout' => 10, 'http_errors' => false, 'timeout' => 30, 'allow_redirects' => ['max' => 5]], $request->getOptions()); + $this->assertSame(['connect_timeout' => 10, 'crypto_method' => 33, 'http_errors' => false, 'timeout' => 30, 'allow_redirects' => ['max' => 5]], $request->getOptions()); $request = $request->maxRedirects(10); - $this->assertSame(['connect_timeout' => 10, 'http_errors' => false, 'timeout' => 30, 'allow_redirects' => ['max' => 10]], $request->getOptions()); + $this->assertSame(['connect_timeout' => 10, 'crypto_method' => 33, 'http_errors' => false, 'timeout' => 30, 'allow_redirects' => ['max' => 10]], $request->getOptions()); } public function testPreventDuplicatedContentType(): void From 4326fc350de2d9e5b1578a0122a8c428895e8391 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 18 May 2023 08:52:31 -0500 Subject: [PATCH 17/99] [11.x] Adds `Dumpable` concern (#47122) * Adds `Support\Traits\Dumpable` * Use globals * Includes dependency * Migrates `Stringable` * Apply fixes from StyleCI * Migrates `EnumeratesValues` * Apply fixes from StyleCI * Uses var dumper * Simplifies * Removes include * Migrates `Builder` * Removes reference to `VarDumper` * Simplifies `VarDumper` * Apply fixes from StyleCI * Migrates `InteractsWithInput` * Apply fixes from StyleCI * Migrates `Carbon` * Migrates `TestResponse` * Apply fixes from StyleCI * MIgrates `Debugging` * Avoids BC * Uses `dump(...$args)` * Argument * Removes dependency * Makes `dump` consistent * Update Dumpable.php --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Enumerable.php | 3 +- .../Collections/Traits/EnumeratesValues.php | 25 +++------------ src/Illuminate/Database/Query/Builder.php | 22 ++++++------- .../Http/Concerns/InteractsWithInput.php | 19 +++-------- src/Illuminate/Support/Carbon.php | 26 ++------------- src/Illuminate/Support/Stringable.php | 21 +++--------- src/Illuminate/Support/Traits/Dumpable.php | 32 +++++++++++++++++++ .../Testing/Fluent/Concerns/Debugging.php | 15 +++------ src/Illuminate/Testing/TestResponse.php | 15 ++------- 9 files changed, 64 insertions(+), 114 deletions(-) create mode 100644 src/Illuminate/Support/Traits/Dumpable.php diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index a561488e8a59..0ed485161098 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -184,9 +184,10 @@ public function dd(...$args); /** * Dump the collection. * + * @param mixed ...$args * @return $this */ - public function dump(); + public function dump(...$args); /** * Get the items that are not present in the given items. diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index f9614963ae36..4c9439d85eab 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -12,7 +12,6 @@ use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; use JsonSerializable; -use Symfony\Component\VarDumper\VarDumper; use Traversable; use UnexpectedValueException; use UnitEnum; @@ -53,7 +52,7 @@ */ trait EnumeratesValues { - use Conditionable; + use Conditionable, Dumpable; /** * Indicates that the object's string representation should be escaped when __toString is invoked. @@ -195,31 +194,15 @@ public function some($key, $operator = null, $value = null) return $this->contains(...func_get_args()); } - /** - * Dump the items and end the script. - * - * @param mixed ...$args - * @return never - */ - public function dd(...$args) - { - $this->dump(...$args); - - exit(1); - } - /** * Dump the items. * + * @param mixed ...$args * @return $this */ - public function dump() + public function dump(...$args) { - (new Collection(func_get_args())) - ->push($this->all()) - ->each(function ($item) { - VarDumper::dump($item); - }); + dump($this->all(), ...$args); return $this; } diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 0b6c87ac8d9f..749a480eba49 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -21,6 +21,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use Illuminate\Support\Str; +use Illuminate\Support\Traits\Dumpable; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; @@ -29,7 +30,7 @@ class Builder implements BuilderContract { - use BuildsQueries, ExplainsQueries, ForwardsCalls, Macroable { + use BuildsQueries, Dumpable, ExplainsQueries, ForwardsCalls, Macroable { __call as macroCall; } @@ -3871,25 +3872,20 @@ public function cloneWithoutBindings(array $except) /** * Dump the current SQL and bindings. * + * @param mixed ...$args * @return $this */ - public function dump() + public function dump(...$args) { - dump($this->toSql(), $this->getBindings()); + dump( + $this->toSql(), + $this->getBindings(), + ...$args, + ); return $this; } - /** - * Die and dump the current SQL and bindings. - * - * @return never - */ - public function dd() - { - dd($this->toSql(), $this->getBindings()); - } - /** * Handle dynamic method calls into the method. * diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index 36dcec2fef4f..20150719db3d 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -5,13 +5,15 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Traits\Dumpable; use SplFileInfo; use stdClass; use Symfony\Component\HttpFoundation\InputBag; -use Symfony\Component\VarDumper\VarDumper; trait InteractsWithInput { + use Dumpable; + /** * Retrieve a server variable from the request. * @@ -606,19 +608,6 @@ protected function retrieveItem($source, $key, $default) return $this->$source->get($key, $default); } - /** - * Dump the request items and end the script. - * - * @param mixed ...$keys - * @return never - */ - public function dd(...$keys) - { - $this->dump(...$keys); - - exit(1); - } - /** * Dump the items. * @@ -629,7 +618,7 @@ public function dump($keys = []) { $keys = is_array($keys) ? $keys : func_get_args(); - VarDumper::dump(count($keys) > 0 ? $this->only($keys) : $this->all()); + dump(count($keys) > 0 ? $this->only($keys) : $this->all()); return $this; } diff --git a/src/Illuminate/Support/Carbon.php b/src/Illuminate/Support/Carbon.php index c1a665c7d0f8..1dcbae319b77 100644 --- a/src/Illuminate/Support/Carbon.php +++ b/src/Illuminate/Support/Carbon.php @@ -5,12 +5,13 @@ use Carbon\Carbon as BaseCarbon; use Carbon\CarbonImmutable as BaseCarbonImmutable; use Illuminate\Support\Traits\Conditionable; +use Illuminate\Support\Traits\Dumpable; use Ramsey\Uuid\Uuid; use Symfony\Component\Uid\Ulid; class Carbon extends BaseCarbon { - use Conditionable; + use Conditionable, Dumpable; /** * {@inheritdoc} @@ -33,27 +34,4 @@ public static function createFromId($id) ? static::createFromInterface(Ulid::fromString($id)->getDateTime()) : static::createFromInterface(Uuid::fromString($id)->getDateTime()); } - - /** - * Dump the instance and end the script. - * - * @param mixed ...$args - * @return never - */ - public function dd(...$args) - { - dd($this, ...$args); - } - - /** - * Dump the instance. - * - * @return $this - */ - public function dump() - { - dump($this); - - return $this; - } } diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 0cb76068e396..b2053151bf89 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -6,14 +6,14 @@ use Closure; use Illuminate\Support\Facades\Date; use Illuminate\Support\Traits\Conditionable; +use Illuminate\Support\Traits\Dumpable; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use JsonSerializable; -use Symfony\Component\VarDumper\VarDumper; class Stringable implements JsonSerializable, ArrayAccess { - use Conditionable, Macroable, Tappable; + use Conditionable, Dumpable, Macroable, Tappable; /** * The underlying string value. @@ -1128,27 +1128,16 @@ public function toHtmlString() /** * Dump the string. * + * @param mixed ...$args * @return $this */ - public function dump() + public function dump(...$args) { - VarDumper::dump($this->value); + dump($this->value, ...$args); return $this; } - /** - * Dump the string and end the script. - * - * @return never - */ - public function dd() - { - $this->dump(); - - exit(1); - } - /** * Get the underlying string value. * diff --git a/src/Illuminate/Support/Traits/Dumpable.php b/src/Illuminate/Support/Traits/Dumpable.php new file mode 100644 index 000000000000..448b5ba62c17 --- /dev/null +++ b/src/Illuminate/Support/Traits/Dumpable.php @@ -0,0 +1,32 @@ +dump(...$args); + + dd(); + } + + /** + * Dump the given arguments. + * + * @param mixed ...$args + * @return $this + */ + public function dump(...$args) + { + dump($this, ...$args); + + return $this; + } +} diff --git a/src/Illuminate/Testing/Fluent/Concerns/Debugging.php b/src/Illuminate/Testing/Fluent/Concerns/Debugging.php index e655556cf939..98726fbbdbbd 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Debugging.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Debugging.php @@ -2,8 +2,12 @@ namespace Illuminate\Testing\Fluent\Concerns; +use Illuminate\Support\Traits\Dumpable; + trait Debugging { + use Dumpable; + /** * Dumps the given props. * @@ -17,17 +21,6 @@ public function dump(string $prop = null): self return $this; } - /** - * Dumps the given props and exits. - * - * @param string|null $prop - * @return never - */ - public function dd(string $prop = null): void - { - dd($this->prop($prop)); - } - /** * Retrieve a prop within the current scope using "dot" notation. * diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 3fa40418d61a..4146ea4bff71 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -15,6 +15,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use Illuminate\Support\Traits\Dumpable; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use Illuminate\Support\ViewErrorBag; @@ -32,7 +33,7 @@ */ class TestResponse implements ArrayAccess { - use Concerns\AssertsStatusCodes, Tappable, Macroable { + use Concerns\AssertsStatusCodes, Dumpable, Tappable, Macroable { __call as macroCall; } @@ -1423,18 +1424,6 @@ protected function session() return $session; } - /** - * Dump the content from the response and end the script. - * - * @return never - */ - public function dd() - { - $this->dump(); - - exit(1); - } - /** * Dump the headers from the response and end the script. * From 98314c8b8ed343565133612ae4c87b3d60acad98 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 18 May 2023 13:53:15 +0000 Subject: [PATCH 18/99] 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 411663cbce42..0994ba3877c2 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -170,8 +170,8 @@ * @method static array allFiles() * @method static bool hasFile(string $key) * @method static \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null file(string|null $key = null, mixed $default = null) - * @method static never dd(mixed ...$keys) * @method static \Illuminate\Http\Request dump(mixed $keys = []) + * @method static never dd(mixed ...$args) * @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 c8229a1a8bc47e0e411c3986efc0e34bce24e665 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 23 May 2023 18:28:37 +0100 Subject: [PATCH 19/99] [11.x] Removed old `WithoutEvents` trait (#47180) * This trait no longer does anything, after withoutEvents was removed in Laravel 10 * Update TestCase.php --- .../Foundation/Testing/TestCase.php | 4 ---- .../Foundation/Testing/WithoutEvents.php | 22 ------------------- 2 files changed, 26 deletions(-) delete mode 100644 src/Illuminate/Foundation/Testing/WithoutEvents.php diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index a7315a2c5124..8d8f23e57254 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -142,10 +142,6 @@ protected function setUpTraits() $this->disableMiddlewareForAllTests(); } - if (isset($uses[WithoutEvents::class])) { - $this->disableEventsForAllTests(); - } - if (isset($uses[WithFaker::class])) { $this->setUpFaker(); } diff --git a/src/Illuminate/Foundation/Testing/WithoutEvents.php b/src/Illuminate/Foundation/Testing/WithoutEvents.php deleted file mode 100644 index fa5df3ce8f5b..000000000000 --- a/src/Illuminate/Foundation/Testing/WithoutEvents.php +++ /dev/null @@ -1,22 +0,0 @@ -withoutEvents(); - } else { - throw new Exception('Unable to disable events. ApplicationTrait not used.'); - } - } -} From a9b38c8c41177ca51f5653d69d04be9e00b62e90 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 23 May 2023 19:28:51 +0200 Subject: [PATCH 20/99] [11.x] Fix tests (#47176) * Fix tests * Temporarily revert explicit coercion --------- Co-authored-by: Nuno Maduro --- tests/Support/SupportCollectionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index c1feb9ab4d5d..8ace98757dc8 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -4383,7 +4383,7 @@ public function testDump($collection) (new $collection([1, 2, 3]))->dump('one', 'two'); - $this->assertSame(['one', 'two', [1, 2, 3]], $log->all()); + $this->assertSame([[1, 2, 3], 'one', 'two'], $log->all()); VarDumper::setHandler(null); } From 4d68b3a08f8b9c1eed850a1e2e8ad5f0aebada6a Mon Sep 17 00:00:00 2001 From: Ramon Rietdijk Date: Tue, 23 May 2023 19:33:58 +0200 Subject: [PATCH 21/99] Added colon to UniqueLock key (#47164) --- src/Illuminate/Bus/UniqueLock.php | 2 +- tests/Integration/Broadcasting/BroadcastManagerTest.php | 2 +- tests/Integration/Queue/JobDispatchingTest.php | 2 +- tests/Integration/Queue/UniqueJobTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Bus/UniqueLock.php b/src/Illuminate/Bus/UniqueLock.php index a4066b77c1c6..dea12303b719 100644 --- a/src/Illuminate/Bus/UniqueLock.php +++ b/src/Illuminate/Bus/UniqueLock.php @@ -70,6 +70,6 @@ protected function getKey($job) ? $job->uniqueId() : ($job->uniqueId ?? ''); - return 'laravel_unique_job:'.get_class($job).$uniqueId; + return 'laravel_unique_job:'.get_class($job).':'.$uniqueId; } } diff --git a/tests/Integration/Broadcasting/BroadcastManagerTest.php b/tests/Integration/Broadcasting/BroadcastManagerTest.php index d23bff703f02..847c1f4cdb62 100644 --- a/tests/Integration/Broadcasting/BroadcastManagerTest.php +++ b/tests/Integration/Broadcasting/BroadcastManagerTest.php @@ -51,7 +51,7 @@ public function testUniqueEventsCanBeBroadcast() Bus::assertNotDispatched(UniqueBroadcastEvent::class); Queue::assertPushed(UniqueBroadcastEvent::class); - $lockKey = 'laravel_unique_job:'.UniqueBroadcastEvent::class.TestEventUnique::class; + $lockKey = 'laravel_unique_job:'.UniqueBroadcastEvent::class.':'.TestEventUnique::class; $this->assertFalse($this->app->get(Cache::class)->lock($lockKey, 10)->get()); } diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index f7fe354180a8..cbd5c65fba60 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -117,7 +117,7 @@ public function testUniqueJobLockIsReleasedForJobDispatchedAfterResponse() */ private function getJobLock($job, $value = null) { - return $this->app->get(Repository::class)->lock('laravel_unique_job:'.$job.$value, 10)->get(); + return $this->app->get(Repository::class)->lock('laravel_unique_job:'.$job.':'.$value, 10)->get(); } } diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index be5dfe3c1c4f..ef0809d239ec 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -150,7 +150,7 @@ public function testLockCanBeReleasedBeforeProcessing() protected function getLockKey($job) { - return 'laravel_unique_job:'.(is_string($job) ? $job : get_class($job)); + return 'laravel_unique_job:'.(is_string($job) ? $job : get_class($job)).':'; } } From ccea96095393ca0ae36e374f2121363c496ded78 Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Wed, 24 May 2023 18:00:17 +0400 Subject: [PATCH 22/99] [11.x] Add methods to `Enumerable` contract (#47189) * Add methods to Enumerable contract * Add missing arguments --------- Co-authored-by: Volodya Khurshudyan --- src/Illuminate/Collections/Enumerable.php | 26 +++++++++++++++++++ src/Illuminate/Collections/LazyCollection.php | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 0ed485161098..559e2ecbfec7 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -565,6 +565,32 @@ public function implode($value, $glue = null); */ public function intersect($items); + /** + * Intersect the collection with the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectUsing($items, callable $callback); + + /** + * Intersect the collection with the given items with additional index check. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectAssoc($items); + + /** + * Intersect the collection with the given items with additional index check, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectAssocUsing($items, callable $callback); + /** * Intersect the collection with the given items by key. * diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 39c7ecc4f8a9..cdb69a8af6ca 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -638,7 +638,7 @@ public function intersect($items) * @param callable(TValue, TValue): int $callback * @return static */ - public function intersectUsing() + public function intersectUsing($items, callable $callback) { return $this->passthru('intersectUsing', func_get_args()); } From f632b12203c708063cababbc8e1ae5de08704356 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 8 Jun 2023 14:42:04 -0500 Subject: [PATCH 23/99] [11.x] Stop magic prefixing (#47380) * remove magic prefixing in some cache drivers * update memcached tests --- src/Illuminate/Cache/DynamoDbStore.php | 2 +- src/Illuminate/Cache/MemcachedStore.php | 2 +- src/Illuminate/Cache/RedisStore.php | 2 +- tests/Cache/CacheMemcachedStoreTest.php | 8 ++++---- tests/Cache/CacheRedisStoreTest.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Cache/DynamoDbStore.php b/src/Illuminate/Cache/DynamoDbStore.php index 73aef8d612cf..8e2fda7ff55e 100644 --- a/src/Illuminate/Cache/DynamoDbStore.php +++ b/src/Illuminate/Cache/DynamoDbStore.php @@ -531,7 +531,7 @@ public function getPrefix() */ public function setPrefix($prefix) { - $this->prefix = ! empty($prefix) ? $prefix.':' : ''; + $this->prefix = $prefix; } /** diff --git a/src/Illuminate/Cache/MemcachedStore.php b/src/Illuminate/Cache/MemcachedStore.php index 299dab9ad2e4..27a69d5c2da1 100755 --- a/src/Illuminate/Cache/MemcachedStore.php +++ b/src/Illuminate/Cache/MemcachedStore.php @@ -274,6 +274,6 @@ public function getPrefix() */ public function setPrefix($prefix) { - $this->prefix = ! empty($prefix) ? $prefix.':' : ''; + $this->prefix = $prefix; } } diff --git a/src/Illuminate/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index 4d03745147e9..f336560e0fed 100755 --- a/src/Illuminate/Cache/RedisStore.php +++ b/src/Illuminate/Cache/RedisStore.php @@ -392,7 +392,7 @@ public function getPrefix() */ public function setPrefix($prefix) { - $this->prefix = ! empty($prefix) ? $prefix.':' : ''; + $this->prefix = $prefix; } /** diff --git a/tests/Cache/CacheMemcachedStoreTest.php b/tests/Cache/CacheMemcachedStoreTest.php index 367a8ca1b655..a61af86e16f2 100755 --- a/tests/Cache/CacheMemcachedStoreTest.php +++ b/tests/Cache/CacheMemcachedStoreTest.php @@ -26,7 +26,7 @@ public function testGetReturnsNullWhenNotFound() $memcache = $this->getMockBuilder(stdClass::class)->addMethods(['get', 'getResultCode'])->getMock(); $memcache->expects($this->once())->method('get')->with($this->equalTo('foo:bar'))->willReturn(null); $memcache->expects($this->once())->method('getResultCode')->willReturn(1); - $store = new MemcachedStore($memcache, 'foo'); + $store = new MemcachedStore($memcache, 'foo:'); $this->assertNull($store->get('bar')); } @@ -48,7 +48,7 @@ public function testMemcacheGetMultiValuesAreReturnedWithCorrectKeys() 'fizz', 'buzz', 'norf', ]); $memcache->expects($this->once())->method('getResultCode')->willReturn(0); - $store = new MemcachedStore($memcache, 'foo'); + $store = new MemcachedStore($memcache, 'foo:'); $this->assertEquals([ 'foo' => 'fizz', 'bar' => 'buzz', @@ -116,9 +116,9 @@ public function testFlushesCached() public function testGetAndSetPrefix() { $store = new MemcachedStore(new Memcached, 'bar'); - $this->assertSame('bar:', $store->getPrefix()); + $this->assertSame('bar', $store->getPrefix()); $store->setPrefix('foo'); - $this->assertSame('foo:', $store->getPrefix()); + $this->assertSame('foo', $store->getPrefix()); $store->setPrefix(null); $this->assertEmpty($store->getPrefix()); } diff --git a/tests/Cache/CacheRedisStoreTest.php b/tests/Cache/CacheRedisStoreTest.php index 2ab6fce62c53..30a100b22e8f 100755 --- a/tests/Cache/CacheRedisStoreTest.php +++ b/tests/Cache/CacheRedisStoreTest.php @@ -143,13 +143,13 @@ public function testGetAndSetPrefix() $redis = $this->getRedis(); $this->assertSame('prefix:', $redis->getPrefix()); $redis->setPrefix('foo'); - $this->assertSame('foo:', $redis->getPrefix()); + $this->assertSame('foo', $redis->getPrefix()); $redis->setPrefix(null); $this->assertEmpty($redis->getPrefix()); } protected function getRedis() { - return new RedisStore(m::mock(Factory::class), 'prefix'); + return new RedisStore(m::mock(Factory::class), 'prefix:'); } } From 35baf526f9ab4562d262ba55883e8c775ae93370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20C=C4=83lin?= <28299559+iRealWorlds@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:26:00 +0300 Subject: [PATCH 24/99] [11.x] Extend the UrlGenerator contract (#47437) * Extend the UrlGenerator contract. * Update UrlGenerator.php --------- Co-authored-by: Taylor Otwell --- .../Contracts/Routing/UrlGenerator.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Illuminate/Contracts/Routing/UrlGenerator.php b/src/Illuminate/Contracts/Routing/UrlGenerator.php index cca221cc7212..a742862aed4d 100644 --- a/src/Illuminate/Contracts/Routing/UrlGenerator.php +++ b/src/Illuminate/Contracts/Routing/UrlGenerator.php @@ -59,6 +59,30 @@ public function asset($path, $secure = null); */ public function route($name, $parameters = [], $absolute = true); + /** + * Create a signed route URL for a named route. + * + * @param string $name + * @param mixed $parameters + * @param \DateTimeInterface|\DateInterval|int|null $expiration + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function signedRoute($name, $parameters = [], $expiration = null, $absolute = true); + + /** + * Create a temporary signed route URL for a named route. + * + * @param string $name + * @param \DateTimeInterface|\DateInterval|int $expiration + * @param array $parameters + * @param bool $absolute + * @return string + */ + public function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true); + /** * Get the URL to a controller action. * From 84ac88e9b130a80bdc1ce69dfcdbb66a52b422ff Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:12:53 -0400 Subject: [PATCH 25/99] [11.x] Format Task runtime to be more human-readable (#47434) * make task runtime human-readable * rename method --------- Co-authored-by: Taylor Otwell --- .../Console/View/Components/Task.php | 5 ++++- src/Illuminate/Queue/Console/WorkCommand.php | 20 ++++--------------- src/Illuminate/Support/InteractsWithTime.php | 19 ++++++++++++++++++ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Console/View/Components/Task.php b/src/Illuminate/Console/View/Components/Task.php index c5b326b17102..ee743eaed028 100644 --- a/src/Illuminate/Console/View/Components/Task.php +++ b/src/Illuminate/Console/View/Components/Task.php @@ -2,6 +2,7 @@ namespace Illuminate\Console\View\Components; +use Illuminate\Support\InteractsWithTime; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -9,6 +10,8 @@ class Task extends Component { + use InteractsWithTime; + /** * Renders the component using the given arguments. * @@ -39,7 +42,7 @@ public function render($description, $task = null, $verbosity = OutputInterface: throw $e; } finally { $runTime = $task - ? (' '.number_format((microtime(true) - $startTime) * 1000).'ms') + ? (' '.$this->runTimeForHumans($startTime)) : ''; $runTimeWidth = mb_strlen($runTime); diff --git a/src/Illuminate/Queue/Console/WorkCommand.php b/src/Illuminate/Queue/Console/WorkCommand.php index b4ed14443f62..9e39681393df 100644 --- a/src/Illuminate/Queue/Console/WorkCommand.php +++ b/src/Illuminate/Queue/Console/WorkCommand.php @@ -13,6 +13,7 @@ use Illuminate\Queue\Worker; use Illuminate\Queue\WorkerOptions; use Illuminate\Support\Carbon; +use Illuminate\Support\InteractsWithTime; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Terminal; @@ -21,6 +22,8 @@ #[AsCommand(name: 'queue:work')] class WorkCommand extends Command { + use InteractsWithTime; + /** * The console command name. * @@ -217,7 +220,7 @@ protected function writeOutput(Job $job, $status) return $this->output->writeln(' RUNNING'); } - $runTime = $this->formatRunTime($this->latestStartedAt); + $runTime = $this->runTimeForHumans($this->latestStartedAt); $dots = max(terminal()->width() - mb_strlen($job->resolveName()) - ( $this->output->isVerbose() ? (mb_strlen($job->getJobId()) + 1) : 0 @@ -250,21 +253,6 @@ protected function now() return Carbon::now(); } - /** - * Given a start time, format the total run time for human readability. - * - * @param float $startTime - * @return string - */ - protected function formatRunTime($startTime) - { - $runTime = (microtime(true) - $startTime) * 1000; - - return $runTime > 1000 - ? CarbonInterval::milliseconds($runTime)->cascade()->forHumans(short: true) - : number_format($runTime, 2).'ms'; - } - /** * Store a failed job event. * diff --git a/src/Illuminate/Support/InteractsWithTime.php b/src/Illuminate/Support/InteractsWithTime.php index 2b617c392a5a..6a64f12a4ce4 100644 --- a/src/Illuminate/Support/InteractsWithTime.php +++ b/src/Illuminate/Support/InteractsWithTime.php @@ -2,6 +2,7 @@ namespace Illuminate\Support; +use Carbon\CarbonInterval; use DateInterval; use DateTimeInterface; @@ -61,4 +62,22 @@ protected function currentTime() { return Carbon::now()->getTimestamp(); } + + /** + * Given a start time, format the total run time for human readability. + * + * @param float $startTime + * @param float $endTime + * @return string + */ + protected function runTimeForHumans($startTime, $endTime = null) + { + $endTime ??= microtime(true); + + $runTime = ($endTime - $startTime) * 1000; + + return $runTime > 1000 + ? CarbonInterval::milliseconds($runTime)->cascade()->forHumans(short: true) + : number_format($runTime, 2).'ms'; + } } From ecaf5e354449f57058144439a04d1e840672abc7 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 15 Jun 2023 20:13:09 +0000 Subject: [PATCH 26/99] Apply fixes from StyleCI --- src/Illuminate/Queue/Console/WorkCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Queue/Console/WorkCommand.php b/src/Illuminate/Queue/Console/WorkCommand.php index 9e39681393df..a8a788e9fdf5 100644 --- a/src/Illuminate/Queue/Console/WorkCommand.php +++ b/src/Illuminate/Queue/Console/WorkCommand.php @@ -2,7 +2,6 @@ namespace Illuminate\Queue\Console; -use Carbon\CarbonInterval; use Illuminate\Console\Command; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Queue\Job; From 8647dcf18e6e315e97daea9ed60ef79b420ad327 Mon Sep 17 00:00:00 2001 From: Chris Kankiewicz Date: Sun, 18 Jun 2023 11:30:20 -0700 Subject: [PATCH 27/99] [11.x] Prevent attributes cast to an enum from being set to another enum (#47465) * Prevent attributes cast to an enum from being set to another enum * formatting --------- Co-authored-by: Taylor Otwell --- .../Eloquent/Concerns/HasAttributes.php | 14 +++++--- .../Database/EloquentModelEnumCastingTest.php | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index d4f1932bf9ec..b96dbca9af88 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -39,6 +39,7 @@ use ReflectionClass; use ReflectionMethod; use ReflectionNamedType; +use ValueError; trait HasAttributes { @@ -309,7 +310,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt } if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) { - $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($attributes[$key]) : null; + $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($this->getCasts()[$key], $attributes[$key]) : null; } if ($attributes[$key] instanceof Arrayable) { @@ -1155,10 +1156,10 @@ protected function setEnumCastableAttribute($key, $value) if (! isset($value)) { $this->attributes[$key] = null; } elseif (is_object($value)) { - $this->attributes[$key] = $this->getStorableEnumValue($value); + $this->attributes[$key] = $this->getStorableEnumValue($enumClass, $value); } else { $this->attributes[$key] = $this->getStorableEnumValue( - $this->getEnumCaseFromValue($enumClass, $value) + $enumClass, $this->getEnumCaseFromValue($enumClass, $value) ); } } @@ -1180,11 +1181,16 @@ protected function getEnumCaseFromValue($enumClass, $value) /** * Get the storable value from the given enum. * + * @param string $expectedEnum * @param \UnitEnum|\BackedEnum $value * @return string|int */ - protected function getStorableEnumValue($value) + protected function getStorableEnumValue($expectedEnum, $value) { + if (! $value instanceof $expectedEnum) { + throw new ValueError(sprintf('Value [%s] is not of the expected enum type [%s].', var_export($value, true), $expectedEnum)); + } + return $value instanceof BackedEnum ? $value->value : $value->name; diff --git a/tests/Integration/Database/EloquentModelEnumCastingTest.php b/tests/Integration/Database/EloquentModelEnumCastingTest.php index c0a47fbf8ce8..fee3c545bfa5 100644 --- a/tests/Integration/Database/EloquentModelEnumCastingTest.php +++ b/tests/Integration/Database/EloquentModelEnumCastingTest.php @@ -8,6 +8,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use ValueError; include 'Enums.php'; @@ -264,6 +265,39 @@ public function testFirstOrCreate() $this->assertEquals(StringStatus::pending, $model->string_status); $this->assertEquals(StringStatus::done, $model2->string_status); } + + public function testAttributeCastToAnEnumCanNotBeSetToAnotherEnum(): void + { + $model = new EloquentModelEnumCastingTestModel; + + $this->expectException(ValueError::class); + $this->expectExceptionMessage( + sprintf('Value [%s] is not of the expected enum type [%s].', var_export(ArrayableStatus::pending, true), StringStatus::class) + ); + + $model->string_status = ArrayableStatus::pending; + } + + public function testAttributeCastToAnEnumCanNotBeSetToAValueNotDefinedOnTheEnum(): void + { + $model = new EloquentModelEnumCastingTestModel; + + $this->expectException(ValueError::class); + $this->expectExceptionMessage( + sprintf('"unexpected_value" is not a valid backing value for enum %s', StringStatus::class) + ); + + $model->string_status = 'unexpected_value'; + } + + public function testAnAttributeWithoutACastCanBeSetToAnEnum(): void + { + $model = new EloquentModelEnumCastingTestModel; + + $model->non_enum_status = StringStatus::pending; + + $this->assertEquals(StringStatus::pending, $model->non_enum_status); + } } class EloquentModelEnumCastingTestModel extends Model From 55bd0753fbeb0e36a908ddbd1683ee30eab8952c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 18 Jun 2023 20:05:02 +0100 Subject: [PATCH 28/99] [11.x] Look for `GuzzleHttp\ClientInterface` first and make sure timeouts and min TLS are applied when falling back (#47404) * Look for `GuzzleHttp\ClientInterface` first and make sure timeouts and min TLS are applied when falling back * formatting * Update Event.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Console/Scheduling/Event.php | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 0ff10188b251..9ff1fa867c42 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -5,6 +5,7 @@ use Closure; use Cron\CronExpression; use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; use GuzzleHttp\Exception\TransferException; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Debug\ExceptionHandler; @@ -597,15 +598,34 @@ public function pingOnFailure($url) */ protected function pingCallback($url) { - return function (Container $container, HttpClient $http) use ($url) { + return function (Container $container) use ($url) { try { - $http->request('GET', $url); + $this->getHttpClient($container)->request('GET', $url); } catch (ClientExceptionInterface|TransferException $e) { $container->make(ExceptionHandler::class)->report($e); } }; } + /** + * Get the Guzzle HTTP client to use to send pings. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return \GuzzleHttp\ClientInterface + */ + protected function getHttpClient(Container $container) + { + return match (true) { + $container->bound(HttpClientInterface::class) => $container->make(HttpClientInterface::class), + $container->bound(HttpClient::class) => $container->make(HttpClient::class), + default => new HttpClient([ + 'connect_timeout' => 10, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + 'timeout' => 30, + ]), + }; + } + /** * State that the command should run in the background. * From 1e8bb5248e389a19fb952821307956b7984d8f5f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 27 Jun 2023 19:32:40 +0100 Subject: [PATCH 29/99] Add `scalar` function to the database connection interface (#47590) --- src/Illuminate/Database/ConnectionInterface.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Database/ConnectionInterface.php b/src/Illuminate/Database/ConnectionInterface.php index ee388694adfa..288adb4206e3 100755 --- a/src/Illuminate/Database/ConnectionInterface.php +++ b/src/Illuminate/Database/ConnectionInterface.php @@ -33,6 +33,18 @@ public function raw($value); */ public function selectOne($query, $bindings = [], $useReadPdo = true); + /** + * Run a select statement and return the first column of the first row. + * + * @param string $query + * @param array $bindings + * @param bool $useReadPdo + * @return mixed + * + * @throws \Illuminate\Database\MultipleColumnsSelectedException + */ + public function scalar($query, $bindings = [], $useReadPdo = true); + /** * Run a select statement against the database. * From ceb3f18f1548341e8dd41e208f12a68ee615cb64 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 28 Jun 2023 10:51:58 -0500 Subject: [PATCH 30/99] [11.x] `assertSee` handle empty values (#47597) * fail when `assertSee` is passed an empty value currently when passing an empty value to `assertSee` the test will pass. these values include: - `null` - `[]` - `''` - `['foo', '', 'bar']` rarely (IMO) is this the desired behavior, and may create false confidence in the test. this change will fail the test by default when passed empty values, but also allow users to override if appropriate. * minor formatting * minor formatting * Update TestResponse.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Testing/TestResponse.php | 10 ++++- tests/Testing/TestResponseTest.php | 56 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 0c0230302c80..5b54215f7706 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -518,13 +518,21 @@ public function assertStreamedContent($value) * @param bool $escape * @return $this */ - public function assertSee($value, $escape = true) + public function assertSee($value, $escape = true, $allowEmptyValues = false) { $value = Arr::wrap($value); + if (! $allowEmptyValues && empty($value)) { + PHPUnit::fail('An empty value was passed to `assertSee`.'); + } + $values = $escape ? array_map('e', $value) : $value; foreach ($values as $value) { + if (! $allowEmptyValues && (string) $value === '') { + PHPUnit::fail('An empty value was passed to `assertSee`.'); + } + PHPUnit::assertStringContainsString((string) $value, $this->getContent()); } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 839593b936e0..439f1b420aec 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -329,6 +329,62 @@ public function testAssertSeeEscapedCanFail() $response->assertSee(['bar & baz', 'baz & qux']); } + public function testAssertSeeCatchesNullValue() + { + $this->expectException(AssertionFailedError::class); + + $response = $this->makeMockResponse([ + 'render' => '
  • foo
  • bar
  • baz
  • foo
', + ]); + + $response->assertSee(null); + } + + public function testAssertSeeCatchesEmptyArray() + { + $this->expectException(AssertionFailedError::class); + + $response = $this->makeMockResponse([ + 'render' => '
  • foo
  • bar
  • baz
  • foo
', + ]); + + $response->assertSee([]); + } + + public function testAssertSeeCatchesEmptyString() + { + $this->expectException(AssertionFailedError::class); + + $response = $this->makeMockResponse([ + 'render' => '
  • foo
  • bar
  • baz
  • foo
', + ]); + + $response->assertSee(''); + } + + public function testAssertSeeCatchesEmptyStringInArray() + { + $this->expectException(AssertionFailedError::class); + + $response = $this->makeMockResponse([ + 'render' => '
  • foo
  • bar
  • baz
  • foo
', + ]); + + $response->assertSee(['foo', '', 'bar']); + } + + public function testAssertSeeAllowsEmptyValues() + { + $response = $this->makeMockResponse([ + 'render' => '
  • foo
  • bar
  • baz
  • foo
', + ]); + + $response->assertSee(null, allowEmptyValues: true); + $response->assertSee([], allowEmptyValues: true); + $response->assertSee('', allowEmptyValues: true); + $response->assertSee(['foo', '', 'bar'], allowEmptyValues: true); + } + public function testAssertSeeInOrder() { $response = $this->makeMockResponse([ From 72b50179058f4171c3cea1eb6e521366794a5f7b Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 28 Jun 2023 10:53:46 -0500 Subject: [PATCH 31/99] Revert "[11.x] `assertSee` handle empty values (#47597)" (#47604) This reverts commit ceb3f18f1548341e8dd41e208f12a68ee615cb64. --- src/Illuminate/Testing/TestResponse.php | 10 +---- tests/Testing/TestResponseTest.php | 56 ------------------------- 2 files changed, 1 insertion(+), 65 deletions(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 5b54215f7706..0c0230302c80 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -518,21 +518,13 @@ public function assertStreamedContent($value) * @param bool $escape * @return $this */ - public function assertSee($value, $escape = true, $allowEmptyValues = false) + public function assertSee($value, $escape = true) { $value = Arr::wrap($value); - if (! $allowEmptyValues && empty($value)) { - PHPUnit::fail('An empty value was passed to `assertSee`.'); - } - $values = $escape ? array_map('e', $value) : $value; foreach ($values as $value) { - if (! $allowEmptyValues && (string) $value === '') { - PHPUnit::fail('An empty value was passed to `assertSee`.'); - } - PHPUnit::assertStringContainsString((string) $value, $this->getContent()); } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 439f1b420aec..839593b936e0 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -329,62 +329,6 @@ public function testAssertSeeEscapedCanFail() $response->assertSee(['bar & baz', 'baz & qux']); } - public function testAssertSeeCatchesNullValue() - { - $this->expectException(AssertionFailedError::class); - - $response = $this->makeMockResponse([ - 'render' => '
  • foo
  • bar
  • baz
  • foo
', - ]); - - $response->assertSee(null); - } - - public function testAssertSeeCatchesEmptyArray() - { - $this->expectException(AssertionFailedError::class); - - $response = $this->makeMockResponse([ - 'render' => '
  • foo
  • bar
  • baz
  • foo
', - ]); - - $response->assertSee([]); - } - - public function testAssertSeeCatchesEmptyString() - { - $this->expectException(AssertionFailedError::class); - - $response = $this->makeMockResponse([ - 'render' => '
  • foo
  • bar
  • baz
  • foo
', - ]); - - $response->assertSee(''); - } - - public function testAssertSeeCatchesEmptyStringInArray() - { - $this->expectException(AssertionFailedError::class); - - $response = $this->makeMockResponse([ - 'render' => '
  • foo
  • bar
  • baz
  • foo
', - ]); - - $response->assertSee(['foo', '', 'bar']); - } - - public function testAssertSeeAllowsEmptyValues() - { - $response = $this->makeMockResponse([ - 'render' => '
  • foo
  • bar
  • baz
  • foo
', - ]); - - $response->assertSee(null, allowEmptyValues: true); - $response->assertSee([], allowEmptyValues: true); - $response->assertSee('', allowEmptyValues: true); - $response->assertSee(['foo', '', 'bar'], allowEmptyValues: true); - } - public function testAssertSeeInOrder() { $response = $this->makeMockResponse([ From 7df3c90403d2049081d8a935e75a9a555e547bba Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 29 Jun 2023 05:21:40 +0800 Subject: [PATCH 32/99] [11.x] Allow `TestResponse` to accept `Illuminate\Http\Request` (#47418) * [11.x] Allow `TestResponse` to accept `Illuminate\Http\Request` This allows use to make assertion on the request from `TestResponse` Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../Testing/Concerns/MakesHttpRequests.php | 7 ++++--- src/Illuminate/Testing/TestResponse.php | 16 +++++++++++++--- tests/Integration/Routing/FallbackRouteTest.php | 9 +++++++-- .../Routing/ImplicitModelRouteBindingTest.php | 4 ++++ tests/Integration/Routing/RouteViewTest.php | 13 +++++++++++-- tests/Integration/Routing/SimpleRouteTest.php | 6 ++++++ tests/Integration/Routing/UrlSigningTest.php | 16 +++++++++++++--- 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 8211e96691a1..169fbae604ae 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -570,7 +570,7 @@ public function call($method, $uri, $parameters = [], $cookies = [], $files = [] $response = $this->followRedirects($response); } - return static::$latestResponse = $this->createTestResponse($response); + return static::$latestResponse = $this->createTestResponse($response, $request); } /** @@ -692,11 +692,12 @@ protected function followRedirects($response) * Create the test response instance from the given response. * * @param \Illuminate\Http\Response $response + * @param \Illuminate\Http\Request $request * @return \Illuminate\Testing\TestResponse */ - protected function createTestResponse($response) + protected function createTestResponse($response, $request) { - return tap(TestResponse::fromBaseResponse($response), function ($response) { + return tap(TestResponse::fromBaseResponse($response, $request), function ($response) { $response->withExceptions( $this->app->bound(LoggedExceptionCollection::class) ? $this->app->make(LoggedExceptionCollection::class) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 0c0230302c80..40d0dfa3973b 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -37,6 +37,13 @@ class TestResponse implements ArrayAccess __call as macroCall; } + /** + * The original request. + * + * @var \Illuminate\Http\Request|null + */ + public $baseRequest; + /** * The response to delegate to. * @@ -62,11 +69,13 @@ class TestResponse implements ArrayAccess * Create a new test response instance. * * @param \Illuminate\Http\Response $response + * @param \Illuminate\Http\Request|null $request * @return void */ - public function __construct($response) + public function __construct($response, $request = null) { $this->baseResponse = $response; + $this->baseRequest = $request; $this->exceptions = new Collection; } @@ -74,11 +83,12 @@ public function __construct($response) * Create a new TestResponse from another response. * * @param \Illuminate\Http\Response $response + * @param \Illuminate\Http\Request|null $request * @return static */ - public static function fromBaseResponse($response) + public static function fromBaseResponse($response, $request = null) { - return new static($response); + return new static($response, $request); } /** diff --git a/tests/Integration/Routing/FallbackRouteTest.php b/tests/Integration/Routing/FallbackRouteTest.php index 110eaf517473..9a621ab8fd78 100644 --- a/tests/Integration/Routing/FallbackRouteTest.php +++ b/tests/Integration/Routing/FallbackRouteTest.php @@ -55,8 +55,13 @@ public function testFallbackWithWildcards() })->where('any', '.*'); $this->assertStringContainsString('one', $this->get('/one')->getContent()); - $this->assertStringContainsString('wildcard', $this->get('/non-existing')->getContent()); - $this->assertEquals(200, $this->get('/non-existing')->getStatusCode()); + + tap($this->get('/non-existing'), function ($response) { + $this->assertStringContainsString('wildcard', $response->getContent()); + $this->assertEquals(200, $response->getStatusCode()); + + $this->assertSame('non-existing', $response->baseRequest->route('any')); + }); } public function testNoRoutes() diff --git a/tests/Integration/Routing/ImplicitModelRouteBindingTest.php b/tests/Integration/Routing/ImplicitModelRouteBindingTest.php index c0a41e640064..d2cf32096222 100644 --- a/tests/Integration/Routing/ImplicitModelRouteBindingTest.php +++ b/tests/Integration/Routing/ImplicitModelRouteBindingTest.php @@ -107,6 +107,8 @@ public function testWithoutRouteCachingEnabled() 'id' => $user->id, 'name' => $user->name, ]); + + $this->assertTrue($user->is($response->baseRequest->route('user'))); } public function testSoftDeletedModelsAreNotRetrieved() @@ -144,6 +146,8 @@ public function testSoftDeletedModelsCanBeRetrievedUsingWithTrashedMethod() 'id' => $user->id, 'name' => $user->name, ]); + + $this->assertTrue($user->is($response->baseRequest->route('user'))); } public function testEnforceScopingImplicitRouteBindings() diff --git a/tests/Integration/Routing/RouteViewTest.php b/tests/Integration/Routing/RouteViewTest.php index 3bc9d1c0867a..88fc61081ea9 100644 --- a/tests/Integration/Routing/RouteViewTest.php +++ b/tests/Integration/Routing/RouteViewTest.php @@ -27,8 +27,17 @@ public function testRouteViewWithParams() $this->assertStringContainsString('Test bar', $this->get('/route/value1/value2')->getContent()); $this->assertStringContainsString('Test bar', $this->get('/route/value1')->getContent()); - $this->assertEquals('value1', $this->get('/route/value1/value2')->viewData('param')); - $this->assertEquals('value2', $this->get('/route/value1/value2')->viewData('param2')); + tap($this->get('/route/value1/value2'), function ($response) { + $this->assertEquals('value1', $response->viewData('param')); + $this->assertEquals('value1', $response->baseRequest->route('param')); + $this->assertEquals('value2', $response->baseRequest->route('param2')); + }); + + tap($this->get('/route/value1/value2'), function ($response) { + $this->assertEquals('value2', $response->viewData('param2')); + $this->assertEquals('value1', $response->baseRequest->route('param')); + $this->assertEquals('value2', $response->baseRequest->route('param2')); + }); } public function testRouteViewWithStatus() diff --git a/tests/Integration/Routing/SimpleRouteTest.php b/tests/Integration/Routing/SimpleRouteTest.php index 1515140852bc..67228a1a1f69 100644 --- a/tests/Integration/Routing/SimpleRouteTest.php +++ b/tests/Integration/Routing/SimpleRouteTest.php @@ -16,5 +16,11 @@ public function testSimpleRouteThroughTheFramework() $response = $this->get('/'); $this->assertSame('Hello World', $response->content()); + + $response = $this->get('/?foo=bar'); + + $this->assertSame('Hello World', $response->content()); + + $this->assertSame('bar', $response->baseRequest->query('foo')); } } diff --git a/tests/Integration/Routing/UrlSigningTest.php b/tests/Integration/Routing/UrlSigningTest.php index fbaf4e7a10fc..c728176c5059 100644 --- a/tests/Integration/Routing/UrlSigningTest.php +++ b/tests/Integration/Routing/UrlSigningTest.php @@ -33,7 +33,12 @@ public function testSigningUrl() })->name('foo'); $this->assertIsString($url = URL::signedRoute('foo', ['id' => 1])); - $this->assertSame('valid', $this->get($url)->original); + + tap($this->get($url), function ($response) { + $this->assertSame('valid', $response->original); + + $this->assertIsString($response->baseRequest->query('signature')); + }); } public function testSigningUrlWithCustomRouteSlug() @@ -46,8 +51,13 @@ public function testSigningUrlWithCustomRouteSlug() $model->routable = 'routable-slug'; $this->assertIsString($url = URL::signedRoute('foo', ['post' => $model])); - $this->assertSame('valid', $this->get($url)->original['valid']); - $this->assertSame('routable-slug', $this->get($url)->original['slug']); + + tap($this->get($url), function ($response) { + $this->assertSame('valid', $response->original['valid']); + $this->assertSame('routable-slug', $response->original['slug']); + + $this->assertSame('routable-slug', $response->baseRequest->route('post')); + }); } public function testTemporarySignedUrls() From d298df7c93a6fd6f9c34af27dc883a3874b0f6aa Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 28 Jun 2023 22:45:00 +0100 Subject: [PATCH 33/99] [11.x] Adds `Model::casts()` method and named static methods for built-in casters (#47237) * Adds `Model::casts()` method * Apply fixes from StyleCI * Adds `AsCollection::using` * Adds `AsEncryptedCollection::using` * Adds `AsEnumArrayObject::using` * Adjusts tests * Fixes docs * Adds `AsEnumCollection::using` * Adds array format support * Tests priority * Tests serialization * Method name * Fixes single element in array * Fixes single element in array * formatting * Update src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php Co-authored-by: Enzo Innocenzi * Adjusts changes after rebase --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell Co-authored-by: Enzo Innocenzi --- .../Database/Eloquent/Casts/AsCollection.php | 11 + .../Eloquent/Casts/AsEncryptedCollection.php | 11 + .../Eloquent/Casts/AsEnumArrayObject.php | 11 + .../Eloquent/Casts/AsEnumCollection.php | 11 + .../Eloquent/Concerns/HasAttributes.php | 52 ++++- tests/Database/DatabaseEloquentModelTest.php | 221 ++++++++++++++++-- 6 files changed, 294 insertions(+), 23 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php index 1b7d6418c7c2..e71df5a3df3c 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php @@ -46,4 +46,15 @@ public function set($model, $key, $value, $attributes) } }; } + + /** + * Specify the collection for the cast. + * + * @param class-string $class + * @return string + */ + public static function using($class) + { + return static::class.':'.$class; + } } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index d1b2c2fd295f..a192d2b0c121 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -49,4 +49,15 @@ public function set($model, $key, $value, $attributes) } }; } + + /** + * Specify the collection for the cast. + * + * @param class-string $class + * @return string + */ + public static function using($class) + { + return static::class.':'.$class; + } } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php b/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php index f5f0571bca50..e52029db9331 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php @@ -81,4 +81,15 @@ protected function getStorableEnumValue($enum) } }; } + + /** + * Specify the Enum for the cast. + * + * @param class-string $class + * @return string + */ + public static function of($class) + { + return static::class.':'.$class; + } } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php index ff632ed223cd..da9cc9b477f0 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php @@ -77,4 +77,15 @@ protected function getStorableEnumValue($enum) } }; } + + /** + * Specify the Enum for the cast. + * + * @param class-string $class + * @return string + */ + public static function of($class) + { + return static::class.':'.$class; + } } diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index d26247b2292b..1347cc4d9366 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -182,6 +182,18 @@ trait HasAttributes */ public static $encrypter; + /** + * Initialize the trait. + * + * @return void + */ + protected function initializeHasAttributes() + { + $this->casts = $this->ensureCastsAreStringValues( + array_merge($this->casts, $this->casts()), + ); + } + /** * Convert the model's attributes to an array. * @@ -719,11 +731,39 @@ protected function mutateAttributeForArray($key, $value) */ public function mergeCasts($casts) { + $casts = $this->ensureCastsAreStringValues($casts); + $this->casts = array_merge($this->casts, $casts); return $this; } + /** + * Ensure that the given casts are strings. + * + * @param array $casts + * @return array + */ + protected function ensureCastsAreStringValues($casts) + { + foreach ($casts as $attribute => $cast) { + $casts[$attribute] = match (true) { + is_array($cast) => value(function () use ($cast) { + if (count($cast) === 1) { + return $cast[0]; + } + + [$cast, $arguments] = [array_shift($cast), $cast]; + + return $cast.':'.implode(',', $arguments); + }), + default => $cast, + }; + } + + return $casts; + } + /** * Cast an attribute to a native PHP type. * @@ -1521,7 +1561,7 @@ public function hasCast($key, $types = null) } /** - * Get the casts array. + * Get the attributes that should be cast. * * @return array */ @@ -1534,6 +1574,16 @@ public function getCasts() return $this->casts; } + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts() + { + return []; + } + /** * Determine whether a value is Date / DateTime castable for inbound manipulation. * diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 4f865ec0e9c3..508d37b31523 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -207,20 +207,38 @@ public function testDirtyOnCastedCustomCollection() { $model = new EloquentModelCastingStub; $model->setRawAttributes([ - 'asCustomCollectionAttribute' => '{"foo": "bar"}', + 'asCustomCollectionAttribute' => '{"bar": "foo"}', ]); $model->syncOriginal(); $this->assertInstanceOf(CustomCollection::class, $model->asCustomCollectionAttribute); $this->assertFalse($model->isDirty('asCustomCollectionAttribute')); - $model->asCustomCollectionAttribute = ['foo' => 'bar']; + $model->asCustomCollectionAttribute = ['bar' => 'foo']; $this->assertFalse($model->isDirty('asCustomCollectionAttribute')); - $model->asCustomCollectionAttribute = ['foo' => 'baz']; + $model->asCustomCollectionAttribute = ['baz' => 'foo']; $this->assertTrue($model->isDirty('asCustomCollectionAttribute')); } + public function testDirtyOnCastedCustomCollectionAsArray() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'asCustomCollectionAsArrayAttribute' => '{"bar": "foo"}', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(CustomCollection::class, $model->asCustomCollectionAsArrayAttribute); + $this->assertFalse($model->isDirty('asCustomCollectionAsArrayAttribute')); + + $model->asCustomCollectionAsArrayAttribute = ['bar' => 'foo']; + $this->assertFalse($model->isDirty('asCustomCollectionAsArrayAttribute')); + + $model->asCustomCollectionAsArrayAttribute = ['baz' => 'foo']; + $this->assertTrue($model->isDirty('asCustomCollectionAsArrayAttribute')); + } + public function testDirtyOnCastedStringable() { $model = new EloquentModelCastingStub; @@ -291,27 +309,27 @@ public function testDirtyOnCastedEncryptedCustomCollection() $this->encrypter->expects('encryptString') ->twice() ->with('{"foo":"bar"}') - ->andReturn('encrypted-value'); + ->andReturn('encrypted-custom-value'); $this->encrypter->expects('decryptString') - ->with('encrypted-value') + ->with('encrypted-custom-value') ->andReturn('{"foo": "bar"}'); $this->encrypter->expects('encryptString') ->with('{"foo":"baz"}') - ->andReturn('new-encrypted-value'); + ->andReturn('new-encrypted-custom-value'); $this->encrypter->expects('decrypt') - ->with('encrypted-value', false) + ->with('encrypted-custom-value', false) ->andReturn('{"foo": "bar"}'); $this->encrypter->expects('decrypt') - ->with('new-encrypted-value', false) + ->with('new-encrypted-custom-value', false) ->andReturn('{"foo":"baz"}'); $model = new EloquentModelCastingStub; $model->setRawAttributes([ - 'asEncryptedCustomCollectionAttribute' => 'encrypted-value', + 'asEncryptedCustomCollectionAttribute' => 'encrypted-custom-value', ]); $model->syncOriginal(); @@ -325,6 +343,49 @@ public function testDirtyOnCastedEncryptedCustomCollection() $this->assertTrue($model->isDirty('asEncryptedCustomCollectionAttribute')); } + public function testDirtyOnCastedEncryptedCustomCollectionAsArray() + { + $this->encrypter = m::mock(Encrypter::class); + Crypt::swap($this->encrypter); + Model::$encrypter = null; + + $this->encrypter->expects('encryptString') + ->twice() + ->with('{"foo":"bar"}') + ->andReturn('encrypted-custom-value'); + + $this->encrypter->expects('decryptString') + ->with('encrypted-custom-value') + ->andReturn('{"foo": "bar"}'); + + $this->encrypter->expects('encryptString') + ->with('{"foo":"baz"}') + ->andReturn('new-encrypted-custom-value'); + + $this->encrypter->expects('decrypt') + ->with('encrypted-custom-value', false) + ->andReturn('{"foo": "bar"}'); + + $this->encrypter->expects('decrypt') + ->with('new-encrypted-custom-value', false) + ->andReturn('{"foo":"baz"}'); + + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'asEncryptedCustomCollectionAsArrayAttribute' => 'encrypted-custom-value', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(CustomCollection::class, $model->asEncryptedCustomCollectionAsArrayAttribute); + $this->assertFalse($model->isDirty('asEncryptedCustomCollectionAsArrayAttribute')); + + $model->asEncryptedCustomCollectionAsArrayAttribute = ['foo' => 'bar']; + $this->assertFalse($model->isDirty('asEncryptedCustomCollectionAsArrayAttribute')); + + $model->asEncryptedCustomCollectionAsArrayAttribute = ['foo' => 'baz']; + $this->assertTrue($model->isDirty('asEncryptedCustomCollectionAsArrayAttribute')); + } + public function testDirtyOnCastedEncryptedArrayObject() { $this->encrypter = m::mock(Encrypter::class); @@ -386,6 +447,24 @@ public function testDirtyOnEnumCollectionObject() $this->assertTrue($model->isDirty('asEnumCollectionAttribute')); } + public function testDirtyOnCustomEnumCollectionObject() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'asCustomEnumCollectionAttribute' => '["draft", "pending"]', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(BaseCollection::class, $model->asCustomEnumCollectionAttribute); + $this->assertFalse($model->isDirty('asCustomEnumCollectionAttribute')); + + $model->asCustomEnumCollectionAttribute = ['draft', 'pending']; + $this->assertFalse($model->isDirty('asCustomEnumCollectionAttribute')); + + $model->asCustomEnumCollectionAttribute = ['draft', 'done']; + $this->assertTrue($model->isDirty('asCustomEnumCollectionAttribute')); + } + public function testDirtyOnEnumArrayObject() { $model = new EloquentModelCastingStub; @@ -404,6 +483,24 @@ public function testDirtyOnEnumArrayObject() $this->assertTrue($model->isDirty('asEnumArrayObjectAttribute')); } + public function testDirtyOnCustomEnumArrayObjectUsing() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'asCustomEnumArrayObjectAttribute' => '["draft", "pending"]', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(ArrayObject::class, $model->asCustomEnumArrayObjectAttribute); + $this->assertFalse($model->isDirty('asCustomEnumArrayObjectAttribute')); + + $model->asCustomEnumArrayObjectAttribute = ['draft', 'pending']; + $this->assertFalse($model->isDirty('asCustomEnumArrayObjectAttribute')); + + $model->asCustomEnumArrayObjectAttribute = ['draft', 'done']; + $this->assertTrue($model->isDirty('asCustomEnumArrayObjectAttribute')); + } + public function testHasCastsOnEnumAttribute() { $model = new EloquentModelEnumCastingStub(); @@ -2122,7 +2219,7 @@ public function testRelationshipTouchOwnersIsNotPropagatedIfNoRelationshipResult $model->touchOwners(); } - public function testModelAttributesAreCastedWhenPresentInCastsArray() + public function testModelAttributesAreCastedWhenPresentInCastsPropertyOrCastsMethod() { $model = new EloquentModelCastingStub; $model->setDateFormat('Y-m-d H:i:s'); @@ -2140,6 +2237,7 @@ public function testModelAttributesAreCastedWhenPresentInCastsArray() $model->datetimeAttribute = '1969-07-20 22:56:00'; $model->timestampAttribute = '1969-07-20 22:56:00'; $model->collectionAttribute = new BaseCollection; + $model->asCustomCollectionAttribute = new CustomCollection; $this->assertIsInt($model->intAttribute); $this->assertIsFloat($model->floatAttribute); @@ -2158,6 +2256,7 @@ public function testModelAttributesAreCastedWhenPresentInCastsArray() $this->assertInstanceOf(Carbon::class, $model->dateAttribute); $this->assertInstanceOf(Carbon::class, $model->datetimeAttribute); $this->assertInstanceOf(BaseCollection::class, $model->collectionAttribute); + $this->assertInstanceOf(CustomCollection::class, $model->asCustomCollectionAttribute); $this->assertSame('1969-07-20', $model->dateAttribute->toDateString()); $this->assertSame('1969-07-20 22:56:00', $model->datetimeAttribute->toDateTimeString()); $this->assertEquals(-14173440, $model->timestampAttribute); @@ -2270,7 +2369,7 @@ public function testModelAttributeCastingFailsOnUnencodableData() $model->getAttributes(); } - public function testModelAttributeCastingWithSpecialFloatValues() + public function testModelAttributeCastingWithFloats() { $model = new EloquentModelCastingStub; @@ -2296,6 +2395,14 @@ public function testModelAttributeCastingWithSpecialFloatValues() $this->assertNan($model->floatAttribute); } + public function testModelAttributeCastingWithArrays() + { + $model = new EloquentModelCastingStub; + + $model->asEnumArrayObjectAttribute = ['draft', 'pending']; + $this->assertInstanceOf(ArrayObject::class, $model->asEnumArrayObjectAttribute); + } + public function testMergeCastsMergesCasts() { $model = new EloquentModelCastingStub; @@ -2308,6 +2415,24 @@ public function testMergeCastsMergesCasts() $this->assertArrayHasKey('foo', $model->getCasts()); } + public function testMergeCastsMergesCastsUsingArrays() + { + $model = new EloquentModelCastingStub; + + $castCount = count($model->getCasts()); + $this->assertArrayNotHasKey('foo', $model->getCasts()); + + $model->mergeCasts([ + 'foo' => ['MyClass', 'myArgumentA'], + 'bar' => ['MyClass', 'myArgumentA', 'myArgumentB'], + ]); + + $this->assertCount($castCount + 2, $model->getCasts()); + $this->assertArrayHasKey('foo', $model->getCasts()); + $this->assertEquals($model->getCasts()['foo'], 'MyClass:myArgumentA'); + $this->assertEquals($model->getCasts()['bar'], 'MyClass:myArgumentA,myArgumentB'); + } + public function testUpdatingNonExistentModelFails() { $model = new EloquentModelStub; @@ -2680,6 +2805,45 @@ public function testGetOriginalCastsAttributes() $this->assertEquals(['foo' => 'bar2'], $model->getAttribute('collectionAttribute')->toArray()); } + public function testCastsMethodHasPriorityOverCastsProperty() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'duplicatedAttribute' => '1', + ], true); + + $this->assertIsInt($model->duplicatedAttribute); + $this->assertEquals(1, $model->duplicatedAttribute); + $this->assertEquals(1, $model->getAttribute('duplicatedAttribute')); + } + + public function testCastsMethodIsTakenInConsiderationOnSerialization() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'duplicatedAttribute' => '1', + ], true); + + $model = unserialize(serialize($model)); + + $this->assertIsInt($model->duplicatedAttribute); + $this->assertEquals(1, $model->duplicatedAttribute); + $this->assertEquals(1, $model->getAttribute('duplicatedAttribute')); + } + + public function testsCastOnArrayFormatWithOneElement() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'singleElementInArrayAttribute' => '{"bar": "foo"}', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(BaseCollection::class, $model->singleElementInArrayAttribute); + $this->assertEquals(['bar' => 'foo'], $model->singleElementInArrayAttribute->toArray()); + $this->assertEquals(['bar' => 'foo'], $model->getAttribute('singleElementInArrayAttribute')->toArray()); + } + public function testUnsavedModel() { $user = new UnsavedModel; @@ -3081,29 +3245,42 @@ public function doNotGetFourthInvalidAttributeEither() class EloquentModelCastingStub extends Model { protected $casts = [ - 'intAttribute' => 'int', 'floatAttribute' => 'float', - 'stringAttribute' => 'string', 'boolAttribute' => 'bool', - 'booleanAttribute' => 'boolean', 'objectAttribute' => 'object', - 'arrayAttribute' => 'array', 'jsonAttribute' => 'json', - 'collectionAttribute' => 'collection', 'dateAttribute' => 'date', - 'datetimeAttribute' => 'datetime', 'timestampAttribute' => 'timestamp', - 'asarrayobjectAttribute' => AsArrayObject::class, 'ascollectionAttribute' => AsCollection::class, - 'asCustomCollectionAttribute' => AsCollection::class.':'.CustomCollection::class, - 'asStringableAttribute' => AsStringable::class, + 'asCustomCollectionAsArrayAttribute' => [AsCollection::class, CustomCollection::class], 'asEncryptedCollectionAttribute' => AsEncryptedCollection::class, - 'asEncryptedCustomCollectionAttribute' => AsEncryptedCollection::class.':'.CustomCollection::class, - 'asEncryptedArrayObjectAttribute' => AsEncryptedArrayObject::class, 'asEnumCollectionAttribute' => AsEnumCollection::class.':'.StringStatus::class, 'asEnumArrayObjectAttribute' => AsEnumArrayObject::class.':'.StringStatus::class, + 'duplicatedAttribute' => 'string', ]; + protected function casts(): array + { + return [ + 'intAttribute' => 'int', + 'stringAttribute' => 'string', + 'booleanAttribute' => 'boolean', + 'arrayAttribute' => 'array', + 'collectionAttribute' => 'collection', + 'datetimeAttribute' => 'datetime', + 'asarrayobjectAttribute' => AsArrayObject::class, + 'asStringableAttribute' => AsStringable::class, + 'asCustomCollectionAttribute' => AsCollection::using(CustomCollection::class), + 'asEncryptedArrayObjectAttribute' => AsEncryptedArrayObject::class, + 'asEncryptedCustomCollectionAttribute' => AsEncryptedCollection::using(CustomCollection::class), + 'asEncryptedCustomCollectionAsArrayAttribute' => [AsEncryptedCollection::class, CustomCollection::class], + 'asCustomEnumCollectionAttribute' => AsEnumCollection::of(StringStatus::class), + 'asCustomEnumArrayObjectAttribute' => AsEnumArrayObject::of(StringStatus::class), + 'singleElementInArrayAttribute' => [AsCollection::class], + 'duplicatedAttribute' => 'int', + ]; + } + public function jsonAttributeValue() { return $this->attributes['jsonAttribute']; From 03e13ca772ba47c46c04d480e33c3fa2e919b13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Debrauwer?= Date: Wed, 28 Jun 2023 23:53:35 +0200 Subject: [PATCH 34/99] [11.x] Increment/decrement with cast value (#47450) * Increment/decrement with cast value * Fix test * wip * wip * wip --- src/Illuminate/Database/Eloquent/Model.php | 4 ++++ .../DatabaseEloquentModelCustomCastingTest.php | 12 ++++++++++-- .../Database/EloquentModelCustomCastingTest.php | 17 ++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 106b8a79d9d6..292a667b3d50 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -967,6 +967,10 @@ protected function incrementOrDecrement($column, $amount, $extra, $method) return false; } + if ($this->isClassDeviable($column)) { + $amount = (clone $this)->setAttribute($column, $amount)->getAttributeFromArray($column); + } + return tap($this->setKeysForSaveQuery($query)->{$method}($column, $amount, $extra), function () use ($column) { $this->syncChanges(); diff --git a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php index 2d578ab3de61..1c4334d5dd77 100644 --- a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php @@ -167,6 +167,14 @@ public function testDeviableCasts() $model->decrement('price', '333.333'); $this->assertSame((new Decimal('320.988'))->getValue(), $model->price->getValue()); + + $model->increment('price', new Decimal('100.001')); + + $this->assertSame((new Decimal('420.989'))->getValue(), $model->price->getValue()); + + $model->decrement('price', new Decimal('200.002')); + + $this->assertSame((new Decimal('220.987'))->getValue(), $model->price->getValue()); } public function testSerializableCasts() @@ -427,12 +435,12 @@ public function set($model, $key, $value, $attributes) public function increment($model, $key, $value, $attributes) { - return new Decimal($attributes[$key] + $value); + return new Decimal($attributes[$key] + ($value instanceof Decimal ? (string) $value : $value)); } public function decrement($model, $key, $value, $attributes) { - return new Decimal($attributes[$key] - $value); + return new Decimal($attributes[$key] - ($value instanceof Decimal ? (string) $value : $value)); } public function serialize($model, $key, $value, $attributes) diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index dac1d066c475..b27149129fd1 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -181,7 +181,7 @@ public function testModelWithCustomCastsWorkWithCustomIncrementDecrement() $this->assertInstanceOf(Euro::class, $model->amount); $this->assertEquals('2', $model->amount->value); - $model->incrementAmount(new Euro('1')); + $model->increment('amount', new Euro('1')); $this->assertEquals('3.00', $model->amount->value); } @@ -394,19 +394,19 @@ public function get($model, $key, $value, $attributes) public function set($model, $key, $value, $attributes) { - return $value->value; + return $value instanceof Euro ? $value->value : $value; } - public function increment($model, $key, string $value, $attributes) + public function increment($model, $key, $value, $attributes) { - $model->$key = new Euro((string) BigNumber::of($model->$key->value)->plus($value)->toScale(2)); + $model->$key = new Euro((string) BigNumber::of($model->$key->value)->plus($value->value)->toScale(2)); return $model->$key; } - public function decrement($model, $key, string $value, $attributes) + public function decrement($model, $key, $value, $attributes) { - $model->$key = new Euro((string) BigNumber::of($model->$key->value)->subtract($value)->toScale(2)); + $model->$key = new Euro((string) BigNumber::of($model->$key->value)->subtract($value->value)->toScale(2)); return $model->$key; } @@ -418,9 +418,4 @@ class Member extends Model protected $casts = [ 'amount' => Euro::class, ]; - - public function incrementAmount(Euro $amount) - { - $this->increment('amount', $amount->value); - } } From a3fc3f292e3ad1b195376eb3aca1e2b8b3c2a004 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Mon, 10 Jul 2023 09:48:07 -0500 Subject: [PATCH 35/99] explicitly declare `\Stringable` interface (#47697) according to the [PHP docs](https://www.php.net/manual/en/class.stringable.php): > Stringable is implicitly present on any class that has the magic __toString() method defined, although it can and should be declared explicitly. The return types of the `__toString()` methods are inconsistent, but I skipped addressing that now, because I'm not exactly clear on how we're handling that in the framework. --- src/Illuminate/Auth/Access/Response.php | 3 ++- src/Illuminate/Broadcasting/Channel.php | 3 ++- src/Illuminate/Database/Eloquent/Model.php | 3 ++- src/Illuminate/Http/Client/Response.php | 3 ++- src/Illuminate/Mail/Transport/ArrayTransport.php | 3 ++- src/Illuminate/Mail/Transport/LogTransport.php | 3 ++- src/Illuminate/Mail/Transport/SesTransport.php | 3 ++- src/Illuminate/Mail/Transport/SesV2Transport.php | 3 ++- src/Illuminate/Pagination/AbstractCursorPaginator.php | 3 ++- src/Illuminate/Pagination/AbstractPaginator.php | 3 ++- src/Illuminate/Support/HtmlString.php | 3 ++- src/Illuminate/Support/Js.php | 3 ++- src/Illuminate/Support/MessageBag.php | 3 ++- src/Illuminate/Support/Stringable.php | 3 ++- src/Illuminate/Support/ViewErrorBag.php | 3 ++- src/Illuminate/Testing/TestComponent.php | 3 ++- src/Illuminate/Testing/TestView.php | 3 ++- src/Illuminate/Validation/Rules/Dimensions.php | 3 ++- src/Illuminate/Validation/Rules/ExcludeIf.php | 3 ++- src/Illuminate/Validation/Rules/Exists.php | 3 ++- src/Illuminate/Validation/Rules/In.php | 4 +++- src/Illuminate/Validation/Rules/NotIn.php | 4 +++- src/Illuminate/Validation/Rules/ProhibitedIf.php | 3 ++- src/Illuminate/Validation/Rules/RequiredIf.php | 3 ++- src/Illuminate/Validation/Rules/Unique.php | 3 ++- src/Illuminate/View/AppendableAttributeValue.php | 4 +++- src/Illuminate/View/ComponentAttributeBag.php | 3 ++- src/Illuminate/View/ComponentSlot.php | 3 ++- src/Illuminate/View/InvokableComponentVariable.php | 3 ++- src/Illuminate/View/View.php | 3 ++- 30 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/Illuminate/Auth/Access/Response.php b/src/Illuminate/Auth/Access/Response.php index 77cabb521de9..ebf5a8eaaa09 100644 --- a/src/Illuminate/Auth/Access/Response.php +++ b/src/Illuminate/Auth/Access/Response.php @@ -3,8 +3,9 @@ namespace Illuminate\Auth\Access; use Illuminate\Contracts\Support\Arrayable; +use Stringable; -class Response implements Arrayable +class Response implements Arrayable, Stringable { /** * Indicates whether the response was allowed. diff --git a/src/Illuminate/Broadcasting/Channel.php b/src/Illuminate/Broadcasting/Channel.php index 02b1a5caaa9a..53094227f559 100644 --- a/src/Illuminate/Broadcasting/Channel.php +++ b/src/Illuminate/Broadcasting/Channel.php @@ -3,8 +3,9 @@ namespace Illuminate\Broadcasting; use Illuminate\Contracts\Broadcasting\HasBroadcastChannel; +use Stringable; -class Channel +class Channel implements Stringable { /** * The channel's name. diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index fbca659784ba..5130d192a27f 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -22,8 +22,9 @@ use Illuminate\Support\Traits\ForwardsCalls; use JsonSerializable; use LogicException; +use Stringable; -abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable +abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, Stringable, UrlRoutable { use Concerns\HasAttributes, Concerns\HasEvents, diff --git a/src/Illuminate/Http/Client/Response.php b/src/Illuminate/Http/Client/Response.php index f26886e2f80e..50493449c858 100644 --- a/src/Illuminate/Http/Client/Response.php +++ b/src/Illuminate/Http/Client/Response.php @@ -6,8 +6,9 @@ use Illuminate\Support\Collection; use Illuminate\Support\Traits\Macroable; use LogicException; +use Stringable; -class Response implements ArrayAccess +class Response implements ArrayAccess, Stringable { use Concerns\DeterminesStatusCode, Macroable { __call as macroCall; diff --git a/src/Illuminate/Mail/Transport/ArrayTransport.php b/src/Illuminate/Mail/Transport/ArrayTransport.php index dc26ed69d90b..36e27d1f369c 100644 --- a/src/Illuminate/Mail/Transport/ArrayTransport.php +++ b/src/Illuminate/Mail/Transport/ArrayTransport.php @@ -3,12 +3,13 @@ namespace Illuminate\Mail\Transport; use Illuminate\Support\Collection; +use Stringable; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\RawMessage; -class ArrayTransport implements TransportInterface +class ArrayTransport implements Stringable, TransportInterface { /** * The collection of Symfony Messages. diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index 291251200a48..3d05a9956558 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -3,12 +3,13 @@ namespace Illuminate\Mail\Transport; use Psr\Log\LoggerInterface; +use Stringable; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\RawMessage; -class LogTransport implements TransportInterface +class LogTransport implements Stringable, TransportInterface { /** * The Logger instance. diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php index 9db7734c62ad..7929bc13803d 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -5,12 +5,13 @@ use Aws\Exception\AwsException; use Aws\Ses\SesClient; use Exception; +use Stringable; use Symfony\Component\Mailer\Header\MetadataHeader; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mime\Message; -class SesTransport extends AbstractTransport +class SesTransport extends AbstractTransport implements Stringable { /** * The Amazon SES instance. diff --git a/src/Illuminate/Mail/Transport/SesV2Transport.php b/src/Illuminate/Mail/Transport/SesV2Transport.php index 5cc3936d85b6..bbeaa1bd5911 100644 --- a/src/Illuminate/Mail/Transport/SesV2Transport.php +++ b/src/Illuminate/Mail/Transport/SesV2Transport.php @@ -5,12 +5,13 @@ use Aws\Exception\AwsException; use Aws\SesV2\SesV2Client; use Exception; +use Stringable; use Symfony\Component\Mailer\Header\MetadataHeader; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mime\Message; -class SesV2Transport extends AbstractTransport +class SesV2Transport extends AbstractTransport implements Stringable { /** * The Amazon SES V2 instance. diff --git a/src/Illuminate/Pagination/AbstractCursorPaginator.php b/src/Illuminate/Pagination/AbstractCursorPaginator.php index e6700789c176..5a150f132279 100644 --- a/src/Illuminate/Pagination/AbstractCursorPaginator.php +++ b/src/Illuminate/Pagination/AbstractCursorPaginator.php @@ -14,12 +14,13 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Tappable; +use Stringable; use Traversable; /** * @mixin \Illuminate\Support\Collection */ -abstract class AbstractCursorPaginator implements Htmlable +abstract class AbstractCursorPaginator implements Htmlable, Stringable { use ForwardsCalls, Tappable; diff --git a/src/Illuminate/Pagination/AbstractPaginator.php b/src/Illuminate/Pagination/AbstractPaginator.php index ed04d5f97234..9ca92fa1cc30 100644 --- a/src/Illuminate/Pagination/AbstractPaginator.php +++ b/src/Illuminate/Pagination/AbstractPaginator.php @@ -8,12 +8,13 @@ use Illuminate\Support\Collection; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Tappable; +use Stringable; use Traversable; /** * @mixin \Illuminate\Support\Collection */ -abstract class AbstractPaginator implements Htmlable +abstract class AbstractPaginator implements Htmlable, Stringable { use ForwardsCalls, Tappable; diff --git a/src/Illuminate/Support/HtmlString.php b/src/Illuminate/Support/HtmlString.php index d6b71d46cde5..624e7f3efa53 100644 --- a/src/Illuminate/Support/HtmlString.php +++ b/src/Illuminate/Support/HtmlString.php @@ -3,8 +3,9 @@ namespace Illuminate\Support; use Illuminate\Contracts\Support\Htmlable; +use Stringable; -class HtmlString implements Htmlable +class HtmlString implements Htmlable, Stringable { /** * The HTML string. diff --git a/src/Illuminate/Support/Js.php b/src/Illuminate/Support/Js.php index c924d1c83262..a3cdfe79fea7 100644 --- a/src/Illuminate/Support/Js.php +++ b/src/Illuminate/Support/Js.php @@ -7,8 +7,9 @@ use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\Support\Jsonable; use JsonSerializable; +use Stringable; -class Js implements Htmlable +class Js implements Htmlable, Stringable { /** * The JavaScript string. diff --git a/src/Illuminate/Support/MessageBag.php b/src/Illuminate/Support/MessageBag.php index 27116941b7f5..2daa4c2735af 100755 --- a/src/Illuminate/Support/MessageBag.php +++ b/src/Illuminate/Support/MessageBag.php @@ -7,8 +7,9 @@ use Illuminate\Contracts\Support\MessageBag as MessageBagContract; use Illuminate\Contracts\Support\MessageProvider; use JsonSerializable; +use Stringable; -class MessageBag implements Jsonable, JsonSerializable, MessageBagContract, MessageProvider +class MessageBag implements Jsonable, JsonSerializable, MessageBagContract, MessageProvider, Stringable { /** * All of the registered messages. diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index b2053151bf89..3589b1974614 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -10,8 +10,9 @@ use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use JsonSerializable; +use Stringable as BaseStringable; -class Stringable implements JsonSerializable, ArrayAccess +class Stringable implements JsonSerializable, ArrayAccess, BaseStringable { use Conditionable, Dumpable, Macroable, Tappable; diff --git a/src/Illuminate/Support/ViewErrorBag.php b/src/Illuminate/Support/ViewErrorBag.php index ff9da4fc58d7..2865fbfa4bdd 100644 --- a/src/Illuminate/Support/ViewErrorBag.php +++ b/src/Illuminate/Support/ViewErrorBag.php @@ -4,11 +4,12 @@ use Countable; use Illuminate\Contracts\Support\MessageBag as MessageBagContract; +use Stringable; /** * @mixin \Illuminate\Contracts\Support\MessageBag */ -class ViewErrorBag implements Countable +class ViewErrorBag implements Countable, Stringable { /** * The array of the view error bags. diff --git a/src/Illuminate/Testing/TestComponent.php b/src/Illuminate/Testing/TestComponent.php index 4a8055149335..0965789b3854 100644 --- a/src/Illuminate/Testing/TestComponent.php +++ b/src/Illuminate/Testing/TestComponent.php @@ -4,8 +4,9 @@ use Illuminate\Testing\Assert as PHPUnit; use Illuminate\Testing\Constraints\SeeInOrder; +use Stringable; -class TestComponent +class TestComponent implements Stringable { /** * The original component. diff --git a/src/Illuminate/Testing/TestView.php b/src/Illuminate/Testing/TestView.php index 137b41e6a992..a08640078637 100644 --- a/src/Illuminate/Testing/TestView.php +++ b/src/Illuminate/Testing/TestView.php @@ -10,8 +10,9 @@ use Illuminate\Testing\Assert as PHPUnit; use Illuminate\Testing\Constraints\SeeInOrder; use Illuminate\View\View; +use Stringable; -class TestView +class TestView implements Stringable { use Macroable; diff --git a/src/Illuminate/Validation/Rules/Dimensions.php b/src/Illuminate/Validation/Rules/Dimensions.php index 624cbcb8caf7..50d67fc379dd 100644 --- a/src/Illuminate/Validation/Rules/Dimensions.php +++ b/src/Illuminate/Validation/Rules/Dimensions.php @@ -3,8 +3,9 @@ namespace Illuminate\Validation\Rules; use Illuminate\Support\Traits\Conditionable; +use Stringable; -class Dimensions +class Dimensions implements Stringable { use Conditionable; diff --git a/src/Illuminate/Validation/Rules/ExcludeIf.php b/src/Illuminate/Validation/Rules/ExcludeIf.php index 2fff3d786bd1..12869d46679c 100644 --- a/src/Illuminate/Validation/Rules/ExcludeIf.php +++ b/src/Illuminate/Validation/Rules/ExcludeIf.php @@ -4,8 +4,9 @@ use Closure; use InvalidArgumentException; +use Stringable; -class ExcludeIf +class ExcludeIf implements Stringable { /** * The condition that validates the attribute. diff --git a/src/Illuminate/Validation/Rules/Exists.php b/src/Illuminate/Validation/Rules/Exists.php index e6dac83dc83e..33b8b4e10bbc 100644 --- a/src/Illuminate/Validation/Rules/Exists.php +++ b/src/Illuminate/Validation/Rules/Exists.php @@ -3,8 +3,9 @@ namespace Illuminate\Validation\Rules; use Illuminate\Support\Traits\Conditionable; +use Stringable; -class Exists +class Exists implements Stringable { use Conditionable, DatabaseRule; diff --git a/src/Illuminate/Validation/Rules/In.php b/src/Illuminate/Validation/Rules/In.php index cc45b338f32a..0557a83e81ea 100644 --- a/src/Illuminate/Validation/Rules/In.php +++ b/src/Illuminate/Validation/Rules/In.php @@ -2,7 +2,9 @@ namespace Illuminate\Validation\Rules; -class In +use Stringable; + +class In implements Stringable { /** * The name of the rule. diff --git a/src/Illuminate/Validation/Rules/NotIn.php b/src/Illuminate/Validation/Rules/NotIn.php index 5701da68e2fa..70a56d82a53a 100644 --- a/src/Illuminate/Validation/Rules/NotIn.php +++ b/src/Illuminate/Validation/Rules/NotIn.php @@ -2,7 +2,9 @@ namespace Illuminate\Validation\Rules; -class NotIn +use Stringable; + +class NotIn implements Stringable { /** * The name of the rule. diff --git a/src/Illuminate/Validation/Rules/ProhibitedIf.php b/src/Illuminate/Validation/Rules/ProhibitedIf.php index fdc35cda3dff..79ec974f9bbb 100644 --- a/src/Illuminate/Validation/Rules/ProhibitedIf.php +++ b/src/Illuminate/Validation/Rules/ProhibitedIf.php @@ -4,8 +4,9 @@ use Closure; use InvalidArgumentException; +use Stringable; -class ProhibitedIf +class ProhibitedIf implements Stringable { /** * The condition that validates the attribute. diff --git a/src/Illuminate/Validation/Rules/RequiredIf.php b/src/Illuminate/Validation/Rules/RequiredIf.php index a1ab74915705..bee7c2886033 100644 --- a/src/Illuminate/Validation/Rules/RequiredIf.php +++ b/src/Illuminate/Validation/Rules/RequiredIf.php @@ -3,8 +3,9 @@ namespace Illuminate\Validation\Rules; use InvalidArgumentException; +use Stringable; -class RequiredIf +class RequiredIf implements Stringable { /** * The condition that validates the attribute. diff --git a/src/Illuminate/Validation/Rules/Unique.php b/src/Illuminate/Validation/Rules/Unique.php index 1f4e86787050..519b66c5d5aa 100644 --- a/src/Illuminate/Validation/Rules/Unique.php +++ b/src/Illuminate/Validation/Rules/Unique.php @@ -4,8 +4,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Traits\Conditionable; +use Stringable; -class Unique +class Unique implements Stringable { use Conditionable, DatabaseRule; diff --git a/src/Illuminate/View/AppendableAttributeValue.php b/src/Illuminate/View/AppendableAttributeValue.php index f275801e4e1d..10981bf976e7 100644 --- a/src/Illuminate/View/AppendableAttributeValue.php +++ b/src/Illuminate/View/AppendableAttributeValue.php @@ -2,7 +2,9 @@ namespace Illuminate\View; -class AppendableAttributeValue +use Stringable; + +class AppendableAttributeValue implements Stringable { /** * The attribute value. diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php index 1d0a790b4256..59db7e621e62 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -11,9 +11,10 @@ use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; use IteratorAggregate; +use Stringable; use Traversable; -class ComponentAttributeBag implements ArrayAccess, Htmlable, IteratorAggregate +class ComponentAttributeBag implements ArrayAccess, Htmlable, IteratorAggregate, Stringable { use Conditionable, Macroable; diff --git a/src/Illuminate/View/ComponentSlot.php b/src/Illuminate/View/ComponentSlot.php index 85665ad64575..0f14245bfa0e 100644 --- a/src/Illuminate/View/ComponentSlot.php +++ b/src/Illuminate/View/ComponentSlot.php @@ -3,8 +3,9 @@ namespace Illuminate\View; use Illuminate\Contracts\Support\Htmlable; +use Stringable; -class ComponentSlot implements Htmlable +class ComponentSlot implements Htmlable, Stringable { /** * The slot attribute bag. diff --git a/src/Illuminate/View/InvokableComponentVariable.php b/src/Illuminate/View/InvokableComponentVariable.php index 0d43ae3d355d..87b209efb0b4 100644 --- a/src/Illuminate/View/InvokableComponentVariable.php +++ b/src/Illuminate/View/InvokableComponentVariable.php @@ -7,9 +7,10 @@ use Illuminate\Contracts\Support\DeferringDisplayableValue; use Illuminate\Support\Enumerable; use IteratorAggregate; +use Stringable; use Traversable; -class InvokableComponentVariable implements DeferringDisplayableValue, IteratorAggregate +class InvokableComponentVariable implements DeferringDisplayableValue, IteratorAggregate, Stringable { /** * The callable instance to resolve the variable value. diff --git a/src/Illuminate/View/View.php b/src/Illuminate/View/View.php index efe6fceffd5e..a3d4ec0c7c67 100755 --- a/src/Illuminate/View/View.php +++ b/src/Illuminate/View/View.php @@ -14,9 +14,10 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\ViewErrorBag; +use Stringable; use Throwable; -class View implements ArrayAccess, Htmlable, ViewContract +class View implements ArrayAccess, Htmlable, Stringable, ViewContract { use Macroable { __call as macroCall; From a36400d62f89bface63f740030b4a56ee072e381 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Sat, 15 Jul 2023 19:43:12 +0000 Subject: [PATCH 36/99] 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 ff537e4df452..3d32cf8621c7 100755 --- a/src/Illuminate/Support/Facades/Blade.php +++ b/src/Illuminate/Support/Facades/Blade.php @@ -28,6 +28,7 @@ * @method static void aliasInclude(string $path, string|null $alias = null) * @method static void directive(string $name, callable $handler) * @method static array getCustomDirectives() + * @method static \Illuminate\View\Compilers\BladeCompiler prepareStringsForCompilationUsing(callable $callback) * @method static void precompiler(callable $precompiler) * @method static void setEchoFormat(string $format) * @method static void withDoubleEncoding() From 20d6b4bde3b92300bbedc6a23179ca13aa8e8b9b Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 16 Aug 2023 17:28:29 +0200 Subject: [PATCH 37/99] [11.x] Improved Performance for Testing with In-Memory Databases (#47912) * Transactional RefreshDatabase for in memory databases This provides a significant performance increase for running tests when using an in memory database. * formatting --------- Co-authored-by: Taylor Otwell --- .../Foundation/Testing/RefreshDatabase.php | 38 +++++++++---------- .../Testing/RefreshDatabaseState.php | 7 ++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index b486cbb0ac91..3ba031058039 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -18,9 +18,11 @@ public function refreshDatabase() { $this->beforeRefreshingDatabase(); - $this->usingInMemoryDatabase() - ? $this->refreshInMemoryDatabase() - : $this->refreshTestDatabase(); + if ($this->usingInMemoryDatabase()) { + $this->restoreInMemoryDatabase(); + } + + $this->refreshTestDatabase(); $this->afterRefreshingDatabase(); } @@ -38,28 +40,19 @@ protected function usingInMemoryDatabase() } /** - * Refresh the in-memory database. + * Restore the in-memory database between tests. * * @return void */ - protected function refreshInMemoryDatabase() + protected function restoreInMemoryDatabase() { - $this->artisan('migrate', $this->migrateUsing()); - - $this->app[Kernel::class]->setArtisan(null); - } + $database = $this->app->make('db'); - /** - * The parameters that should be used when running "migrate". - * - * @return array - */ - protected function migrateUsing() - { - return [ - '--seed' => $this->shouldSeed(), - '--seeder' => $this->seeder(), - ]; + foreach ($this->connectionsToTransact() as $name) { + if (isset(RefreshDatabaseState::$inMemoryConnections[$name])) { + $database->connection($name)->setPdo(RefreshDatabaseState::$inMemoryConnections[$name]); + } + } } /** @@ -91,6 +84,11 @@ public function beginDatabaseTransaction() foreach ($this->connectionsToTransact() as $name) { $connection = $database->connection($name); + + if ($this->usingInMemoryDatabase()) { + RefreshDatabaseState::$inMemoryConnections[$name] ??= $connection->getPdo(); + } + $dispatcher = $connection->getEventDispatcher(); $connection->unsetEventDispatcher(); diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabaseState.php b/src/Illuminate/Foundation/Testing/RefreshDatabaseState.php index a42d3d081bda..e64c34a2b86a 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabaseState.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabaseState.php @@ -4,6 +4,13 @@ class RefreshDatabaseState { + /** + * The current SQLite in-memory database connections. + * + * @var array + */ + public static $inMemoryConnections = []; + /** * Indicates if the test database has been migrated. * From 4b17b5ca1522a56f43f90e5ae7f16550908d4aad Mon Sep 17 00:00:00 2001 From: Philipp <32090713+ouun@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:40:54 +0200 Subject: [PATCH 38/99] [11.x] Env Encryption: Use environmentFilePath() for all .env file paths (#48271) * Use environmentFilePath() for env specific .env files * Use environmentFilePath() for env specific .env files --- .../Foundation/Console/EnvironmentDecryptCommand.php | 4 ++-- .../Foundation/Console/EnvironmentEncryptCommand.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php b/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php index f3c3fa2c5a39..a173388f4bc5 100644 --- a/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php +++ b/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php @@ -73,7 +73,7 @@ public function handle() $key = $this->parseKey($key); $encryptedFile = ($this->option('env') - ? base_path('.env').'.'.$this->option('env') + ? Str::finish(dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR).'.env.'.$this->option('env') : $this->laravel->environmentFilePath()).'.encrypted'; $outputFile = $this->outputFilePath(); @@ -138,7 +138,7 @@ protected function parseKey(string $key) */ protected function outputFilePath() { - $path = Str::finish($this->option('path') ?: base_path(), DIRECTORY_SEPARATOR); + $path = Str::finish($this->option('path') ?: dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR); $outputFile = $this->option('filename') ?: ('.env'.($this->option('env') ? '.'.$this->option('env') : '')); $outputFile = ltrim($outputFile, DIRECTORY_SEPARATOR); diff --git a/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php b/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php index 7c7b11130f4b..f2c55fd3e98d 100644 --- a/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php +++ b/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php @@ -64,7 +64,7 @@ public function handle() $keyPassed = $key !== null; $environmentFile = $this->option('env') - ? base_path('.env').'.'.$this->option('env') + ? Str::finish(dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR).'.env.'.$this->option('env') : $this->laravel->environmentFilePath(); $encryptedFile = $environmentFile.'.encrypted'; From 25aeb5f76571cf19a07b392897d44cf72bdbdb9c Mon Sep 17 00:00:00 2001 From: Julius Kiekbusch Date: Fri, 15 Sep 2023 15:17:41 +0200 Subject: [PATCH 39/99] Remove PHP 8.2 Checks (#48416) --- src/Illuminate/Container/Container.php | 16 ++++------------ tests/Container/ContextualBindingTest.php | 8 +++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index adb73bb51f86..5ffab77ee511 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -676,20 +676,12 @@ public function call($callback, array $parameters = [], $defaultMethod = null) */ protected function getClassForCallable($callback) { - if (PHP_VERSION_ID >= 80200) { - if (is_callable($callback) && - ! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) { - return $reflector->getClosureScopeClass()->name ?? false; - } - - return false; - } - - if (! is_array($callback)) { - return false; + if (is_callable($callback) && + ! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) { + return $reflector->getClosureScopeClass()->name ?? false; } - return is_string($callback[0]) ? $callback[0] : get_class($callback[0]); + return false; } /** diff --git a/tests/Container/ContextualBindingTest.php b/tests/Container/ContextualBindingTest.php index 24de4287c6d2..4c8efa496c9b 100644 --- a/tests/Container/ContextualBindingTest.php +++ b/tests/Container/ContextualBindingTest.php @@ -497,11 +497,9 @@ public function testContextualBindingWorksForMethodInvocation() $valueResolvedUsingArraySyntax = $container->call([$object, 'method']); $this->assertInstanceOf(ContainerContextImplementationStub::class, $valueResolvedUsingArraySyntax); - if (PHP_VERSION_ID >= 80200) { - // first class callable syntax... - $valueResolvedUsingFirstClassSyntax = $container->call($object->method(...)); - $this->assertInstanceOf(ContainerContextImplementationStub::class, $valueResolvedUsingFirstClassSyntax); - } + // first class callable syntax... + $valueResolvedUsingFirstClassSyntax = $container->call($object->method(...)); + $this->assertInstanceOf(ContainerContextImplementationStub::class, $valueResolvedUsingFirstClassSyntax); } } From 4443051783ed6963e038bc493d6e1b27bc5ed96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcello=20Silv=C3=A9rio?= Date: Wed, 20 Sep 2023 23:42:49 -0300 Subject: [PATCH 40/99] test: add test support str::numbers Objective is to test the Str class method to remove all non-numeric characters from a string or array of strings. --- tests/Support/SupportStrTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 8a2e7979f70c..c7eab560e99f 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -501,6 +501,17 @@ public function testLength() $this->assertEquals(11, Str::length('foo bar baz', 'UTF-8')); } + public function testNumbers() + { + $this->assertSame('5551234567', Str::numbers('(555) 123-4567')); + $this->assertSame('443', Str::numbers('L4r4v3l!')); + $this->assertSame('', Str::numbers('Laravel!')); + + $arrayValue = ['(555) 123-4567', 'L4r4v3l', 'Laravel!']; + $arrayExpected = ['5551234567', '443', '']; + $this->assertSame($arrayExpected, Str::numbers($arrayValue)); + } + public function testRandom() { $this->assertEquals(16, strlen(Str::random())); From e340ecd53516345c70aca8161e33458e36367b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcello=20Silv=C3=A9rio?= Date: Wed, 20 Sep 2023 23:43:22 -0300 Subject: [PATCH 41/99] feat: add method numbers for Remove all non-numeric characters --- src/Illuminate/Support/Str.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 62c7ccda605f..c4d19b95d5f0 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -578,6 +578,17 @@ public static function limit($value, $limit = 100, $end = '...') return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; } + /** + * Remove all non-numeric characters from a string or an array of strings. + * + * @param string|string[] $value + * @return string|string[] + */ + public static function numbers(string|array $value): string|array + { + return preg_replace('/[^0-9]/', '', $value); + } + /** * Convert the given string to lower-case. * From e13f10d22ce619c64ce25e751f0720aff561eff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcello=20Silv=C3=A9rio?= <110649120+smarcelloc@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:08:45 -0300 Subject: [PATCH 42/99] style: add space in params value --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index c4d19b95d5f0..46b9f3909be1 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -581,7 +581,7 @@ public static function limit($value, $limit = 100, $end = '...') /** * Remove all non-numeric characters from a string or an array of strings. * - * @param string|string[] $value + * @param string|string[] $value * @return string|string[] */ public static function numbers(string|array $value): string|array From f008879159d8a5dccf54673f2c23f54fb259a1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcello=20Silv=C3=A9rio?= <110649120+smarcelloc@users.noreply.github.com> Date: Thu, 21 Sep 2023 21:25:01 -0300 Subject: [PATCH 43/99] style: space in param $value --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 46b9f3909be1..3b16012e96e5 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -581,7 +581,7 @@ public static function limit($value, $limit = 100, $end = '...') /** * Remove all non-numeric characters from a string or an array of strings. * - * @param string|string[] $value + * @param string|string[] $value * @return string|string[] */ public static function numbers(string|array $value): string|array From e4d25cdeb074763975ce5afa61ceff9513662246 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 22 Sep 2023 10:18:09 -0500 Subject: [PATCH 44/99] formatting --- src/Illuminate/Support/Str.php | 22 +++++++++++----------- src/Illuminate/Support/Stringable.php | 10 ++++++++++ tests/Support/SupportStringableTest.php | 5 +++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 3b16012e96e5..f173bcaa31b6 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -578,17 +578,6 @@ public static function limit($value, $limit = 100, $end = '...') return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; } - /** - * Remove all non-numeric characters from a string or an array of strings. - * - * @param string|string[] $value - * @return string|string[] - */ - public static function numbers(string|array $value): string|array - { - return preg_replace('/[^0-9]/', '', $value); - } - /** * Convert the given string to lower-case. * @@ -750,6 +739,17 @@ public static function matchAll($pattern, $subject) return collect($matches[1] ?? $matches[0]); } + /** + * Remove all non-numeric characters from a string. + * + * @param string $value + * @return string + */ + public static function numbers($value) + { + return preg_replace('/[^0-9]/', '', $value); + } + /** * Pad both sides of a string with another. * diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 23bd6a283bf9..d83a4a17da53 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -496,6 +496,16 @@ public function test($pattern) return $this->isMatch($pattern); } + /** + * Remove all non-numeric characters from a string. + * + * @return static + */ + public function numbers() + { + return new static(Str::numbers($this->value)); + } + /** * Pad both sides of the string with another. * diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index a16eb12bd982..79a93d98cb0f 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -1224,6 +1224,11 @@ public function testBooleanMethod() $this->assertFalse($this->stringable('no')->toBoolean()); } + public function testNumbers() + { + $this->assertSame('5551234567', (string) $this->stringable('(555) 123-4567')->numbers()); + } + public function testToDate() { $current = Carbon::create(2020, 1, 1, 16, 30, 25); From a3ee57772f3b7ddfd7f653be99033c00fcbd4b92 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Mon, 25 Sep 2023 04:13:07 +0330 Subject: [PATCH 45/99] add missing channel params to `sendNow` method in notifications (#48518) --- src/Illuminate/Contracts/Notifications/Dispatcher.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Contracts/Notifications/Dispatcher.php b/src/Illuminate/Contracts/Notifications/Dispatcher.php index a483ca27c253..35ea6b6aca70 100644 --- a/src/Illuminate/Contracts/Notifications/Dispatcher.php +++ b/src/Illuminate/Contracts/Notifications/Dispatcher.php @@ -18,7 +18,8 @@ public function send($notifiables, $notification); * * @param \Illuminate\Support\Collection|array|mixed $notifiables * @param mixed $notification + * @param array|null $channels * @return void */ - public function sendNow($notifiables, $notification); + public function sendNow($notifiables, $notification, array $channels = null); } From a822bac80efd3d2639cd2873b20600075c4dfd40 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 25 Sep 2023 08:59:13 -0500 Subject: [PATCH 46/99] wip --- src/Illuminate/Http/Client/PendingRequest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index d264e00fb145..18658f105bd1 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -200,11 +200,11 @@ class PendingRequest protected $request; /** - * The Guzzle request options that are mergable via array_merge_recursive. + * The Guzzle request options that are mergeable via array_merge_recursive. * * @var array */ - protected $mergableOptions = [ + protected $mergeableOptions = [ 'cookies', 'form_params', 'headers', @@ -625,7 +625,7 @@ public function withOptions(array $options) { return tap($this, function () use ($options) { $this->options = array_replace_recursive( - array_merge_recursive($this->options, Arr::only($options, $this->mergableOptions)), + array_merge_recursive($this->options, Arr::only($options, $this->mergeableOptions)), $options ); }); @@ -1294,7 +1294,7 @@ public function runBeforeSendingCallbacks($request, array $options) public function mergeOptions(...$options) { return array_replace_recursive( - array_merge_recursive($this->options, Arr::only($options, $this->mergableOptions)), + array_merge_recursive($this->options, Arr::only($options, $this->mergeableOptions)), ...$options ); } From 118b3ec0ec44ce9cfb900cf2934c4d999177c80f Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 28 Sep 2023 16:27:54 +0200 Subject: [PATCH 47/99] wip --- src/Illuminate/Foundation/Testing/RefreshDatabase.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index 78adda9e1f1e..4c4e084ab0fa 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -87,11 +87,12 @@ public function beginDatabaseTransaction() foreach ($this->connectionsToTransact() as $name) { $connection = $database->connection($name); + $connection->setTransactionManager($transactionsManager); + if ($this->usingInMemoryDatabase()) { RefreshDatabaseState::$inMemoryConnections[$name] ??= $connection->getPdo(); } - $connection->setTransactionManager($transactionsManager); $dispatcher = $connection->getEventDispatcher(); $connection->unsetEventDispatcher(); From dd9a6f093ee60600ae7c5d81f60fd9909b44e1c9 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 3 Oct 2023 09:07:35 +0800 Subject: [PATCH 48/99] [11.x] Remove deprecated `Illuminate\Support\Composer` usage in (#48615) `MigratorGeneratorCommand` Signed-off-by: Mior Muhammad Zaki --- .../Console/MigrationGeneratorCommand.php | 14 +------------- .../Providers/ArtisanServiceProvider.php | 14 +++++++------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/Illuminate/Console/MigrationGeneratorCommand.php b/src/Illuminate/Console/MigrationGeneratorCommand.php index a3a3e002b675..fe1d98d81aa8 100644 --- a/src/Illuminate/Console/MigrationGeneratorCommand.php +++ b/src/Illuminate/Console/MigrationGeneratorCommand.php @@ -3,7 +3,6 @@ namespace Illuminate\Console; use Illuminate\Filesystem\Filesystem; -use Illuminate\Support\Composer; abstract class MigrationGeneratorCommand extends Command { @@ -14,28 +13,17 @@ abstract class MigrationGeneratorCommand extends Command */ protected $files; - /** - * The Composer instance. - * - * @var \Illuminate\Support\Composer - * - * @deprecated Will be removed in a future Laravel version. - */ - protected $composer; - /** * Create a new migration generator command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @param \Illuminate\Support\Composer $composer * @return void */ - public function __construct(Filesystem $files, Composer $composer) + public function __construct(Filesystem $files) { parent::__construct(); $this->files = $files; - $this->composer = $composer; } /** diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index b92bd4ac91cb..7269e90ede95 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -292,7 +292,7 @@ protected function registerCacheForgetCommand() protected function registerCacheTableCommand() { $this->app->singleton(CacheTableCommand::class, function ($app) { - return new CacheTableCommand($app['files'], $app['composer']); + return new CacheTableCommand($app['files']); }); } @@ -508,7 +508,7 @@ protected function registerNotificationMakeCommand() protected function registerNotificationTableCommand() { $this->app->singleton(NotificationTableCommand::class, function ($app) { - return new NotificationTableCommand($app['files'], $app['composer']); + return new NotificationTableCommand($app['files']); }); } @@ -638,7 +638,7 @@ protected function registerQueueWorkCommand() protected function registerQueueFailedTableCommand() { $this->app->singleton(FailedTableCommand::class, function ($app) { - return new FailedTableCommand($app['files'], $app['composer']); + return new FailedTableCommand($app['files']); }); } @@ -650,7 +650,7 @@ protected function registerQueueFailedTableCommand() protected function registerQueueTableCommand() { $this->app->singleton(TableCommand::class, function ($app) { - return new TableCommand($app['files'], $app['composer']); + return new TableCommand($app['files']); }); } @@ -662,7 +662,7 @@ protected function registerQueueTableCommand() protected function registerQueueBatchesTableCommand() { $this->app->singleton(BatchesTableCommand::class, function ($app) { - return new BatchesTableCommand($app['files'], $app['composer']); + return new BatchesTableCommand($app['files']); }); } @@ -722,7 +722,7 @@ protected function registerScopeMakeCommand() protected function registerSeederMakeCommand() { $this->app->singleton(SeederMakeCommand::class, function ($app) { - return new SeederMakeCommand($app['files'], $app['composer']); + return new SeederMakeCommand($app['files']); }); } @@ -734,7 +734,7 @@ protected function registerSeederMakeCommand() protected function registerSessionTableCommand() { $this->app->singleton(SessionTableCommand::class, function ($app) { - return new SessionTableCommand($app['files'], $app['composer']); + return new SessionTableCommand($app['files']); }); } From 106c01539cea5bc14e59dfeffb99433ca98ebcbb Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Tue, 10 Oct 2023 14:19:35 +0200 Subject: [PATCH 49/99] Reintroduce Carbon dependency (#48687) I believe this was inadvertently removed when resolving a merge conflict in 8dde5dc3dd02ff9975262d80ecf388a165643e41 --- src/Illuminate/Support/composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 3062f82161b0..5d84b67da753 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -23,6 +23,7 @@ "illuminate/conditionable": "^11.0", "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", + "nesbot/carbon": "^2.67", "voku/portable-ascii": "^2.0" }, "conflict": { From 838592e7de3fddef2df9f1dcbe741a427e637ba9 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 12 Oct 2023 10:14:04 +0200 Subject: [PATCH 50/99] [11.x] Symfony v7.0 support (#48403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Try Symfony v7 * 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 * 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/Http/Request.php Co-authored-by: Julius Kiekbusch * Update src/Illuminate/Console/Command.php Co-authored-by: Ngô Quốc Đạt * 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 * Uses Termwind v2 * Update composer.json * Update composer.json * Update composer.json * Update composer.json --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki Co-authored-by: Julius Kiekbusch Co-authored-by: Ngô Quốc Đạt Co-authored-by: StyleCI Bot Co-authored-by: Nuno Maduro --- composer.json | 32 +++++++++---------- src/Illuminate/Cache/composer.json | 2 +- src/Illuminate/Collections/composer.json | 2 +- src/Illuminate/Console/Application.php | 4 +-- .../Console/BufferedConsoleOutput.php | 4 +-- src/Illuminate/Console/Command.php | 3 +- src/Illuminate/Console/OutputStyle.php | 10 ++---- src/Illuminate/Console/QuestionHelper.php | 2 +- src/Illuminate/Console/composer.json | 4 +-- src/Illuminate/Cookie/composer.json | 4 +-- src/Illuminate/Database/composer.json | 2 +- src/Illuminate/Filesystem/composer.json | 6 ++-- .../Foundation/Console/ClosureCommand.php | 2 +- src/Illuminate/Http/Request.php | 8 ++--- src/Illuminate/Http/composer.json | 8 ++--- src/Illuminate/Mail/composer.json | 8 ++--- src/Illuminate/Process/composer.json | 2 +- src/Illuminate/Queue/composer.json | 2 +- src/Illuminate/Routing/composer.json | 6 ++-- .../Session/SymfonySessionDecorator.php | 32 ++++++------------- src/Illuminate/Session/composer.json | 4 +-- src/Illuminate/Support/composer.json | 6 ++-- .../Testing/ParallelConsoleOutput.php | 2 +- src/Illuminate/Validation/composer.json | 4 +-- 24 files changed, 69 insertions(+), 90 deletions(-) diff --git a/composer.json b/composer.json index e830ecddba7b..092eff4266dc 100644 --- a/composer.json +++ b/composer.json @@ -28,30 +28,30 @@ "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", "egulias/email-validator": "^3.2.1|^4.0", - "fruitcake/php-cors": "^1.2", + "fruitcake/php-cors": "^1.3", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.9", + "laravel/prompts": "dev-symfony7", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^3.0", "nesbot/carbon": "^2.67", - "nunomaduro/termwind": "^1.13", + "nunomaduro/termwind": "^2.0", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^6.3", - "symfony/error-handler": "^6.3", - "symfony/finder": "^6.3", - "symfony/http-foundation": "^6.3", - "symfony/http-kernel": "^6.3", - "symfony/mailer": "^6.3", - "symfony/mime": "^6.3", - "symfony/process": "^6.3", - "symfony/routing": "^6.3", - "symfony/uid": "^6.3", - "symfony/var-dumper": "^6.3", + "symfony/console": "^7.0", + "symfony/error-handler": "^7.0", + "symfony/finder": "^7.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/mailer": "^7.0", + "symfony/mime": "^7.0", + "symfony/process": "^7.0", + "symfony/routing": "^7.0", + "symfony/uid": "^7.0", + "symfony/var-dumper": "^7.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.4.1", "voku/portable-ascii": "^2.0" @@ -109,8 +109,8 @@ "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", - "symfony/cache": "^6.3", - "symfony/http-client": "^6.3.4" + "symfony/cache": "^7.0", + "symfony/http-client": "^7.0" }, "provide": { "psr/container-implementation": "1.1|2.0", diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index d486a4335f46..ec0d26e18469 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -40,7 +40,7 @@ "illuminate/database": "Required to use the database cache driver (^11.0).", "illuminate/filesystem": "Required to use the file cache driver (^11.0).", "illuminate/redis": "Required to use the redis cache driver (^11.0).", - "symfony/cache": "Required to use PSR-6 cache bridge (^6.3)." + "symfony/cache": "Required to use PSR-6 cache bridge (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index 80671fb4dade..1924032ab915 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -33,7 +33,7 @@ } }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.3)." + "symfony/var-dumper": "Required to use the dump method (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 98536ce41eec..f594258e9e76 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -206,9 +206,9 @@ public function output() * Add a command to the console. * * @param \Symfony\Component\Console\Command\Command $command - * @return \Symfony\Component\Console\Command\Command + * @return \Symfony\Component\Console\Command\Command|null */ - public function add(SymfonyCommand $command) + public function add(SymfonyCommand $command): ?SymfonyCommand { if ($command instanceof Command) { $command->setLaravel($this->laravel); diff --git a/src/Illuminate/Console/BufferedConsoleOutput.php b/src/Illuminate/Console/BufferedConsoleOutput.php index aa4e6ceedc4e..d4ee3954f393 100644 --- a/src/Illuminate/Console/BufferedConsoleOutput.php +++ b/src/Illuminate/Console/BufferedConsoleOutput.php @@ -27,10 +27,8 @@ public function fetch() /** * {@inheritdoc} - * - * @return void */ - protected function doWrite(string $message, bool $newline) + protected function doWrite(string $message, bool $newline): void { $this->buffer .= $message; diff --git a/src/Illuminate/Console/Command.php b/src/Illuminate/Console/Command.php index 7e1b3a1ff6ed..9e0290ebcc6d 100755 --- a/src/Illuminate/Console/Command.php +++ b/src/Illuminate/Console/Command.php @@ -190,9 +190,8 @@ public function run(InputInterface $input, OutputInterface $output): int * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return int */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { if ($this instanceof Isolatable && $this->option('isolated') !== false && ! $this->commandIsolationMutex()->create($this)) { diff --git a/src/Illuminate/Console/OutputStyle.php b/src/Illuminate/Console/OutputStyle.php index cbfc257220e7..b0d6f94e8b17 100644 --- a/src/Illuminate/Console/OutputStyle.php +++ b/src/Illuminate/Console/OutputStyle.php @@ -64,7 +64,7 @@ public function askQuestion(Question $question): mixed /** * {@inheritdoc} */ - public function write(string|iterable $messages, bool $newline = false, int $options = 0) + public function write(string|iterable $messages, bool $newline = false, int $options = 0): void { $this->newLinesWritten = $this->trailingNewLineCount($messages) + (int) $newline; $this->newLineWritten = $this->newLinesWritten > 0; @@ -74,10 +74,8 @@ public function write(string|iterable $messages, bool $newline = false, int $opt /** * {@inheritdoc} - * - * @return void */ - public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void { $this->newLinesWritten = $this->trailingNewLineCount($messages) + 1; $this->newLineWritten = true; @@ -87,10 +85,8 @@ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORM /** * {@inheritdoc} - * - * @return void */ - public function newLine(int $count = 1) + public function newLine(int $count = 1): void { $this->newLinesWritten += $count; $this->newLineWritten = $this->newLinesWritten > 0; diff --git a/src/Illuminate/Console/QuestionHelper.php b/src/Illuminate/Console/QuestionHelper.php index 43b4cf8296c1..a3bf62a520ab 100644 --- a/src/Illuminate/Console/QuestionHelper.php +++ b/src/Illuminate/Console/QuestionHelper.php @@ -17,7 +17,7 @@ class QuestionHelper extends SymfonyQuestionHelper * * @return void */ - protected function writePrompt(OutputInterface $output, Question $question) + protected function writePrompt(OutputInterface $output, Question $question): void { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 38ecc805c087..fa918c8a6329 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -23,8 +23,8 @@ "illuminate/view": "^11.0", "laravel/prompts": "^0.1.9", "nunomaduro/termwind": "^1.13", - "symfony/console": "^6.3", - "symfony/process": "^6.3" + "symfony/console": "^7.0", + "symfony/process": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index b2ffb16f88dc..e8514738ce54 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -20,8 +20,8 @@ "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.3", - "symfony/http-kernel": "^6.3" + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index 3c76a0174268..0e3ed4ca7c50 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -42,7 +42,7 @@ "illuminate/events": "Required to use the observers with Eloquent (^11.0).", "illuminate/filesystem": "Required to use the migrations (^11.0).", "illuminate/pagination": "Required to paginate the result set (^11.0).", - "symfony/finder": "Required to use Eloquent model factories (^6.3)." + "symfony/finder": "Required to use Eloquent model factories (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index b2657b376d75..b46be7d136d2 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -19,7 +19,7 @@ "illuminate/contracts": "^11.0", "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", - "symfony/finder": "^6.3" + "symfony/finder": "^7.0" }, "autoload": { "psr-4": { @@ -41,8 +41,8 @@ "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.3).", - "symfony/mime": "Required to enable support for guessing extensions (^6.3)." + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/mime": "Required to enable support for guessing extensions (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Foundation/Console/ClosureCommand.php b/src/Illuminate/Foundation/Console/ClosureCommand.php index 4cd54e8e4a79..c5be75ecc103 100644 --- a/src/Illuminate/Foundation/Console/ClosureCommand.php +++ b/src/Illuminate/Foundation/Console/ClosureCommand.php @@ -39,7 +39,7 @@ public function __construct($signature, Closure $callback) * @param \Symfony\Component\Console\Output\OutputInterface $output * @return int */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $inputs = array_merge($input->getArguments(), $input->getOptions()); diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index 251bb2c7c940..4707dbe1fd4a 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -533,7 +533,7 @@ protected function filterFiles($files) */ public function hasSession(bool $skipIfUninitialized = false): bool { - return ! is_null($this->session); + return $this->session instanceof SymfonySessionDecorator; } /** @@ -542,7 +542,7 @@ public function hasSession(bool $skipIfUninitialized = false): bool public function getSession(): SessionInterface { return $this->hasSession() - ? new SymfonySessionDecorator($this->session()) + ? $this->session : throw new SessionNotFoundException; } @@ -559,7 +559,7 @@ public function session() throw new RuntimeException('Session store not set on request.'); } - return $this->session; + return $this->session->store; } /** @@ -570,7 +570,7 @@ public function session() */ public function setLaravelSession($session) { - $this->session = $session; + $this->session = new SymfonySessionDecorator($session); } /** diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 05c6b9d00eba..768d72bc6637 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -16,15 +16,15 @@ "require": { "php": "^8.2", "ext-filter": "*", - "fruitcake/php-cors": "^1.2", + "fruitcake/php-cors": "^1.3", "guzzlehttp/uri-template": "^1.0", "illuminate/collections": "^11.0", "illuminate/macroable": "^11.0", "illuminate/session": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.3", - "symfony/http-kernel": "^6.3", - "symfony/mime": "^6.3" + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/mime": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 42a64ccdac78..ad0211e96d7c 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -22,7 +22,7 @@ "illuminate/support": "^11.0", "league/commonmark": "^2.2", "psr/log": "^1.0|^2.0|^3.0", - "symfony/mailer": "^6.3", + "symfony/mailer": "^7.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5" }, "autoload": { @@ -37,9 +37,9 @@ }, "suggest": { "aws/aws-sdk-php": "Required to use the SES mail driver (^3.235.5).", - "symfony/http-client": "Required to use the Symfony API mail transports (^6.3).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.3).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.3)." + "symfony/http-client": "Required to use the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Process/composer.json b/src/Illuminate/Process/composer.json index c056b4aac494..76eca7284827 100644 --- a/src/Illuminate/Process/composer.json +++ b/src/Illuminate/Process/composer.json @@ -19,7 +19,7 @@ "illuminate/contracts": "^10.0", "illuminate/macroable": "^10.0", "illuminate/support": "^10.0", - "symfony/process": "^6.3" + "symfony/process": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index f341b6768392..66bc9c0f4afd 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -25,7 +25,7 @@ "illuminate/support": "^11.0", "laravel/serializable-closure": "^1.2.2", "ramsey/uuid": "^4.7", - "symfony/process": "^6.3" + "symfony/process": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index 82825c675cab..92d08b0908fd 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -25,9 +25,9 @@ "illuminate/pipeline": "^11.0", "illuminate/session": "^11.0", "illuminate/support": "^11.0", - "symfony/http-foundation": "^6.3", - "symfony/http-kernel": "^6.3", - "symfony/routing": "^6.3" + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/routing": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Session/SymfonySessionDecorator.php b/src/Illuminate/Session/SymfonySessionDecorator.php index 02034910bb9e..978816683fe7 100644 --- a/src/Illuminate/Session/SymfonySessionDecorator.php +++ b/src/Illuminate/Session/SymfonySessionDecorator.php @@ -13,9 +13,9 @@ class SymfonySessionDecorator implements SessionInterface /** * The underlying Laravel session store. * - * @var \Illuminate\Session\Store + * @var \Illuminate\Contracts\Session\Session */ - protected $store; + public readonly Session $store; /** * Create a new session decorator. @@ -46,10 +46,8 @@ public function getId(): string /** * {@inheritdoc} - * - * @return void */ - public function setId(string $id) + public function setId(string $id): void { $this->store->setId($id); } @@ -64,10 +62,8 @@ public function getName(): string /** * {@inheritdoc} - * - * @return void */ - public function setName(string $name) + public function setName(string $name): void { $this->store->setName($name); } @@ -94,10 +90,8 @@ public function migrate(bool $destroy = false, int $lifetime = null): bool /** * {@inheritdoc} - * - * @return void */ - public function save() + public function save(): void { $this->store->save(); } @@ -120,10 +114,8 @@ public function get(string $name, mixed $default = null): mixed /** * {@inheritdoc} - * - * @return void */ - public function set(string $name, mixed $value) + public function set(string $name, mixed $value): void { $this->store->put($name, $value); } @@ -138,10 +130,8 @@ public function all(): array /** * {@inheritdoc} - * - * @return void */ - public function replace(array $attributes) + public function replace(array $attributes): void { $this->store->replace($attributes); } @@ -156,10 +146,8 @@ public function remove(string $name): mixed /** * {@inheritdoc} - * - * @return void */ - public function clear() + public function clear(): void { $this->store->flush(); } @@ -174,10 +162,8 @@ public function isStarted(): bool /** * {@inheritdoc} - * - * @return void */ - public function registerBag(SessionBagInterface $bag) + public function registerBag(SessionBagInterface $bag): void { throw new BadMethodCallException('Method not implemented by Laravel.'); } diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index 85a08b559167..56789edc0f78 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -21,8 +21,8 @@ "illuminate/contracts": "^11.0", "illuminate/filesystem": "^11.0", "illuminate/support": "^11.0", - "symfony/finder": "^6.3", - "symfony/http-foundation": "^6.3" + "symfony/finder": "^7.0", + "symfony/http-foundation": "^7.0" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 5d84b67da753..e0b4a7e13119 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -46,9 +46,9 @@ "illuminate/filesystem": "Required to use the composer class (^11.0).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", - "symfony/process": "Required to use the composer class (^6.3).", - "symfony/uid": "Required to use Str::ulid() (^6.3).", - "symfony/var-dumper": "Required to use the dd function (^6.3).", + "symfony/process": "Required to use the composer class (^7.0).", + "symfony/uid": "Required to use Str::ulid() (^7.0).", + "symfony/var-dumper": "Required to use the dd function (^7.0).", "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." }, "config": { diff --git a/src/Illuminate/Testing/ParallelConsoleOutput.php b/src/Illuminate/Testing/ParallelConsoleOutput.php index 91008dde890d..6df016b037b6 100644 --- a/src/Illuminate/Testing/ParallelConsoleOutput.php +++ b/src/Illuminate/Testing/ParallelConsoleOutput.php @@ -49,7 +49,7 @@ public function __construct($output) * @param int $options * @return void */ - public function write($messages, bool $newline = false, int $options = 0) + public function write($messages, bool $newline = false, int $options = 0): void { $messages = collect($messages)->filter(function ($message) { return ! Str::contains($message, $this->ignore); diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 4cf5d733e16b..a1f6fe3e6992 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -25,8 +25,8 @@ "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", "illuminate/translation": "^11.0", - "symfony/http-foundation": "^6.3", - "symfony/mime": "^6.3" + "symfony/http-foundation": "^7.0", + "symfony/mime": "^7.0" }, "autoload": { "psr-4": { From 873003132652c989a7a4b17df1f89e9577ba1257 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 12 Oct 2023 16:02:10 +0100 Subject: [PATCH 51/99] [11.x] Bumps termwind on `illuminate\console` (#48714) --- src/Illuminate/Console/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index fa918c8a6329..57f90e368794 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -22,7 +22,7 @@ "illuminate/support": "^11.0", "illuminate/view": "^11.0", "laravel/prompts": "^0.1.9", - "nunomaduro/termwind": "^1.13", + "nunomaduro/termwind": "^2.0", "symfony/console": "^7.0", "symfony/process": "^7.0" }, From 02dadc0a2d680c1ca206b4d67e79d8a13b84ddf8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 12 Oct 2023 16:07:26 +0100 Subject: [PATCH 52/99] [11.x] Bumps `laravel/promps` (#48715) --- src/Illuminate/Console/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 57f90e368794..c09ee5d922cd 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -21,7 +21,7 @@ "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", "illuminate/view": "^11.0", - "laravel/prompts": "^0.1.9", + "laravel/prompts": "dev-symfony7", "nunomaduro/termwind": "^2.0", "symfony/console": "^7.0", "symfony/process": "^7.0" From 713260385278be243f9ffa77e129c3b62e9ee14b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 17 Oct 2023 04:06:26 +0800 Subject: [PATCH 53/99] [11.x] Use PHPUnit's `transformException()` introduced in PHPUnit 10.1 (#48733) * [11.x] Use PHPUnit's `transformException()` introduced in PHPUnit 10.1 This removed the need to override `runTest()` method which is marked as `final` in the upcoming PHPUnit 11. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- src/Illuminate/Foundation/Testing/TestCase.php | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 092eff4266dc..b4547d815abd 100644 --- a/composer.json +++ b/composer.json @@ -107,7 +107,7 @@ "orchestra/testbench-core": "^9.0", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", - "phpunit/phpunit": "^10.0.7", + "phpunit/phpunit": "^10.1", "predis/predis": "^2.0.2", "symfony/cache": "^7.0", "symfony/http-client": "^7.0" diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index 8d8f23e57254..a42ce7ff98bc 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -162,21 +162,15 @@ protected function setUpTraits() /** * {@inheritdoc} */ - protected function runTest(): mixed + protected function transformException(Throwable $error): Throwable { - $result = null; + $response = static::$latestResponse ?? null; - try { - $result = parent::runTest(); - } catch (Throwable $e) { - if (! is_null(static::$latestResponse)) { - static::$latestResponse->transformNotSuccessfulException($e); - } - - throw $e; + if (! is_null($response)) { + $response->transformNotSuccessfulException($error); } - return $result; + return $error; } /** From d9d549dd5db1a0c0fe0bc283613ec68664e82a49 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 24 Oct 2023 23:09:54 +0800 Subject: [PATCH 54/99] [11.x] Support for Doctrine DBAL 4 (#48752) * Doctrine 4 Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * 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 * 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 * Update src/Illuminate/Database/Schema/Grammars/ChangeColumn.php * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Dries Vints --- composer.json | 4 +- .../Database/DBAL/TimestampType.php | 28 +-- .../PDO/Concerns/ConnectsToDatabase.php | 6 +- src/Illuminate/Database/PDO/Connection.php | 34 ++-- .../Database/PDO/SqlServerConnection.php | 34 ++-- .../Database/PDO/SqlServerDriver.php | 6 +- src/Illuminate/Database/Schema/Blueprint.php | 2 +- src/Illuminate/Database/Schema/Builder.php | 4 +- .../Database/Schema/Grammars/ChangeColumn.php | 10 +- .../Database/Schema/Grammars/Grammar.php | 2 +- .../Database/Schema/Grammars/RenameColumn.php | 19 +- .../Schema/Grammars/SQLiteGrammar.php | 24 ++- src/Illuminate/Database/composer.json | 2 +- .../DatabaseMySqlSchemaGrammarTest.php | 2 +- .../DatabaseSQLiteSchemaGrammarTest.php | 4 +- .../DatabaseSchemaBuilderIntegrationTest.php | 56 ------ tests/Database/DatabaseSchemaBuilderTest.php | 11 +- .../ConfigureCustomDoctrineTypeTest.php | 8 +- .../Database/DBAL/TimestampTypeTest.php | 2 +- .../Database/DatabaseSchemaBlueprintTest.php} | 166 +++++++++--------- .../Database/DatabaseSchemaBuilderTest.php | 76 ++++++++ .../Database/Fixtures/TinyInteger.php | 2 +- 22 files changed, 271 insertions(+), 231 deletions(-) rename tests/{Database/DatabaseSchemaBlueprintIntegrationTest.php => Integration/Database/DatabaseSchemaBlueprintTest.php} (82%) create mode 100644 tests/Integration/Database/DatabaseSchemaBuilderTest.php diff --git a/composer.json b/composer.json index b4547d815abd..932521b7f0a9 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "ext-gmp": "*", "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^3.5.1", + "doctrine/dbal": "^4.0", "fakerphp/faker": "^1.21", "guzzlehttp/guzzle": "^7.6", "league/flysystem-aws-s3-v3": "^3.0", @@ -161,7 +161,7 @@ "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.6).", diff --git a/src/Illuminate/Database/DBAL/TimestampType.php b/src/Illuminate/Database/DBAL/TimestampType.php index e702523925a1..aee4a2a0130b 100644 --- a/src/Illuminate/Database/DBAL/TimestampType.php +++ b/src/Illuminate/Database/DBAL/TimestampType.php @@ -2,19 +2,14 @@ namespace Illuminate\Database\DBAL; -use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MariaDb1027Platform; -use Doctrine\DBAL\Platforms\MariaDb1052Platform; +use Doctrine\DBAL\Platforms\Exception\NotSupported; +use Doctrine\DBAL\Platforms\MariaDB1052Platform; use Doctrine\DBAL\Platforms\MariaDBPlatform; -use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; use Doctrine\DBAL\Platforms\MySQLPlatform; -use Doctrine\DBAL\Platforms\PostgreSQL100Platform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; -use Doctrine\DBAL\Platforms\SQLServer2012Platform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Types\PhpDateTimeMappingType; use Doctrine\DBAL\Types\Type; @@ -24,24 +19,19 @@ class TimestampType extends Type implements PhpDateTimeMappingType /** * {@inheritdoc} * - * @throws DBALException + * @throws \Doctrine\DBAL\Platforms\Exception\NotSupported */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { return match (get_class($platform)) { MySQLPlatform::class, - MySQL57Platform::class, MySQL80Platform::class, MariaDBPlatform::class, - MariaDb1027Platform::class, - MariaDb1052Platform::class, => $this->getMySqlPlatformSQLDeclaration($column), - PostgreSQLPlatform::class, - PostgreSQL94Platform::class, - PostgreSQL100Platform::class => $this->getPostgresPlatformSQLDeclaration($column), - SQLServerPlatform::class, - SQLServer2012Platform::class => $this->getSqlServerPlatformSQLDeclaration($column), - SqlitePlatform::class => 'DATETIME', - default => throw new DBALException('Invalid platform: '.substr(strrchr(get_class($platform), '\\'), 1)), + MariaDB1052Platform::class, => $this->getMySqlPlatformSQLDeclaration($column), + PostgreSQLPlatform::class => $this->getPostgresPlatformSQLDeclaration($column), + SQLServerPlatform::class => $this->getSqlServerPlatformSQLDeclaration($column), + SQLitePlatform::class => 'DATETIME', + default => throw NotSupported::new('TIMESTAMP'), }; } diff --git a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php b/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php index d2a8d6006df3..a2354182bc2c 100644 --- a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php +++ b/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\PDO\Concerns; +use Doctrine\DBAL\Driver\Connection as ConnectionContract; use Illuminate\Database\PDO\Connection; use InvalidArgumentException; use PDO; @@ -12,14 +13,11 @@ trait ConnectsToDatabase * Create a new database connection. * * @param mixed[] $params - * @param string|null $username - * @param string|null $password - * @param mixed[] $driverOptions * @return \Illuminate\Database\PDO\Connection * * @throws \InvalidArgumentException */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params): ConnectionContract { if (! isset($params['pdo']) || ! $params['pdo'] instanceof PDO) { throw new InvalidArgumentException('Laravel requires the "pdo" property to be set and be a PDO instance.'); diff --git a/src/Illuminate/Database/PDO/Connection.php b/src/Illuminate/Database/PDO/Connection.php index 427bda2cc4b3..a8318867d741 100644 --- a/src/Illuminate/Database/PDO/Connection.php +++ b/src/Illuminate/Database/PDO/Connection.php @@ -2,18 +2,18 @@ namespace Illuminate\Database\PDO; +use Doctrine\DBAL\Driver\Connection as ConnectionContract; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Result; use Doctrine\DBAL\Driver\PDO\Statement; use Doctrine\DBAL\Driver\Result as ResultInterface; -use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use PDO; use PDOException; use PDOStatement; -class Connection implements ServerInfoAwareConnection +class Connection implements ConnectionContract { /** * The underlying PDO connection. @@ -94,11 +94,11 @@ public function query(string $sql): ResultInterface * Get the last insert ID. * * @param string|null $name - * @return mixed + * @return string|int * * @throws \Doctrine\DBAL\Driver\PDO\Exception */ - public function lastInsertId($name = null) + public function lastInsertId($name = null): string|int { try { if ($name === null) { @@ -125,31 +125,31 @@ protected function createStatement(PDOStatement $stmt): Statement /** * Begin a new database transaction. * - * @return bool + * @return void */ - public function beginTransaction() + public function beginTransaction(): void { - return $this->connection->beginTransaction(); + $this->connection->beginTransaction(); } /** * Commit a database transaction. * - * @return bool + * @return void */ - public function commit() + public function commit(): void { - return $this->connection->commit(); + $this->connection->commit(); } /** * Rollback a database transaction. * - * @return bool + * @return void */ - public function rollBack() + public function rollBack(): void { - return $this->connection->rollBack(); + $this->connection->rollBack(); } /** @@ -159,7 +159,7 @@ public function rollBack() * @param string $type * @return string */ - public function quote($input, $type = ParameterType::STRING) + public function quote($input, $type = ParameterType::STRING): string { return $this->connection->quote($input, $type); } @@ -169,17 +169,17 @@ public function quote($input, $type = ParameterType::STRING) * * @return string */ - public function getServerVersion() + public function getServerVersion(): string { return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** - * Get the wrapped PDO connection. + * Get the native PDO connection. * * @return \PDO */ - public function getWrappedConnection(): PDO + public function getNativeConnection(): PDO { return $this->connection; } diff --git a/src/Illuminate/Database/PDO/SqlServerConnection.php b/src/Illuminate/Database/PDO/SqlServerConnection.php index d32d3c3e0a6d..9da969333e2f 100644 --- a/src/Illuminate/Database/PDO/SqlServerConnection.php +++ b/src/Illuminate/Database/PDO/SqlServerConnection.php @@ -2,14 +2,14 @@ namespace Illuminate\Database\PDO; +use Doctrine\DBAL\Driver\Connection as ConnectionContract; use Doctrine\DBAL\Driver\PDO\SQLSrv\Statement; use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use PDO; -class SqlServerConnection implements ServerInfoAwareConnection +class SqlServerConnection implements ConnectionContract { /** * The underlying connection instance. @@ -68,9 +68,9 @@ public function exec(string $statement): int * Get the last insert ID. * * @param string|null $name - * @return mixed + * @return string|int */ - public function lastInsertId($name = null) + public function lastInsertId($name = null): string|int { if ($name === null) { return $this->connection->lastInsertId($name); @@ -84,31 +84,31 @@ public function lastInsertId($name = null) /** * Begin a new database transaction. * - * @return bool + * @return void */ - public function beginTransaction() + public function beginTransaction(): void { - return $this->connection->beginTransaction(); + $this->connection->beginTransaction(); } /** * Commit a database transaction. * - * @return bool + * @return void */ - public function commit() + public function commit(): void { - return $this->connection->commit(); + $this->connection->commit(); } /** * Rollback a database transaction. * - * @return bool + * @return void */ - public function rollBack() + public function rollBack(): void { - return $this->connection->rollBack(); + $this->connection->rollBack(); } /** @@ -118,7 +118,7 @@ public function rollBack() * @param int $type * @return string */ - public function quote($value, $type = ParameterType::STRING) + public function quote($value, $type = ParameterType::STRING): string { $val = $this->connection->quote($value, $type); @@ -135,17 +135,17 @@ public function quote($value, $type = ParameterType::STRING) * * @return string */ - public function getServerVersion() + public function getServerVersion(): string { return $this->connection->getServerVersion(); } /** - * Get the wrapped PDO connection. + * Get the native PDO connection. * * @return \PDO */ - public function getWrappedConnection(): PDO + public function getNativeConnection(): PDO { return $this->connection->getWrappedConnection(); } diff --git a/src/Illuminate/Database/PDO/SqlServerDriver.php b/src/Illuminate/Database/PDO/SqlServerDriver.php index 1b0d9574e748..ac7b8a1aedef 100644 --- a/src/Illuminate/Database/PDO/SqlServerDriver.php +++ b/src/Illuminate/Database/PDO/SqlServerDriver.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\PDO; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; +use Doctrine\DBAL\Driver\Connection as ConnectionContract; class SqlServerDriver extends AbstractSQLServerDriver { @@ -10,12 +11,9 @@ class SqlServerDriver extends AbstractSQLServerDriver * Create a new database connection. * * @param mixed[] $params - * @param string|null $username - * @param string|null $password - * @param mixed[] $driverOptions * @return \Illuminate\Database\PDO\SqlServerConnection */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params): ConnectionContract { return new SqlServerConnection( new Connection($params['pdo']) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index f41123aca642..ec5a87bb2399 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -977,7 +977,7 @@ public function float($column, $total = 8, $places = 2, $unsigned = false) * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function double($column, $total = null, $places = null, $unsigned = false) + public function double($column, $total = 15, $places = 6, $unsigned = false) { return $this->addColumn('double', $column, compact('total', 'places', 'unsigned')); } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index a38b526c9a33..0717a9019939 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -239,7 +239,9 @@ public function getColumnType($table, $column) { $table = $this->connection->getTablePrefix().$table; - return $this->connection->getDoctrineColumn($table, $column)->getType()->getName(); + $type = $this->connection->getDoctrineColumn($table, $column)->getType(); + + return $type::lookupName($type); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php index 009f7ab93758..7f429c0eccc5 100644 --- a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php +++ b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php @@ -118,22 +118,22 @@ protected static function getDoctrineColumn(Table $table, Fluent $fluent) */ protected static function getDoctrineColumnChangeOptions(Fluent $fluent) { - $options = ['type' => static::getDoctrineColumnType($fluent['type'])]; + $options = ['Type' => static::getDoctrineColumnType($fluent['type'])]; if (! in_array($fluent['type'], ['smallint', 'integer', 'bigint'])) { - $options['autoincrement'] = false; + $options['Autoincrement'] = false; } if (in_array($fluent['type'], ['tinyText', 'text', 'mediumText', 'longText'])) { - $options['length'] = static::calculateDoctrineTextLength($fluent['type']); + $options['Length'] = static::calculateDoctrineTextLength($fluent['type']); } if ($fluent['type'] === 'char') { - $options['fixed'] = true; + $options['Fixed'] = true; } if (static::doesntNeedCharacterOptions($fluent['type'])) { - $options['customSchemaOptions'] = [ + $options['PlatformOptions'] = [ 'collation' => '', 'charset' => '', ]; diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index c6325b57cd04..9933a38be0fc 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -332,7 +332,7 @@ public function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema $table = $schema->introspectTable($tableName); - return new TableDiff(tableName: $tableName, fromTable: $table); + return $schema->createComparator()->compareTables(oldTable: $table, newTable: $table); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php index a10e72b2f1f0..ff611c93160a 100644 --- a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php +++ b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php @@ -62,11 +62,20 @@ protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, */ protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) { - $tableDiff->renamedColumns = [ - $command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column)), - ]; - - return $tableDiff; + return new TableDiff( + $tableDiff->getOldTable(), + $tableDiff->getAddedColumns(), + $tableDiff->getModifiedColumns(), + $tableDiff->getDroppedColumns(), + [$command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column))], + $tableDiff->getAddedIndexes(), + $tableDiff->getModifiedIndexes(), + $tableDiff->getDroppedIndexes(), + $tableDiff->getRenamedIndexes(), + $tableDiff->getAddedForeignKeys(), + $tableDiff->getModifiedColumns(), + $tableDiff->getDroppedForeignKeys(), + ); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 9c3b1c9fb61e..07827d876f77 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Schema\Grammars; use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; @@ -318,13 +319,32 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connect $blueprint, $schema = $connection->getDoctrineSchemaManager() ); + $droppedColumns = []; + foreach ($command->columns as $name) { - $tableDiff->removedColumns[$name] = $connection->getDoctrineColumn( + $droppedColumns[$name] = $connection->getDoctrineColumn( $this->getTablePrefix().$blueprint->getTable(), $name ); } - return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); + + return (array) $platform->getAlterTableSQL( + new TableDiff( + $tableDiff->getOldTable(), + $tableDiff->getAddedColumns(), + $tableDiff->getModifiedColumns(), + $droppedColumns, + $tableDiff->getRenamedColumns(), + $tableDiff->getAddedIndexes(), + $tableDiff->getModifiedIndexes(), + $tableDiff->getDroppedIndexes(), + $tableDiff->getRenamedIndexes(), + $tableDiff->getAddedForeignKeys(), + $tableDiff->getModifiedColumns(), + $tableDiff->getDroppedForeignKeys(), + ) + ); } } diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index 0e3ed4ca7c50..c840493bd621 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -36,7 +36,7 @@ }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.21).", "illuminate/console": "Required to use the database commands (^11.0).", "illuminate/events": "Required to use the observers with Eloquent (^11.0).", diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 7bca44c41a3d..d601b04a6fb0 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -756,7 +756,7 @@ public function testAddingDouble() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` double not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` double(15, 6) not null', $statements[0]); } public function testAddingDoubleSpecifyingPrecision() diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 1a80835d8abe..a414e813a49a 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -164,7 +164,7 @@ public function testRenameIndex() }); $manager = $db->getConnection()->getDoctrineSchemaManager(); - $details = $manager->listTableDetails('prefix_users'); + $details = $manager->introspectTable('prefix_users'); $this->assertTrue($details->hasIndex('index1')); $this->assertFalse($details->hasIndex('index2')); @@ -172,7 +172,7 @@ public function testRenameIndex() $table->renameIndex('index1', 'index2'); }); - $details = $manager->listTableDetails('prefix_users'); + $details = $manager->introspectTable('prefix_users'); $this->assertFalse($details->hasIndex('index1')); $this->assertTrue($details->hasIndex('index2')); diff --git a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php index d469645fc8a1..719540076439 100644 --- a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php +++ b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php @@ -39,28 +39,6 @@ protected function tearDown(): void Facade::setFacadeApplication(null); } - public function testDropAllTablesWorksWithForeignKeys() - { - $this->db->connection()->getSchemaBuilder()->create('table1', function (Blueprint $table) { - $table->integer('id'); - $table->string('name'); - }); - - $this->db->connection()->getSchemaBuilder()->create('table2', function (Blueprint $table) { - $table->integer('id'); - $table->string('user_id'); - $table->foreign('user_id')->references('id')->on('table1'); - }); - - $this->assertTrue($this->db->connection()->getSchemaBuilder()->hasTable('table1')); - $this->assertTrue($this->db->connection()->getSchemaBuilder()->hasTable('table2')); - - $this->db->connection()->getSchemaBuilder()->dropAllTables(); - - $this->assertFalse($this->db->connection()->getSchemaBuilder()->hasTable('table1')); - $this->assertFalse($this->db->connection()->getSchemaBuilder()->hasTable('table2')); - } - public function testHasColumnWithTablePrefix() { $this->db->connection()->setTablePrefix('test_'); @@ -73,40 +51,6 @@ public function testHasColumnWithTablePrefix() $this->assertTrue($this->db->connection()->getSchemaBuilder()->hasColumn('table1', 'name')); } - public function testHasColumnAndIndexWithPrefixIndexDisabled() - { - $this->db->addConnection([ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => 'example_', - 'prefix_indexes' => false, - ]); - - $this->db->connection()->getSchemaBuilder()->create('table1', function (Blueprint $table) { - $table->integer('id'); - $table->string('name')->index(); - }); - - $this->assertArrayHasKey('table1_name_index', $this->db->connection()->getDoctrineSchemaManager()->listTableIndexes('example_table1')); - } - - public function testHasColumnAndIndexWithPrefixIndexEnabled() - { - $this->db->addConnection([ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => 'example_', - 'prefix_indexes' => true, - ]); - - $this->db->connection()->getSchemaBuilder()->create('table1', function (Blueprint $table) { - $table->integer('id'); - $table->string('name')->index(); - }); - - $this->assertArrayHasKey('example_table1_name_index', $this->db->connection()->getDoctrineSchemaManager()->listTableIndexes('example_table1')); - } - public function testDropColumnWithTablePrefix() { $this->db->connection()->setTablePrefix('test_'); diff --git a/tests/Database/DatabaseSchemaBuilderTest.php b/tests/Database/DatabaseSchemaBuilderTest.php index b22bfd7dc70e..0e39e494308e 100755 --- a/tests/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Database/DatabaseSchemaBuilderTest.php @@ -2,8 +2,11 @@ namespace Illuminate\Tests\Database; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Types\Type; use Illuminate\Database\Connection; use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Schema\Grammars\Grammar; use LogicException; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -70,15 +73,15 @@ public function testTableHasColumns() public function testGetColumnTypeAddsPrefix() { $connection = m::mock(Connection::class); - $column = m::mock(stdClass::class); - $type = m::mock(stdClass::class); - $grammar = m::mock(stdClass::class); + $column = m::mock(Column::class); + $type = m::mock(Type::class); + $grammar = m::mock(Grammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $builder = new Builder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); $connection->shouldReceive('getDoctrineColumn')->once()->with('prefix_users', 'id')->andReturn($column); $column->shouldReceive('getType')->once()->andReturn($type); - $type->shouldReceive('getName')->once()->andReturn('integer'); + $type->shouldReceive('lookupName')->once()->andReturn('integer'); $this->assertSame('integer', $builder->getColumnType('users', 'id')); } diff --git a/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php b/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php index f0f36deb4e36..6cf2c07af602 100644 --- a/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php +++ b/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php @@ -25,7 +25,7 @@ public function testRegisterCustomDoctrineTypesWithNonDefaultDatabaseConnections { $this->assertTrue( DB::connection() - ->getDoctrineSchemaManager() + ->getDoctrineConnection() ->getDatabasePlatform() ->hasDoctrineTypeMappingFor('xml') ); @@ -34,7 +34,7 @@ public function testRegisterCustomDoctrineTypesWithNonDefaultDatabaseConnections // this is not the default connection but it has the custom type mappings $this->assertTrue( DB::connection('sqlite') - ->getDoctrineSchemaManager() + ->getDoctrineConnection() ->getDatabasePlatform() ->hasDoctrineTypeMappingFor('xml') ); @@ -87,7 +87,7 @@ public function testRenameConfiguredCustomDoctrineColumnTypeWithMysql() class PostgresXmlType extends Type { - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { return 'xml'; } @@ -100,7 +100,7 @@ public function getName() class MySQLBitType extends Type { - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { return 'bit'; } diff --git a/tests/Integration/Database/DBAL/TimestampTypeTest.php b/tests/Integration/Database/DBAL/TimestampTypeTest.php index d7cec38c53b0..f53265cf8431 100644 --- a/tests/Integration/Database/DBAL/TimestampTypeTest.php +++ b/tests/Integration/Database/DBAL/TimestampTypeTest.php @@ -20,7 +20,7 @@ public function testRegisterTimestampTypeOnConnection() { $this->assertTrue( $this->app['db']->connection() - ->getDoctrineSchemaManager() + ->getDoctrineConnection() ->getDatabasePlatform() ->hasDoctrineTypeMappingFor('timestamp') ); diff --git a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php similarity index 82% rename from tests/Database/DatabaseSchemaBlueprintIntegrationTest.php rename to tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 5fd34340606a..66a217a02f15 100644 --- a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -1,53 +1,38 @@ db = $db = new DB; - - $db->addConnection([ - 'driver' => 'sqlite', - 'database' => ':memory:', - ]); - - $db->setAsGlobal(); + $this->beforeApplicationDestroyed(function () { + Schema::useNativeSchemaOperationsIfPossible(false); + }); - $container = new Container; - $container->instance('db', $db->getDatabaseManager()); - Facade::setFacadeApplication($container); + parent::setUp(); } - protected function tearDown(): void + protected function defineEnvironment($app) { - Facade::clearResolvedInstances(); - Facade::setFacadeApplication(null); - $this->db->connection()->getSchemaBuilder()->useNativeSchemaOperationsIfPossible(false); + $app['config']->set([ + 'database.default' => 'testing', + ]); } public function testRenamingAndChangingColumnsWork() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name'); $table->string('age'); }); @@ -57,31 +42,19 @@ public function testRenamingAndChangingColumnsWork() $table->integer('age')->change(); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); // Expect one of the following two query sequences to be present... $expected = [ [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) NOT NULL, age INTEGER NOT NULL)', + 'CREATE TABLE users (name VARCHAR NOT NULL, age INTEGER NOT NULL)', 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users', 'DROP TABLE __temp__users', 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', 'DROP TABLE users', - 'CREATE TABLE users (first_name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL COLLATE "BINARY")', - 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users', - 'DROP TABLE __temp__users', - ], - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE "BINARY", age INTEGER NOT NULL)', - 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users', - 'DROP TABLE __temp__users', - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (first_name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL COLLATE "BINARY")', + 'CREATE TABLE users (first_name VARCHAR NOT NULL, age VARCHAR NOT NULL)', 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users', 'DROP TABLE __temp__users', ], @@ -92,7 +65,7 @@ public function testRenamingAndChangingColumnsWork() public function testRenamingColumnsWithoutDoctrineWorks() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $schema->useNativeSchemaOperationsIfPossible(); @@ -130,7 +103,7 @@ public function testRenamingColumnsWithoutDoctrineWorks() public function testDroppingColumnsWithoutDoctrineWorks() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $schema->useNativeSchemaOperationsIfPossible(); @@ -144,7 +117,7 @@ public function testDroppingColumnsWithoutDoctrineWorks() public function testNativeColumnModifyingOnMySql() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $schema->useNativeSchemaOperationsIfPossible(); @@ -172,7 +145,7 @@ public function testNativeColumnModifyingOnMySql() public function testNativeColumnModifyingOnPostgreSql() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $schema->useNativeSchemaOperationsIfPossible(); @@ -248,7 +221,7 @@ public function testNativeColumnModifyingOnPostgreSql() public function testNativeColumnModifyingOnSqlServer() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $schema->useNativeSchemaOperationsIfPossible(); @@ -285,7 +258,7 @@ public function testNativeColumnModifyingOnSqlServer() public function testChangingColumnWithCollationWorks() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('age'); }); @@ -297,13 +270,13 @@ public function testChangingColumnWithCollationWorks() $table->integer('age')->collation('NOCASE')->change(); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users', 'DROP TABLE users', - 'CREATE TABLE users (age INTEGER NOT NULL)', + 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "RTRIM")', 'INSERT INTO users (age) SELECT age FROM __temp__users', 'DROP TABLE __temp__users', ], @@ -311,13 +284,13 @@ public function testChangingColumnWithCollationWorks() $this->assertContains($queries, $expected); - $queries = $blueprint2->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint2->toSql(DB::connection(), new SQLiteGrammar); $expected = [ [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users', 'DROP TABLE users', - 'CREATE TABLE users (age INTEGER NOT NULL)', + 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "NOCASE")', 'INSERT INTO users (age) SELECT age FROM __temp__users', 'DROP TABLE __temp__users', ], @@ -328,7 +301,7 @@ public function testChangingColumnWithCollationWorks() public function testChangingCharColumnsWork() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name'); }); @@ -336,7 +309,7 @@ public function testChangingCharColumnsWork() $table->char('name', 50)->change(); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ [ @@ -360,7 +333,7 @@ public function testChangingCharColumnsWork() public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumnsWork() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->increments('id'); }); @@ -368,7 +341,7 @@ public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumns $table->binary('id')->change(); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ [ @@ -385,7 +358,7 @@ public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumns public function testChangingDoubleColumnsWork() { - $this->db->connection()->getSchemaBuilder()->create('products', function ($table) { + DB::connection()->getSchemaBuilder()->create('products', function ($table) { $table->integer('price'); }); @@ -393,7 +366,7 @@ public function testChangingDoubleColumnsWork() $table->double('price')->change(); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ 'CREATE TEMPORARY TABLE __temp__products AS SELECT price FROM products', @@ -408,12 +381,12 @@ public function testChangingDoubleColumnsWork() public function testRenameIndexWorks() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name'); $table->string('age'); }); - $this->db->connection()->getSchemaBuilder()->table('users', function ($table) { + DB::connection()->getSchemaBuilder()->table('users', function ($table) { $table->index(['name'], 'index1'); }); @@ -421,7 +394,7 @@ public function testRenameIndexWorks() $table->renameIndex('index1', 'index2'); }); - $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ 'DROP INDEX index1', @@ -430,7 +403,7 @@ public function testRenameIndexWorks() $this->assertEquals($expected, $queries); - $queries = $blueprint->toSql($this->db->connection(), new SqlServerGrammar); + $queries = $blueprint->toSql(DB::connection(), new SqlServerGrammar); $expected = [ 'sp_rename N\'"users"."index1"\', "index2", N\'INDEX\'', @@ -438,7 +411,7 @@ public function testRenameIndexWorks() $this->assertEquals($expected, $queries); - $queries = $blueprint->toSql($this->db->connection(), new MySqlGrammar); + $queries = $blueprint->toSql(DB::connection(), new MySqlGrammar); $expected = [ 'alter table `users` rename index `index1` to `index2`', @@ -446,7 +419,7 @@ public function testRenameIndexWorks() $this->assertEquals($expected, $queries); - $queries = $blueprint->toSql($this->db->connection(), new PostgresGrammar); + $queries = $blueprint->toSql(DB::connection(), new PostgresGrammar); $expected = [ 'alter index "index1" rename to "index2"', @@ -457,7 +430,7 @@ public function testRenameIndexWorks() public function testAddUniqueIndexWithoutNameWorks() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name')->nullable(); }); @@ -465,10 +438,15 @@ public function testAddUniqueIndexWithoutNameWorks() $table->string('name')->nullable()->unique()->change(); }); - $queries = $blueprintMySql->toSql($this->db->connection(), new MySqlGrammar); + $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); $expected = [ [ + 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', + 'DROP TABLE users', + 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', + 'INSERT INTO users (name) SELECT name FROM __temp__users', + 'DROP TABLE __temp__users', 'alter table `users` add unique `users_name_unique`(`name`)', ], ]; @@ -479,10 +457,16 @@ public function testAddUniqueIndexWithoutNameWorks() $table->string('name')->nullable()->unique()->change(); }); - $queries = $blueprintPostgres->toSql($this->db->connection(), new PostgresGrammar); + $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); $expected = [ [ + + 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', + 'DROP TABLE users', + 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', + 'INSERT INTO users (name) SELECT name FROM __temp__users', + 'DROP TABLE __temp__users', 'alter table "users" add constraint "users_name_unique" unique ("name")', ], ]; @@ -493,10 +477,15 @@ public function testAddUniqueIndexWithoutNameWorks() $table->string('name')->nullable()->unique()->change(); }); - $queries = $blueprintSQLite->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); $expected = [ [ + 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', + 'DROP TABLE users', + 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', + 'INSERT INTO users (name) SELECT name FROM __temp__users', + 'DROP TABLE __temp__users', 'create unique index "users_name_unique" on "users" ("name")', ], ]; @@ -507,10 +496,15 @@ public function testAddUniqueIndexWithoutNameWorks() $table->string('name')->nullable()->unique()->change(); }); - $queries = $blueprintSqlServer->toSql($this->db->connection(), new SqlServerGrammar); + $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); $expected = [ [ + 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', + 'DROP TABLE users', + 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', + 'INSERT INTO users (name) SELECT name FROM __temp__users', + 'DROP TABLE __temp__users', 'create unique index "users_name_unique" on "users" ("name")', ], ]; @@ -520,7 +514,7 @@ public function testAddUniqueIndexWithoutNameWorks() public function testAddUniqueIndexWithNameWorks() { - $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name')->nullable(); }); @@ -528,10 +522,15 @@ public function testAddUniqueIndexWithNameWorks() $table->string('name')->nullable()->unique('index1')->change(); }); - $queries = $blueprintMySql->toSql($this->db->connection(), new MySqlGrammar); + $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); $expected = [ [ + 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', + 'DROP TABLE users', + 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', + 'INSERT INTO users (name) SELECT name FROM __temp__users', + 'DROP TABLE __temp__users', 'alter table `users` add unique `index1`(`name`)', ], ]; @@ -542,7 +541,7 @@ public function testAddUniqueIndexWithNameWorks() $table->unsignedInteger('name')->nullable()->unique('index1')->change(); }); - $queries = $blueprintPostgres->toSql($this->db->connection(), new PostgresGrammar); + $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', @@ -559,7 +558,7 @@ public function testAddUniqueIndexWithNameWorks() $table->unsignedInteger('name')->nullable()->unique('index1')->change(); }); - $queries = $blueprintSQLite->toSql($this->db->connection(), new SQLiteGrammar); + $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', @@ -576,7 +575,7 @@ public function testAddUniqueIndexWithNameWorks() $table->unsignedInteger('name')->nullable()->unique('index1')->change(); }); - $queries = $blueprintSqlServer->toSql($this->db->connection(), new SqlServerGrammar); + $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', @@ -592,7 +591,7 @@ public function testAddUniqueIndexWithNameWorks() public function testDropIndexOnColumnChangeWorks() { - $connection = $this->db->connection(); + $connection = DB::connection(); $connection->getSchemaBuilder()->create('users', function ($table) { $table->string('name')->nullable(); @@ -640,7 +639,7 @@ public function testItEnsuresDroppingMultipleColumnsIsAvailable() $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table) { $table->dropColumn('name'); $table->dropColumn('email'); }); @@ -651,7 +650,7 @@ public function testItEnsuresRenamingMultipleColumnsIsAvailable() $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table) { $table->renameColumn('name', 'first_name'); $table->renameColumn('name2', 'last_name'); }); @@ -662,7 +661,7 @@ public function testItEnsuresRenamingAndDroppingMultipleColumnsIsAvailable() $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table) { $table->dropColumn('name'); $table->renameColumn('name2', 'last_name'); }); @@ -670,15 +669,16 @@ public function testItEnsuresRenamingAndDroppingMultipleColumnsIsAvailable() public function testItDoesNotSetPrecisionHigherThanSupportedWhenRenamingTimestamps() { - $this->db->connection()->getSchemaBuilder()->create('users', function (Blueprint $table) { + Schema::create('users', function (Blueprint $table) { $table->timestamp('created_at'); }); try { // this would only fail in mysql, postgres and sql server - $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table) { $table->renameColumn('created_at', 'new_created_at'); }); + $this->addToAssertionCount(1); // it did not throw } catch (\Exception $e) { // Expecting something similar to: @@ -693,7 +693,7 @@ public function testItEnsuresDroppingForeignKeyIsAvailable() $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage("SQLite doesn't support dropping foreign keys (you would need to re-create the table)."); - $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table) { $table->dropForeign('something'); }); } diff --git a/tests/Integration/Database/DatabaseSchemaBuilderTest.php b/tests/Integration/Database/DatabaseSchemaBuilderTest.php new file mode 100644 index 000000000000..b016a0124eee --- /dev/null +++ b/tests/Integration/Database/DatabaseSchemaBuilderTest.php @@ -0,0 +1,76 @@ +set([ + 'database.default' => 'testing', + 'database.connections.sqlite-with-prefix' => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => 'example_', + 'prefix_indexes' => false, + ], + 'database.connections.sqlite-with-indexed-prefix' => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => 'example_', + 'prefix_indexes' => true, + ], + ]); + } + + public function testDropAllTablesWorksWithForeignKeys() + { + Schema::create('table1', function (Blueprint $table) { + $table->integer('id'); + $table->string('name'); + }); + + Schema::create('table2', function (Blueprint $table) { + $table->integer('id'); + $table->string('user_id'); + $table->foreign('user_id')->references('id')->on('table1'); + }); + + $this->assertTrue(Schema::hasTable('table1')); + $this->assertTrue(Schema::hasTable('table2')); + + Schema::dropAllTables(); + + $this->assertFalse(Schema::hasTable('table1')); + $this->assertFalse(Schema::hasTable('table2')); + } + + public function testHasColumnAndIndexWithPrefixIndexDisabled() + { + $connection = DB::connection('sqlite-with-prefix'); + + Schema::connection('sqlite-with-prefix')->create('table1', function (Blueprint $table) { + $table->integer('id'); + $table->string('name')->index(); + }); + + $this->assertArrayHasKey('table1_name_index', $connection->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + } + + public function testHasColumnAndIndexWithPrefixIndexEnabled() + { + $connection = DB::connection('sqlite-with-indexed-prefix'); + + Schema::connection('sqlite-with-indexed-prefix')->create('table1', function (Blueprint $table) { + $table->integer('id'); + $table->string('name')->index(); + }); + + $this->assertArrayHasKey('example_table1_name_index', $connection->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + } +} diff --git a/tests/Integration/Database/Fixtures/TinyInteger.php b/tests/Integration/Database/Fixtures/TinyInteger.php index 8eefbabdd264..6b94dff3e869 100644 --- a/tests/Integration/Database/Fixtures/TinyInteger.php +++ b/tests/Integration/Database/Fixtures/TinyInteger.php @@ -21,7 +21,7 @@ class TinyInteger extends Type * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return string */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { return 'TINYINT'; } From a3b26b5088a372154490fdd6b9fe75306ca3c0c1 Mon Sep 17 00:00:00 2001 From: Julius Kiekbusch Date: Tue, 24 Oct 2023 23:09:28 +0200 Subject: [PATCH 55/99] Update laravel/prompts constraint (#48812) --- composer.json | 2 +- src/Illuminate/Console/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 932521b7f0a9..a231d3d21617 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "egulias/email-validator": "^3.2.1|^4.0", "fruitcake/php-cors": "^1.3", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "dev-symfony7", + "laravel/prompts": "^0.1.12", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index c09ee5d922cd..68e50920d317 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -21,7 +21,7 @@ "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", "illuminate/view": "^11.0", - "laravel/prompts": "dev-symfony7", + "laravel/prompts": "^0.1.12", "nunomaduro/termwind": "^2.0", "symfony/console": "^7.0", "symfony/process": "^7.0" From da71348153b4d768ba9080ddd60aa706ca5de207 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 26 Oct 2023 11:30:12 +0200 Subject: [PATCH 56/99] wip --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 544d1573958d..439d904e1aa3 100644 --- a/composer.json +++ b/composer.json @@ -112,7 +112,7 @@ "predis/predis": "^2.0.2", "symfony/cache": "^7.0", "symfony/http-client": "^7.0", - "symfony/psr-http-message-bridge": "^2.0" + "symfony/psr-http-message-bridge": "^v7.0.0-BETA1" }, "provide": { "psr/container-implementation": "1.1|2.0", From eb8b176c5516778c1e0feb03ea61e666a2457c34 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 26 Oct 2023 22:09:14 +0800 Subject: [PATCH 57/99] [11.x] PHPUnit 11 Forward Compatibility (#48796) * 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 * Apply fixes from StyleCI * 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 * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * Update composer.json --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot --- tests/Auth/AuthAccessGateTest.php | 8 +- tests/Broadcasting/BroadcasterTest.php | 5 +- .../UsePusherChannelsNamesTest.php | 9 +- tests/Bus/BusBatchTest.php | 5 +- tests/Cache/CacheMemcachedConnectorTest.php | 9 +- tests/Cache/CacheMemcachedStoreTest.php | 5 +- tests/Cache/CacheRepositoryTest.php | 4 +- tests/Cache/RedisCacheIntegrationTest.php | 10 +- tests/Console/Scheduling/EventTest.php | 17 +- tests/Database/DatabaseConnectorTest.php | 13 +- .../Database/DatabaseMySqlSchemaStateTest.php | 5 +- tests/Encryption/EncrypterTest.php | 5 +- tests/Filesystem/FilesystemAdapterTest.php | 5 +- tests/Filesystem/FilesystemManagerTest.php | 5 +- tests/Filesystem/FilesystemTest.php | 26 +- tests/Hashing/HasherTest.php | 13 +- tests/Http/HttpJsonResponseTest.php | 13 +- tests/Http/HttpRequestTest.php | 13 +- tests/Http/HttpTestingFileFactoryTest.php | 7 +- .../ApiAuthenticationWithEloquentTest.php | 5 +- .../Cache/MemcachedCacheLockTestCase.php | 5 +- .../Cache/MemcachedTaggedCacheTestCase.php | 5 +- .../Cache/PhpRedisCacheLockTest.php | 17 +- tests/Integration/Cache/Psr6RedisTest.php | 5 +- .../Integration/Console/CommandEventsTest.php | 5 +- .../Console/CommandSchedulingTest.php | 5 +- .../Console/GeneratorCommandTest.php | 5 +- .../Scheduling/SubMinuteSchedulingTest.php | 3 +- .../EloquentModelCustomCastingTest.php | 21 +- .../Database/EloquentWhereHasTest.php | 7 +- ...baseEmulatePreparesMySqlConnectionTest.php | 8 +- .../MySql/DatabaseMySqlConnectionTest.php | 25 +- ...SqlSchemaBuilderAlterTableWithEnumTest.php | 8 +- .../MySql/DatabaseMySqlSchemaBuilderTest.php | 8 +- .../Integration/Database/MySql/EscapeTest.php | 8 +- .../Database/MySql/FulltextTest.php | 8 +- .../DatabasePostgresConnectionTest.php | 21 +- .../Database/Postgres/EscapeTest.php | 8 +- .../Database/Postgres/FulltextTest.php | 8 +- .../Postgres/PostgresSchemaBuilderTest.php | 8 +- .../DatabaseSqlServerConnectionTest.php | 13 +- .../Sqlite/DatabaseSqliteConnectionTest.php | 5 +- .../Integration/Filesystem/FilesystemTest.php | 5 +- tests/Integration/Filesystem/StorageTest.php | 5 +- .../Foundation/ExceptionHandlerTest.php | 5 +- tests/Integration/Queue/CustomPayloadTest.php | 5 +- .../Integration/Routing/RouteRedirectTest.php | 5 +- tests/Mail/MailManagerTest.php | 5 +- tests/Queue/QueueDatabaseQueueUnitTest.php | 5 +- tests/Queue/RedisQueueIntegrationTest.php | 56 +- tests/Routing/RouteUriTest.php | 5 +- tests/Routing/RoutingUrlGeneratorTest.php | 9 +- tests/Support/ConfigurationUrlParserTest.php | 5 +- tests/Support/SupportCollectionTest.php | 1221 +++++------------ tests/Support/SupportHelpersTest.php | 5 +- tests/Support/SupportStrTest.php | 25 +- tests/Testing/Concerns/TestDatabasesTest.php | 5 +- tests/Testing/ParallelTestingTest.php | 5 +- .../TranslationMessageSelectorTest.php | 5 +- tests/Validation/ValidationValidatorTest.php | 47 +- tests/View/Blade/BladeEchoHandlerTest.php | 9 +- .../View/Blade/BladeForeachStatementsTest.php | 5 +- .../View/Blade/BladeForelseStatementsTest.php | 5 +- tests/View/ViewBladeCompilerTest.php | 4 +- tests/View/ViewFileViewFinderTest.php | 5 +- 65 files changed, 540 insertions(+), 1299 deletions(-) diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index c6ed6c2cbcc4..3f143f1651e5 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -8,6 +8,7 @@ use Illuminate\Auth\Access\Response; use Illuminate\Container\Container; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use stdClass; @@ -555,9 +556,7 @@ public function testForUserMethodAttachesANewUserToANewGateInstanceWithGuessCall $this->assertSame(3, $counter); } - /** - * @dataProvider notCallableDataProvider - */ + #[DataProvider('notCallableDataProvider')] public function testDefineSecondParameterShouldBeStringOrCallable($callback) { $this->expectException(InvalidArgumentException::class); @@ -1036,12 +1035,11 @@ public function testEveryAbilityCheckFailsIfNonePass() } /** - * @dataProvider hasAbilitiesTestDataProvider - * * @param array $abilitiesToSet * @param array|string $abilitiesToCheck * @param bool $expectedHasValue */ + #[DataProvider('hasAbilitiesTestDataProvider')] public function testHasAbilities($abilitiesToSet, $abilitiesToCheck, $expectedHasValue) { $gate = $this->getBasicGate(); diff --git a/tests/Broadcasting/BroadcasterTest.php b/tests/Broadcasting/BroadcasterTest.php index d19ecfdc186a..eca82ca9754d 100644 --- a/tests/Broadcasting/BroadcasterTest.php +++ b/tests/Broadcasting/BroadcasterTest.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -314,9 +315,7 @@ public function testUserAuthenticationWithoutResolve() $this->assertNull($this->broadcaster->resolveAuthenticatedUser(new Request(['socket_id' => '1234.1234']))); } - /** - * @dataProvider channelNameMatchPatternProvider - */ + #[DataProvider('channelNameMatchPatternProvider')] public function testChannelNameMatchPattern($channel, $pattern, $shouldMatch) { $this->assertEquals($shouldMatch, $this->broadcaster->channelNameMatchesPattern($channel, $pattern)); diff --git a/tests/Broadcasting/UsePusherChannelsNamesTest.php b/tests/Broadcasting/UsePusherChannelsNamesTest.php index c2b87d57026b..122e83e8c3f4 100644 --- a/tests/Broadcasting/UsePusherChannelsNamesTest.php +++ b/tests/Broadcasting/UsePusherChannelsNamesTest.php @@ -4,13 +4,12 @@ use Illuminate\Broadcasting\Broadcasters\Broadcaster; use Illuminate\Broadcasting\Broadcasters\UsePusherChannelConventions; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class UsePusherChannelsNamesTest extends TestCase { - /** - * @dataProvider channelsProvider - */ + #[DataProvider('channelsProvider')] public function testChannelNameNormalization($requestChannelName, $normalizedName) { $broadcaster = new FakeBroadcasterUsingPusherChannelsNames; @@ -31,9 +30,7 @@ public function testChannelNameNormalizationSpecialCase() ); } - /** - * @dataProvider channelsProvider - */ + #[DataProvider('channelsProvider')] public function testIsGuardedChannel($requestChannelName, $_, $guarded) { $broadcaster = new FakeBroadcasterUsingPusherChannelsNames; diff --git a/tests/Bus/BusBatchTest.php b/tests/Bus/BusBatchTest.php index 49c394611185..9f870f9adc9d 100644 --- a/tests/Bus/BusBatchTest.php +++ b/tests/Bus/BusBatchTest.php @@ -19,6 +19,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\CallQueuedClosure; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -414,9 +415,7 @@ public function test_options_serialization_on_postgres() $builder->shouldHaveReceived('first'); } - /** - * @dataProvider serializedOptions - */ + #[DataProvider('serializedOptions')] public function test_options_unserialize_on_postgres($serialize, $options) { $factory = m::mock(BatchFactory::class); diff --git a/tests/Cache/CacheMemcachedConnectorTest.php b/tests/Cache/CacheMemcachedConnectorTest.php index 402b210b0dbe..f853afa2ee78 100755 --- a/tests/Cache/CacheMemcachedConnectorTest.php +++ b/tests/Cache/CacheMemcachedConnectorTest.php @@ -5,6 +5,7 @@ use Illuminate\Cache\MemcachedConnector; use Memcached; use Mockery as m; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use stdClass; @@ -48,9 +49,7 @@ public function testServersAreAddedCorrectlyWithPersistentConnection() $this->assertSame($result, $memcached); } - /** - * @requires extension memcached - */ + #[RequiresPhpExtension('memcached')] public function testServersAreAddedCorrectlyWithValidOptions() { $validOptions = [ @@ -71,9 +70,7 @@ public function testServersAreAddedCorrectlyWithValidOptions() $this->assertSame($result, $memcached); } - /** - * @requires extension memcached - */ + #[RequiresPhpExtension('memcached')] public function testServersAreAddedCorrectlyWithSaslCredentials() { $saslCredentials = ['foo', 'bar']; diff --git a/tests/Cache/CacheMemcachedStoreTest.php b/tests/Cache/CacheMemcachedStoreTest.php index 0400be141844..cb5dadc8f9bb 100755 --- a/tests/Cache/CacheMemcachedStoreTest.php +++ b/tests/Cache/CacheMemcachedStoreTest.php @@ -6,11 +6,10 @@ use Illuminate\Support\Carbon; use Memcached; use Mockery as m; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; -/** - * @requires extension memcached - */ +#[RequiresPhpExtension('memcached')] class CacheMemcachedStoreTest extends TestCase { protected function tearDown(): void diff --git a/tests/Cache/CacheRepositoryTest.php b/tests/Cache/CacheRepositoryTest.php index 56f70c58edd4..5c4b77ce070f 100755 --- a/tests/Cache/CacheRepositoryTest.php +++ b/tests/Cache/CacheRepositoryTest.php @@ -19,6 +19,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Carbon; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class CacheRepositoryTest extends TestCase @@ -280,10 +281,9 @@ public static function dataProviderTestGetSeconds() } /** - * @dataProvider dataProviderTestGetSeconds - * * @param mixed $duration */ + #[DataProvider('dataProviderTestGetSeconds')] public function testGetSeconds($duration) { $repo = $this->getRepository(); diff --git a/tests/Cache/RedisCacheIntegrationTest.php b/tests/Cache/RedisCacheIntegrationTest.php index 410a02c6f82a..ff99dd0bfd81 100644 --- a/tests/Cache/RedisCacheIntegrationTest.php +++ b/tests/Cache/RedisCacheIntegrationTest.php @@ -5,6 +5,7 @@ use Illuminate\Cache\RedisStore; use Illuminate\Cache\Repository; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class RedisCacheIntegrationTest extends TestCase @@ -24,10 +25,9 @@ protected function tearDown(): void } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testRedisCacheAddTwice($driver) { $store = new RedisStore($this->redis[$driver]); @@ -40,10 +40,9 @@ public function testRedisCacheAddTwice($driver) /** * Breaking change. * - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testRedisCacheAddFalse($driver) { $store = new RedisStore($this->redis[$driver]); @@ -56,10 +55,9 @@ public function testRedisCacheAddFalse($driver) /** * Breaking change. * - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testRedisCacheAddNull($driver) { $store = new RedisStore($this->redis[$driver]); diff --git a/tests/Console/Scheduling/EventTest.php b/tests/Console/Scheduling/EventTest.php index 8d7883b9cb35..4cc6c02d0f5a 100644 --- a/tests/Console/Scheduling/EventTest.php +++ b/tests/Console/Scheduling/EventTest.php @@ -6,6 +6,7 @@ use Illuminate\Console\Scheduling\EventMutex; use Illuminate\Support\Str; use Mockery as m; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use PHPUnit\Framework\TestCase; class EventTest extends TestCase @@ -17,9 +18,7 @@ protected function tearDown(): void parent::tearDown(); } - /** - * @requires OS Linux|Darwin - */ + #[RequiresOperatingSystem('Linux|Darwin')] public function testBuildCommandUsingUnix() { $event = new Event(m::mock(EventMutex::class), 'php -i'); @@ -27,9 +26,7 @@ public function testBuildCommandUsingUnix() $this->assertSame("php -i > '/dev/null' 2>&1", $event->buildCommand()); } - /** - * @requires OS Windows - */ + #[RequiresOperatingSystem('Windows')] public function testBuildCommandUsingWindows() { $event = new Event(m::mock(EventMutex::class), 'php -i'); @@ -37,9 +34,7 @@ public function testBuildCommandUsingWindows() $this->assertSame('php -i > "NUL" 2>&1', $event->buildCommand()); } - /** - * @requires OS Linux|Darwin - */ + #[RequiresOperatingSystem('Linux|Darwin')] public function testBuildCommandInBackgroundUsingUnix() { $event = new Event(m::mock(EventMutex::class), 'php -i'); @@ -50,9 +45,7 @@ public function testBuildCommandInBackgroundUsingUnix() $this->assertSame("(php -i > '/dev/null' 2>&1 ; '".PHP_BINARY."' 'artisan' schedule:finish {$scheduleId} \"$?\") > '/dev/null' 2>&1 &", $event->buildCommand()); } - /** - * @requires OS Windows - */ + #[RequiresOperatingSystem('Windows')] public function testBuildCommandInBackgroundUsingWindows() { $event = new Event(m::mock(EventMutex::class), 'php -i'); diff --git a/tests/Database/DatabaseConnectorTest.php b/tests/Database/DatabaseConnectorTest.php index 79ee6d0606c4..7ada98829e8a 100755 --- a/tests/Database/DatabaseConnectorTest.php +++ b/tests/Database/DatabaseConnectorTest.php @@ -10,6 +10,8 @@ use Mockery as m; use PDO; use PDOStatement; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use stdClass; @@ -27,9 +29,7 @@ public function testOptionResolution() $this->assertEquals([0 => 'baz', 1 => 'bar', 2 => 'boom'], $connector->getOptions(['options' => [0 => 'baz', 2 => 'boom']])); } - /** - * @dataProvider mySqlConnectProvider - */ + #[DataProvider('mySqlConnectProvider')] public function testMySqlConnectCallsCreateConnectionWithProperArguments($dsn, $config) { $connector = $this->getMockBuilder(MySqlConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); @@ -90,11 +90,10 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments() } /** - * @dataProvider provideSearchPaths - * * @param string $searchPath * @param string $expectedSql */ + #[DataProvider('provideSearchPaths')] public function testPostgresSearchPathIsSet($searchPath, $expectedSql) { $dsn = 'pgsql:host=foo;dbname=\'bar\''; @@ -317,9 +316,7 @@ public function testSqlServerConnectCallsCreateConnectionWithOptionalArguments() $this->assertSame($result, $connection); } - /** - * @requires extension odbc - */ + #[RequiresPhpExtension('odbc')] public function testSqlServerConnectCallsCreateConnectionWithPreferredODBC() { $config = ['odbc' => true, 'odbc_datasource_name' => 'server=localhost;database=test;']; diff --git a/tests/Database/DatabaseMySqlSchemaStateTest.php b/tests/Database/DatabaseMySqlSchemaStateTest.php index 63a7107e7caa..0468a0d501d5 100644 --- a/tests/Database/DatabaseMySqlSchemaStateTest.php +++ b/tests/Database/DatabaseMySqlSchemaStateTest.php @@ -5,14 +5,13 @@ use Generator; use Illuminate\Database\MySqlConnection; use Illuminate\Database\Schema\MySqlSchemaState; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ReflectionMethod; class DatabaseMySqlSchemaStateTest extends TestCase { - /** - * @dataProvider provider - */ + #[DataProvider('provider')] public function testConnectionString(string $expectedConnectionString, array $expectedVariables, array $dbConfig): void { $connection = $this->createMock(MySqlConnection::class); diff --git a/tests/Encryption/EncrypterTest.php b/tests/Encryption/EncrypterTest.php index c6a0f1d28143..64f2e9d39502 100755 --- a/tests/Encryption/EncrypterTest.php +++ b/tests/Encryption/EncrypterTest.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Encryption\Encrypter; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -221,9 +222,7 @@ public static function provideTamperedData() ]; } - /** - * @dataProvider provideTamperedData - */ + #[DataProvider('provideTamperedData')] public function testTamperedPayloadWillGetRejected($payload) { $this->expectException(DecryptException::class); diff --git a/tests/Filesystem/FilesystemAdapterTest.php b/tests/Filesystem/FilesystemAdapterTest.php index d6cc99c64dcd..3d509e0c7745 100644 --- a/tests/Filesystem/FilesystemAdapterTest.php +++ b/tests/Filesystem/FilesystemAdapterTest.php @@ -17,6 +17,7 @@ use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToWriteFile; use Mockery as m; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -426,9 +427,7 @@ public function testPutFileWithoutPath() $this->assertSame('normal file content', $filesystemAdapter->read($storagePath)); } - /** - * @requires extension ftp - */ + #[RequiresPhpExtension('ftp')] public function testCreateFtpDriver() { $filesystem = new FilesystemManager(new Application); diff --git a/tests/Filesystem/FilesystemManagerTest.php b/tests/Filesystem/FilesystemManagerTest.php index ef1eb632f92a..8850c5557a06 100644 --- a/tests/Filesystem/FilesystemManagerTest.php +++ b/tests/Filesystem/FilesystemManagerTest.php @@ -6,6 +6,7 @@ use Illuminate\Filesystem\FilesystemManager; use Illuminate\Foundation\Application; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use PHPUnit\Framework\TestCase; class FilesystemManagerTest extends TestCase @@ -98,9 +99,7 @@ public function testCanBuildScopedDisks() } } - /** - * @requires OS Linux|Darwin - */ + #[RequiresOperatingSystem('Linux|Darwin')] public function testCanBuildScopedDisksWithVisibility() { try { diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php index f9cd9cd239a6..54fc3b0b2bc2 100755 --- a/tests/Filesystem/FilesystemTest.php +++ b/tests/Filesystem/FilesystemTest.php @@ -7,6 +7,10 @@ use Illuminate\Support\LazyCollection; use Illuminate\Testing\Assert; use Mockery as m; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use SplFileInfo; @@ -14,18 +18,14 @@ class FilesystemTest extends TestCase { private static $tempDir; - /** - * @beforeClass - */ + #[BeforeClass] public static function setUpTempDir() { self::$tempDir = sys_get_temp_dir().'/tmp'; mkdir(self::$tempDir); } - /** - * @afterClass - */ + #[AfterClass] public static function tearDownTempDir() { $files = new Filesystem; @@ -97,9 +97,7 @@ public function testReplaceInFileCorrectlyReplaces() $this->assertStringEqualsFile($tempFile, 'Hello Taylor'); } - /** - * @requires OS Linux|Darwin - */ + #[RequiresOperatingSystem('Linux|Darwin')] public function testReplaceWhenUnixSymlinkExists() { $tempFile = self::$tempDir.'/file.txt'; @@ -428,9 +426,7 @@ public function testSizeOutputsSize() $this->assertEquals($size, $files->size(self::$tempDir.'/foo.txt')); } - /** - * @requires extension fileinfo - */ + #[RequiresPhpExtension('fileinfo')] public function testMimeTypeOutputsMimeType() { file_put_contents(self::$tempDir.'/foo.txt', 'foo'); @@ -527,10 +523,8 @@ public function testMakeDirectory() $this->assertFileExists(self::$tempDir.'/created'); } - /** - * @requires extension pcntl - * @requires OS Linux|Darwin - */ + #[RequiresOperatingSystem('Linux|Darwin')] + #[RequiresPhpExtension('pcntl')] public function testSharedGet() { $content = str_repeat('123456', 1000000); diff --git a/tests/Hashing/HasherTest.php b/tests/Hashing/HasherTest.php index 3559cc0303d3..70c6c3d175b0 100755 --- a/tests/Hashing/HasherTest.php +++ b/tests/Hashing/HasherTest.php @@ -8,6 +8,7 @@ use Illuminate\Hashing\ArgonHasher; use Illuminate\Hashing\BcryptHasher; use Illuminate\Hashing\HashManager; +use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -82,9 +83,7 @@ public function testBasicArgon2idHashing() $this->assertTrue($this->hashManager->isHashed($value)); } - /** - * @depends testBasicBcryptHashing - */ + #[Depends('testBasicBcryptHashing')] public function testBasicBcryptVerification() { $this->expectException(RuntimeException::class); @@ -94,9 +93,7 @@ public function testBasicBcryptVerification() (new BcryptHasher(['verify' => true]))->check('password', $argonHashed); } - /** - * @depends testBasicArgon2iHashing - */ + #[Depends('testBasicArgon2iHashing')] public function testBasicArgon2iVerification() { $this->expectException(RuntimeException::class); @@ -106,9 +103,7 @@ public function testBasicArgon2iVerification() (new ArgonHasher(['verify' => true]))->check('password', $bcryptHashed); } - /** - * @depends testBasicArgon2idHashing - */ + #[Depends('testBasicArgon2idHashing')] public function testBasicArgon2idVerification() { $this->expectException(RuntimeException::class); diff --git a/tests/Http/HttpJsonResponseTest.php b/tests/Http/HttpJsonResponseTest.php index 48b5aef73267..a2b296da59b7 100644 --- a/tests/Http/HttpJsonResponseTest.php +++ b/tests/Http/HttpJsonResponseTest.php @@ -7,14 +7,13 @@ use Illuminate\Http\JsonResponse; use InvalidArgumentException; use JsonSerializable; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use stdClass; class HttpJsonResponseTest extends TestCase { - /** - * @dataProvider setAndRetrieveDataProvider - */ + #[DataProvider('setAndRetrieveDataProvider')] public function testSetAndRetrieveData($data) { $response = new JsonResponse($data); @@ -67,9 +66,7 @@ public function testSetAndRetrieveStatusCode() $this->assertSame(404, $response->getStatusCode()); } - /** - * @dataProvider jsonErrorDataProvider - */ + #[DataProvider('jsonErrorDataProvider')] public function testInvalidArgumentExceptionOnJsonError($data) { $this->expectException(InvalidArgumentException::class); @@ -77,9 +74,7 @@ public function testInvalidArgumentExceptionOnJsonError($data) new JsonResponse(['data' => $data]); } - /** - * @dataProvider jsonErrorDataProvider - */ + #[DataProvider('jsonErrorDataProvider')] public function testGracefullyHandledSomeJsonErrorsWithPartialOutputOnError($data) { new JsonResponse(['data' => $data], 200, [], JSON_PARTIAL_OUTPUT_ON_ERROR); diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 124f0bdbd33a..10db3c4701a5 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -12,6 +12,7 @@ use Illuminate\Tests\Database\Fixtures\Models\Money\Price; use InvalidArgumentException; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use RuntimeException; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; @@ -80,9 +81,7 @@ public function testDecodedPathMethod() $this->assertSame('foo bar', $request->decodedPath()); } - /** - * @dataProvider segmentProvider - */ + #[DataProvider('segmentProvider')] public function testSegmentMethod($path, $segment, $expected) { $request = Request::create($path); @@ -99,9 +98,7 @@ public static function segmentProvider() ]; } - /** - * @dataProvider segmentsProvider - */ + #[DataProvider('segmentsProvider')] public function testSegmentsMethod($path, $expected) { $request = Request::create($path); @@ -1068,9 +1065,7 @@ public static function getPrefersCases() ]; } - /** - * @dataProvider getPrefersCases - */ + #[DataProvider('getPrefersCases')] public function testPrefersMethod($accept, $prefers, $expected) { $this->assertSame( diff --git a/tests/Http/HttpTestingFileFactoryTest.php b/tests/Http/HttpTestingFileFactoryTest.php index c2824b6fe327..5ee649cce689 100644 --- a/tests/Http/HttpTestingFileFactoryTest.php +++ b/tests/Http/HttpTestingFileFactoryTest.php @@ -3,13 +3,14 @@ namespace Illuminate\Tests\Http; use Illuminate\Http\Testing\FileFactory; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; /** - * @requires extension gd - * * @link https://www.php.net/manual/en/function.gd-info.php */ +#[RequiresPhpExtension('gd')] class HttpTestingFileFactoryTest extends TestCase { public function testImagePng() @@ -118,7 +119,7 @@ public function testCreateWithoutMimeType() ); } - /** @dataProvider generateImageDataProvider */ + #[DataProvider('generateImageDataProvider')] public function testCallingCreateWithoutGDLoadedThrowsAnException(string $fileExtension, string $driver) { if ($this->isGDSupported($driver)) { diff --git a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php index c5fc77aec88d..df78219bb62a 100644 --- a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php +++ b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php @@ -7,10 +7,9 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - */ +#[RequiresPhpExtension('pdo_mysql')] class ApiAuthenticationWithEloquentTest extends TestCase { protected function getEnvironmentSetUp($app) diff --git a/tests/Integration/Cache/MemcachedCacheLockTestCase.php b/tests/Integration/Cache/MemcachedCacheLockTestCase.php index 7f3e93f8c7c0..706bb8b3ae37 100644 --- a/tests/Integration/Cache/MemcachedCacheLockTestCase.php +++ b/tests/Integration/Cache/MemcachedCacheLockTestCase.php @@ -4,10 +4,9 @@ use Illuminate\Contracts\Cache\LockTimeoutException; use Illuminate\Support\Facades\Cache; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension memcached - */ +#[RequiresPhpExtension('memcached')] class MemcachedCacheLockTestCase extends MemcachedIntegrationTestCase { public function testMemcachedLocksCanBeAcquiredAndReleased() diff --git a/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php b/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php index 4aab9422a8fd..ee7787aa407e 100644 --- a/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php +++ b/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php @@ -3,10 +3,9 @@ namespace Illuminate\Tests\Integration\Cache; use Illuminate\Support\Facades\Cache; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension memcached - */ +#[RequiresPhpExtension('memcached')] class MemcachedTaggedCacheTestCase extends MemcachedIntegrationTestCase { public function testMemcachedCanStoreAndRetrieveTaggedCacheItems() diff --git a/tests/Integration/Cache/PhpRedisCacheLockTest.php b/tests/Integration/Cache/PhpRedisCacheLockTest.php index 0ca4ea85f960..fe40f2ac3fe3 100644 --- a/tests/Integration/Cache/PhpRedisCacheLockTest.php +++ b/tests/Integration/Cache/PhpRedisCacheLockTest.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Support\Facades\Cache; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Redis; class PhpRedisCacheLockTest extends TestCase @@ -138,9 +139,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithMsgpackSerialization() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension lzf - */ + #[RequiresPhpExtension('lzf')] public function testRedisLockCanBeAcquiredAndReleasedWithLzfCompression() { if (! defined('Redis::COMPRESSION_LZF')) { @@ -167,9 +166,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLzfCompression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension zstd - */ + #[RequiresPhpExtension('zstd')] public function testRedisLockCanBeAcquiredAndReleasedWithZstdCompression() { if (! defined('Redis::COMPRESSION_ZSTD')) { @@ -215,9 +212,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithZstdCompression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension lz4 - */ + #[RequiresPhpExtension('lz4')] public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression() { if (! defined('Redis::COMPRESSION_LZ4')) { @@ -263,9 +258,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension Lzf - */ + #[RequiresPhpExtension('lzf')] public function testRedisLockCanBeAcquiredAndReleasedWithSerializationAndCompression() { if (! defined('Redis::COMPRESSION_LZF')) { diff --git a/tests/Integration/Cache/Psr6RedisTest.php b/tests/Integration/Cache/Psr6RedisTest.php index a242a2f77e29..062d5ec4dbfb 100644 --- a/tests/Integration/Cache/Psr6RedisTest.php +++ b/tests/Integration/Cache/Psr6RedisTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Tests\Integration\Cache\Fixtures\Unserializable; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class Psr6RedisTest extends TestCase { @@ -25,9 +26,7 @@ protected function tearDown(): void $this->tearDownRedis(); } - /** - * @dataProvider redisClientDataProvider - */ + #[DataProvider('redisClientDataProvider')] public function testTransactionIsNotOpenedWhenSerializationFails($redisClient): void { $this->app['config']['cache.default'] = 'redis'; diff --git a/tests/Integration/Console/CommandEventsTest.php b/tests/Integration/Console/CommandEventsTest.php index 167835eb9b7f..cc63e22dc4fc 100644 --- a/tests/Integration/Console/CommandEventsTest.php +++ b/tests/Integration/Console/CommandEventsTest.php @@ -12,6 +12,7 @@ use Illuminate\Support\Str; use Orchestra\Testbench\Foundation\Application as Testbench; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class CommandEventsTest extends TestCase { @@ -48,9 +49,7 @@ protected function setUp(): void parent::setUp(); } - /** - * @dataProvider foregroundCommandEventsProvider - */ + #[DataProvider('foregroundCommandEventsProvider')] public function testCommandEventsReceiveParsedInput($callback) { $this->app[ConsoleKernel::class]->registerCommand(new CommandEventsTestCommand); diff --git a/tests/Integration/Console/CommandSchedulingTest.php b/tests/Integration/Console/CommandSchedulingTest.php index 1e02c3b3d2fa..1a4c918aa5e0 100644 --- a/tests/Integration/Console/CommandSchedulingTest.php +++ b/tests/Integration/Console/CommandSchedulingTest.php @@ -6,6 +6,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class CommandSchedulingTest extends TestCase { @@ -62,9 +63,7 @@ protected function tearDown(): void parent::tearDown(); } - /** - * @dataProvider executionProvider - */ + #[DataProvider('executionProvider')] public function testExecutionOrder($background, $expected) { $schedule = $this->app->make(Schedule::class); diff --git a/tests/Integration/Console/GeneratorCommandTest.php b/tests/Integration/Console/GeneratorCommandTest.php index 18889bf5ed3b..cb8ce75a6818 100644 --- a/tests/Integration/Console/GeneratorCommandTest.php +++ b/tests/Integration/Console/GeneratorCommandTest.php @@ -3,12 +3,11 @@ namespace Illuminate\Tests\Integration\Console; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class GeneratorCommandTest extends TestCase { - /** - * @dataProvider reservedNamesDataProvider - */ + #[DataProvider('reservedNamesDataProvider')] public function testItCannotGenerateClassUsingReservedName($given) { $this->artisan('make:command', ['name' => $given]) diff --git a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php index bc7b98d5b1b4..f40626a9dbe5 100644 --- a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php +++ b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Sleep; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class SubMinuteSchedulingTest extends TestCase { @@ -45,7 +46,7 @@ public function test_it_doesnt_wait_for_sub_minute_events_when_none_are_schedule Sleep::assertNeverSlept(); } - /** @dataProvider frequencyProvider */ + #[DataProvider('frequencyProvider')] public function test_it_runs_sub_minute_callbacks($frequency, $expectedRuns) { $runs = 0; diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index b27149129fd1..0d2e15c54f41 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -12,11 +12,10 @@ use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Database\Schema\Blueprint; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; -/** - * @group integration - */ +#[Group('integration')] class EloquentModelCustomCastingTest extends TestCase { protected function setUp(): void @@ -67,9 +66,7 @@ protected function tearDown(): void $this->schema()->drop('members'); } - /** - * @requires extension gmp - */ + #[RequiresPhpExtension('gmp')] public function testSavingCastedAttributesToDatabase() { /** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */ @@ -106,9 +103,7 @@ public function testSavingCastedAttributesToDatabase() $this->assertInstanceOf(GMP::class, $model->amount); } - /** - * @requires extension gmp - */ + #[RequiresPhpExtension('gmp')] public function testInvalidArgumentExceptionOnInvalidValue() { /** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */ @@ -127,9 +122,7 @@ public function testInvalidArgumentExceptionOnInvalidValue() $this->assertSame('address_line_two_value', $model->address->lineTwo); } - /** - * @requires extension gmp - */ + #[RequiresPhpExtension('gmp')] public function testInvalidArgumentExceptionOnNull() { /** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */ @@ -148,9 +141,7 @@ public function testInvalidArgumentExceptionOnNull() $this->assertSame('address_line_two_value', $model->address->lineTwo); } - /** - * @requires extension gmp - */ + #[RequiresPhpExtension('gmp')] public function testModelsWithCustomCastsCanBeConvertedToArrays() { /** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */ diff --git a/tests/Integration/Database/EloquentWhereHasTest.php b/tests/Integration/Database/EloquentWhereHasTest.php index 22cfaf5d5a75..26a52c0e1651 100644 --- a/tests/Integration/Database/EloquentWhereHasTest.php +++ b/tests/Integration/Database/EloquentWhereHasTest.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Tests\Integration\Database\DatabaseTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class EloquentWhereHasTest extends DatabaseTestCase { @@ -49,9 +50,8 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed() /** * Check that the 'whereRelation' callback function works. - * - * @dataProvider dataProviderWhereRelationCallback */ + #[DataProvider('dataProviderWhereRelationCallback')] public function testWhereRelationCallback($callbackEloquent, $callbackQuery) { $userWhereRelation = User::whereRelation('posts', $callbackEloquent); @@ -69,9 +69,8 @@ public function testWhereRelationCallback($callbackEloquent, $callbackQuery) /** * Check that the 'orWhereRelation' callback function works. - * - * @dataProvider dataProviderWhereRelationCallback */ + #[DataProvider('dataProviderWhereRelationCallback')] public function testOrWhereRelationCallback($callbackEloquent, $callbackQuery) { $userOrWhereRelation = User::orWhereRelation('posts', $callbackEloquent); diff --git a/tests/Integration/Database/MySql/DatabaseEmulatePreparesMySqlConnectionTest.php b/tests/Integration/Database/MySql/DatabaseEmulatePreparesMySqlConnectionTest.php index 9848da4a642b..62592a40486f 100755 --- a/tests/Integration/Database/MySql/DatabaseEmulatePreparesMySqlConnectionTest.php +++ b/tests/Integration/Database/MySql/DatabaseEmulatePreparesMySqlConnectionTest.php @@ -3,11 +3,11 @@ namespace Illuminate\Tests\Integration\Database\MySql; use PDO; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class DatabaseEmulatePreparesMySqlConnectionTest extends DatabaseMySqlConnectionTest { protected function getEnvironmentSetUp($app) diff --git a/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php b/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php index a365157cf924..b1cb6a67f2c6 100644 --- a/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php +++ b/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php @@ -5,11 +5,12 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class DatabaseMySqlConnectionTest extends MySqlTestCase { const TABLE = 'player'; @@ -32,9 +33,7 @@ protected function destroyDatabaseMigrations() Schema::drop(self::TABLE); } - /** - * @dataProvider floatComparisonsDataProvider - */ + #[DataProvider('floatComparisonsDataProvider')] public function testJsonFloatComparison($value, $operator, $shouldMatch) { DB::table(self::TABLE)->insert([self::JSON_COL => '{"rank":'.self::FLOAT_VAL.'}']); @@ -68,9 +67,7 @@ public function testFloatValueStoredCorrectly() $this->assertEquals(self::FLOAT_VAL, DB::table(self::TABLE)->value(self::FLOAT_COL)); } - /** - * @dataProvider jsonWhereNullDataProvider - */ + #[DataProvider('jsonWhereNullDataProvider')] public function testJsonWhereNull($expected, $key, array $value = ['value' => 123]) { DB::table(self::TABLE)->insert([self::JSON_COL => json_encode($value)]); @@ -78,9 +75,7 @@ public function testJsonWhereNull($expected, $key, array $value = ['value' => 12 $this->assertSame($expected, DB::table(self::TABLE)->whereNull(self::JSON_COL.'->'.$key)->exists()); } - /** - * @dataProvider jsonWhereNullDataProvider - */ + #[DataProvider('jsonWhereNullDataProvider')] public function testJsonWhereNotNull($expected, $key, array $value = ['value' => 123]) { DB::table(self::TABLE)->insert([self::JSON_COL => json_encode($value)]); @@ -122,9 +117,7 @@ public function testJsonPathUpdate() $this->assertSame(1, $updatedCount); } - /** - * @dataProvider jsonContainsKeyDataProvider - */ + #[DataProvider('jsonContainsKeyDataProvider')] public function testWhereJsonContainsKey($count, $column) { DB::table(self::TABLE)->insert([ diff --git a/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderAlterTableWithEnumTest.php b/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderAlterTableWithEnumTest.php index 18b156dc98da..ad1290f384f8 100644 --- a/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderAlterTableWithEnumTest.php +++ b/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderAlterTableWithEnumTest.php @@ -4,12 +4,12 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use stdClass; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class DatabaseMySqlSchemaBuilderAlterTableWithEnumTest extends MySqlTestCase { protected function defineDatabaseMigrationsAfterDatabaseRefreshed() diff --git a/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderTest.php b/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderTest.php index bcbcd6d12bdd..f493b687e522 100644 --- a/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderTest.php +++ b/tests/Integration/Database/MySql/DatabaseMySqlSchemaBuilderTest.php @@ -5,11 +5,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class DatabaseMySqlSchemaBuilderTest extends MySqlTestCase { public function testAddCommentToTable() diff --git a/tests/Integration/Database/MySql/EscapeTest.php b/tests/Integration/Database/MySql/EscapeTest.php index 9ad6d6e8a41f..3f50939d49a0 100644 --- a/tests/Integration/Database/MySql/EscapeTest.php +++ b/tests/Integration/Database/MySql/EscapeTest.php @@ -2,12 +2,12 @@ namespace Illuminate\Tests\Integration\Database\MySql; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use RuntimeException; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class EscapeTest extends MySqlTestCase { public function testEscapeInt() diff --git a/tests/Integration/Database/MySql/FulltextTest.php b/tests/Integration/Database/MySql/FulltextTest.php index a98d0c74b48a..69b7e33b8a1c 100644 --- a/tests/Integration/Database/MySql/FulltextTest.php +++ b/tests/Integration/Database/MySql/FulltextTest.php @@ -5,11 +5,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_mysql')] class FulltextTest extends MySqlTestCase { protected function defineDatabaseMigrationsAfterDatabaseRefreshed() diff --git a/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php b/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php index 41c238787712..7b4aab0fb019 100644 --- a/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php +++ b/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php @@ -5,11 +5,12 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_pgsql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_pgsql')] class DatabasePostgresConnectionTest extends PostgresTestCase { protected function defineDatabaseMigrationsAfterDatabaseRefreshed() @@ -26,9 +27,7 @@ protected function destroyDatabaseMigrations() Schema::drop('json_table'); } - /** - * @dataProvider jsonWhereNullDataProvider - */ + #[DataProvider('jsonWhereNullDataProvider')] public function testJsonWhereNull($expected, $key, array $value = ['value' => 123]) { DB::table('json_table')->insert(['json_col' => json_encode($value)]); @@ -36,9 +35,7 @@ public function testJsonWhereNull($expected, $key, array $value = ['value' => 12 $this->assertSame($expected, DB::table('json_table')->whereNull("json_col->$key")->exists()); } - /** - * @dataProvider jsonWhereNullDataProvider - */ + #[DataProvider('jsonWhereNullDataProvider')] public function testJsonWhereNotNull($expected, $key, array $value = ['value' => 123]) { DB::table('json_table')->insert(['json_col' => json_encode($value)]); @@ -91,9 +88,7 @@ public function testJsonPathUpdate() $this->assertSame(1, $updatedCount); } - /** - * @dataProvider jsonContainsKeyDataProvider - */ + #[DataProvider('jsonContainsKeyDataProvider')] public function testWhereJsonContainsKey($count, $column) { DB::table('json_table')->insert([ diff --git a/tests/Integration/Database/Postgres/EscapeTest.php b/tests/Integration/Database/Postgres/EscapeTest.php index dc382b4a1143..9feaedb2fd9f 100644 --- a/tests/Integration/Database/Postgres/EscapeTest.php +++ b/tests/Integration/Database/Postgres/EscapeTest.php @@ -2,12 +2,12 @@ namespace Illuminate\Tests\Integration\Database\Postgres; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use RuntimeException; -/** - * @requires extension pdo_pgsql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_pgsql')] class EscapeTest extends PostgresTestCase { public function testEscapeInt() diff --git a/tests/Integration/Database/Postgres/FulltextTest.php b/tests/Integration/Database/Postgres/FulltextTest.php index 39ddb6837022..81befb9fcbbe 100644 --- a/tests/Integration/Database/Postgres/FulltextTest.php +++ b/tests/Integration/Database/Postgres/FulltextTest.php @@ -5,11 +5,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_pgsql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_pgsql')] class FulltextTest extends PostgresTestCase { protected function defineDatabaseMigrationsAfterDatabaseRefreshed() diff --git a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php index 8efa387060ca..3aa249c2aece 100644 --- a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php +++ b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php @@ -5,11 +5,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_pgsql - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_pgsql')] class PostgresSchemaBuilderTest extends PostgresTestCase { protected function getEnvironmentSetUp($app) diff --git a/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php b/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php index 28b27f89f80f..975eb8c67954 100644 --- a/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php +++ b/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php @@ -5,11 +5,12 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_sqlsrv - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] +#[RequiresPhpExtension('pdo_sqlsrv')] class DatabaseSqlServerConnectionTest extends SqlServerTestCase { protected function defineDatabaseMigrationsAfterDatabaseRefreshed() @@ -26,9 +27,7 @@ protected function destroyDatabaseMigrations() Schema::drop('json_table'); } - /** - * @dataProvider jsonContainsKeyDataProvider - */ + #[DataProvider('jsonContainsKeyDataProvider')] public function testWhereJsonContainsKey($count, $column) { DB::table('json_table')->insert([ diff --git a/tests/Integration/Database/Sqlite/DatabaseSqliteConnectionTest.php b/tests/Integration/Database/Sqlite/DatabaseSqliteConnectionTest.php index 212ee52be7d8..f62ee3d22f09 100644 --- a/tests/Integration/Database/Sqlite/DatabaseSqliteConnectionTest.php +++ b/tests/Integration/Database/Sqlite/DatabaseSqliteConnectionTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Tests\Integration\Database\DatabaseTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class DatabaseSqliteConnectionTest extends DatabaseTestCase { @@ -38,9 +39,7 @@ protected function destroyDatabaseMigrations() Schema::drop('json_table'); } - /** - * @dataProvider jsonContainsKeyDataProvider - */ + #[DataProvider('jsonContainsKeyDataProvider')] public function testWhereJsonContainsKey($count, $column) { DB::table('json_table')->insert([ diff --git a/tests/Integration/Filesystem/FilesystemTest.php b/tests/Integration/Filesystem/FilesystemTest.php index 8bd38fe309fe..2bde297d09f9 100644 --- a/tests/Integration/Filesystem/FilesystemTest.php +++ b/tests/Integration/Filesystem/FilesystemTest.php @@ -4,11 +4,10 @@ use Illuminate\Support\Facades\File; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use Symfony\Component\Process\Process; -/** - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] class FilesystemTest extends TestCase { protected $stubFile; diff --git a/tests/Integration/Filesystem/StorageTest.php b/tests/Integration/Filesystem/StorageTest.php index 4193a629e178..95b4990bda04 100644 --- a/tests/Integration/Filesystem/StorageTest.php +++ b/tests/Integration/Filesystem/StorageTest.php @@ -5,11 +5,10 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use Symfony\Component\Process\Process; -/** - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|Darwin')] class StorageTest extends TestCase { protected $stubFile; diff --git a/tests/Integration/Foundation/ExceptionHandlerTest.php b/tests/Integration/Foundation/ExceptionHandlerTest.php index b6c71b96e321..d13a74f4122b 100644 --- a/tests/Integration/Foundation/ExceptionHandlerTest.php +++ b/tests/Integration/Foundation/ExceptionHandlerTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Process\PhpProcess; use Throwable; @@ -123,9 +124,7 @@ public function testItHasFallbackErrorMessageForUnknownStatusCodes() ]); } - /** - * @dataProvider exitCodesProvider - */ + #[DataProvider('exitCodesProvider')] public function testItReturnsNonZeroExitCodesForUncaughtExceptions($providers, $successful) { $basePath = static::applicationBasePath(); diff --git a/tests/Integration/Queue/CustomPayloadTest.php b/tests/Integration/Queue/CustomPayloadTest.php index 5ae3a8f43017..e3f7cf28b9a8 100644 --- a/tests/Integration/Queue/CustomPayloadTest.php +++ b/tests/Integration/Queue/CustomPayloadTest.php @@ -8,6 +8,7 @@ use Illuminate\Queue\Queue; use Illuminate\Support\ServiceProvider; use Orchestra\Testbench\Concerns\CreatesApplication; +use PHPUnit\Framework\Attributes\DataProvider; class CustomPayloadTest extends TestCase { @@ -25,9 +26,7 @@ public static function websites() yield ['blog.laravel.com']; } - /** - * @dataProvider websites - */ + #[DataProvider('websites')] public function test_custom_payload_gets_cleared_for_each_data_provider(string $websites) { $dispatcher = $this->app->make(QueueingDispatcher::class); diff --git a/tests/Integration/Routing/RouteRedirectTest.php b/tests/Integration/Routing/RouteRedirectTest.php index edf0882963f1..e46e8e7b4717 100644 --- a/tests/Integration/Routing/RouteRedirectTest.php +++ b/tests/Integration/Routing/RouteRedirectTest.php @@ -6,12 +6,11 @@ use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class RouteRedirectTest extends TestCase { - /** - * @dataProvider routeRedirectDataSets - */ + #[DataProvider('routeRedirectDataSets')] public function testRouteRedirect($redirectFrom, $redirectTo, $requestUri, $redirectUri) { $this->withoutExceptionHandling(); diff --git a/tests/Mail/MailManagerTest.php b/tests/Mail/MailManagerTest.php index deb824da5ec0..4ae036416be9 100644 --- a/tests/Mail/MailManagerTest.php +++ b/tests/Mail/MailManagerTest.php @@ -4,13 +4,12 @@ use InvalidArgumentException; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; class MailManagerTest extends TestCase { - /** - * @dataProvider emptyTransportConfigDataProvider - */ + #[DataProvider('emptyTransportConfigDataProvider')] public function testEmptyTransportConfig($transport) { $this->app['config']->set('mail.mailers.custom_smtp', [ diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 17087db4e592..fee6daff4ac1 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -8,6 +8,7 @@ use Illuminate\Queue\Queue; use Illuminate\Support\Str; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ReflectionClass; use stdClass; @@ -19,9 +20,7 @@ protected function tearDown(): void m::close(); } - /** - * @dataProvider pushJobsDataProvider - */ + #[DataProvider('pushJobsDataProvider')] public function testPushProperlyPushesJobOntoDatabase($uuid, $job, $displayNameStartsWith, $jobStartsWith) { Str::createUuidsUsing(function () use ($uuid) { diff --git a/tests/Queue/RedisQueueIntegrationTest.php b/tests/Queue/RedisQueueIntegrationTest.php index e7d26522f1e4..a195775cf82b 100644 --- a/tests/Queue/RedisQueueIntegrationTest.php +++ b/tests/Queue/RedisQueueIntegrationTest.php @@ -12,6 +12,8 @@ use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; class RedisQueueIntegrationTest extends TestCase @@ -45,10 +47,9 @@ protected function tearDown(): void } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testExpiredJobsArePopped($driver) { $this->setQueue($driver); @@ -77,14 +78,12 @@ public function testExpiredJobsArePopped($driver) } /** - * @dataProvider redisDriverProvider - * - * @requires extension pcntl - * * @param mixed $driver * * @throws \Exception */ + #[DataProvider('redisDriverProvider')] + #[RequiresPhpExtension('pcntl')] public function testBlockingPop($driver) { $this->tearDownRedis(); @@ -105,10 +104,9 @@ public function testBlockingPop($driver) } // /** - // * @dataProvider redisDriverProvider - // * // * @param string $driver // */ + // #[DataProvider('redisDriverProvider')] // public function testMigrateMoreThan100Jobs($driver) // { // $this->setQueue($driver); @@ -122,10 +120,9 @@ public function testBlockingPop($driver) // } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopProperlyPopsJobOffOfRedis($driver) { $this->setQueue($driver); @@ -157,10 +154,9 @@ public function testPopProperlyPopsJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopProperlyPopsDelayedJobOffOfRedis($driver) { $this->setQueue($driver); @@ -184,10 +180,9 @@ public function testPopProperlyPopsDelayedJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopPopsDelayedJobOffOfRedisWhenExpireNull($driver) { $this->setQueue($driver, 'default', null, null); @@ -214,10 +209,9 @@ public function testPopPopsDelayedJobOffOfRedisWhenExpireNull($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBlockingPopProperlyPopsJobOffOfRedis($driver) { $this->setQueue($driver, 'default', null, 60, 5); @@ -235,10 +229,9 @@ public function testBlockingPopProperlyPopsJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBlockingPopProperlyPopsExpiredJobs($driver) { Str::createUuidsUsing(function () { @@ -266,10 +259,9 @@ public function testBlockingPopProperlyPopsExpiredJobs($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testNotExpireJobsWhenExpireNull($driver) { $this->setQueue($driver, 'default', null, null); @@ -315,10 +307,9 @@ public function testNotExpireJobsWhenExpireNull($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testExpireJobsWhenExpireSet($driver) { $this->setQueue($driver, 'default', null, 30); @@ -344,10 +335,9 @@ public function testExpireJobsWhenExpireSet($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testRelease($driver) { $this->setQueue($driver); @@ -385,10 +375,9 @@ public function testRelease($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testReleaseInThePast($driver) { $this->setQueue($driver); @@ -403,10 +392,9 @@ public function testReleaseInThePast($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testDelete($driver) { $this->setQueue($driver); @@ -427,10 +415,9 @@ public function testDelete($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testClear($driver) { $this->setQueue($driver); @@ -447,10 +434,9 @@ public function testClear($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testSize($driver) { $this->setQueue($driver); @@ -468,10 +454,9 @@ public function testSize($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPushJobQueuedEvent($driver) { $events = m::mock(Dispatcher::class); @@ -493,10 +478,9 @@ public function testPushJobQueuedEvent($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBulkJobQueuedEvent($driver) { $events = m::mock(Dispatcher::class); diff --git a/tests/Routing/RouteUriTest.php b/tests/Routing/RouteUriTest.php index b9e0688b1be1..076d5deb7a72 100644 --- a/tests/Routing/RouteUriTest.php +++ b/tests/Routing/RouteUriTest.php @@ -3,13 +3,12 @@ namespace Illuminate\Tests\Routing; use Illuminate\Routing\RouteUri; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class RouteUriTest extends TestCase { - /** - * @dataProvider uriProvider - */ + #[DataProvider('uriProvider')] public function testRouteUrisAreProperlyParsed($uri, $expectedParsedUri, $expectedBindingFields) { $parsed = RouteUri::parse($uri); diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index 2c6079adf36b..6fdd50f2ce2a 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -10,6 +10,7 @@ use Illuminate\Routing\RouteCollection; use Illuminate\Routing\UrlGenerator; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -587,9 +588,7 @@ public static function providerRouteParameters() ]; } - /** - * @dataProvider providerRouteParameters - */ + #[DataProvider('providerRouteParameters')] public function testUrlGenerationForControllersRequiresPassingOfRequiredParameters($parameters) { $this->expectException(UrlGenerationException::class); @@ -641,9 +640,7 @@ public static function provideParametersAndExpectedMeaningfulExceptionMessages() ]; } - /** - * @dataProvider provideParametersAndExpectedMeaningfulExceptionMessages - */ + #[DataProvider('provideParametersAndExpectedMeaningfulExceptionMessages')] public function testUrlGenerationThrowsExceptionForMissingParametersWithMeaningfulMessage($parameters, $expectedMeaningfulExceptionMessage) { $this->expectException(UrlGenerationException::class); diff --git a/tests/Support/ConfigurationUrlParserTest.php b/tests/Support/ConfigurationUrlParserTest.php index 9035d08a45b6..55434eefa156 100644 --- a/tests/Support/ConfigurationUrlParserTest.php +++ b/tests/Support/ConfigurationUrlParserTest.php @@ -3,13 +3,12 @@ namespace Illuminate\Tests\Support; use Illuminate\Support\ConfigurationUrlParser; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ConfigurationUrlParserTest extends TestCase { - /** - * @dataProvider databaseUrls - */ + #[DataProvider('databaseUrls')] public function testDatabaseUrlsAreParsed($config, $expectedOutput) { $this->assertEquals($expectedOutput, (new ConfigurationUrlParser)->parseConfiguration($config)); diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 0555a9964888..0057989b1cf8 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -19,6 +19,7 @@ use IteratorAggregate; use JsonSerializable; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ReflectionClass; use stdClass; @@ -30,18 +31,14 @@ class SupportCollectionTest extends TestCase { - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstReturnsFirstItemInCollection($collection) { $c = new $collection(['foo', 'bar']); $this->assertSame('foo', $c->first()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWithCallback($collection) { $data = new $collection(['foo', 'bar', 'baz']); @@ -51,9 +48,7 @@ public function testFirstWithCallback($collection) $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWithCallbackAndDefault($collection) { $data = new $collection(['foo', 'bar']); @@ -63,9 +58,7 @@ public function testFirstWithCallbackAndDefault($collection) $this->assertSame('default', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWithDefaultAndWithoutCallback($collection) { $data = new $collection; @@ -77,9 +70,7 @@ public function testFirstWithDefaultAndWithoutCallback($collection) $this->assertSame('foo', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) { $collection = new $collection([ @@ -92,9 +83,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) $this->assertSame(['name' => 'foo'], $collection->sole('name', 'foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfNoItemsExist($collection) { $this->expectException(ItemNotFoundException::class); @@ -107,9 +96,7 @@ public function testSoleThrowsExceptionIfNoItemsExist($collection) $collection->where('name', 'INVALID')->sole(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) { $this->expectExceptionObject(new MultipleItemsFoundException(2)); @@ -123,9 +110,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) $collection->where('name', 'foo')->sole(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection) { $data = new $collection(['foo', 'bar', 'baz']); @@ -135,9 +120,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback( $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -149,9 +132,7 @@ public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($collection) { $this->expectExceptionObject(new MultipleItemsFoundException(2)); @@ -163,9 +144,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($coll }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailReturnsFirstItemInCollection($collection) { $collection = new $collection([ @@ -178,9 +157,7 @@ public function testFirstOrFailReturnsFirstItemInCollection($collection) $this->assertSame(['name' => 'foo'], $collection->firstOrFail('name', 'foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection) { $this->expectException(ItemNotFoundException::class); @@ -193,9 +170,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection) $collection->where('name', 'INVALID')->firstOrFail(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection) { $collection = new $collection([ @@ -207,9 +182,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($coll $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->firstOrFail()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection) { $data = new $collection(['foo', 'bar', 'baz']); @@ -219,9 +192,7 @@ public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCa $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -233,9 +204,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collec }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection) { $data = new $collection(['foo', 'bar', 'bar']); @@ -248,9 +217,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCa ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailStopsIteratingAtFirstMatch($collection) { $data = new $collection([ @@ -270,9 +237,7 @@ function () { })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWhere($collection) { $data = new $collection([ @@ -291,9 +256,7 @@ public function testFirstWhere($collection) $this->assertNull($data->firstWhere(fn ($value) => ($value['nonexistent'] ?? null) === 'key')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastReturnsLastItemInCollection($collection) { $c = new $collection(['foo', 'bar']); @@ -303,9 +266,7 @@ public function testLastReturnsLastItemInCollection($collection) $this->assertNull($c->last()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithCallback($collection) { $data = new $collection([100, 200, 300]); @@ -325,9 +286,7 @@ public function testLastWithCallback($collection) $this->assertNull($result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithCallbackAndDefault($collection) { $data = new $collection(['foo', 'bar']); @@ -343,9 +302,7 @@ public function testLastWithCallbackAndDefault($collection) $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithDefaultAndWithoutCallback($collection) { $data = new $collection; @@ -391,9 +348,7 @@ public function testShiftReturnsAndRemovesFirstXItemsInCollection() $this->assertEquals(new Collection(['foo', 'bar', 'baz']), (new Collection(['foo', 'bar', 'baz']))->shift(6)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliding($collection) { // Default parameters: $size = 2, $step = 1 @@ -450,9 +405,7 @@ public function testSliding($collection) $this->assertInstanceOf($collection, $chunks->skip(1)->first()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEmptyCollectionIsEmpty($collection) { $c = new $collection; @@ -460,9 +413,7 @@ public function testEmptyCollectionIsEmpty($collection) $this->assertTrue($c->isEmpty()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEmptyCollectionIsNotEmpty($collection) { $c = new $collection(['foo', 'bar']); @@ -471,9 +422,7 @@ public function testEmptyCollectionIsNotEmpty($collection) $this->assertTrue($c->isNotEmpty()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionIsConstructed($collection) { $data = new $collection('foo'); @@ -492,9 +441,7 @@ public function testCollectionIsConstructed($collection) $this->assertEmpty($data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionShuffleWithSeed($collection) { $data = new $collection(range(0, 100, 10)); @@ -505,9 +452,7 @@ public function testCollectionShuffleWithSeed($collection) $this->assertEquals($firstRandom, $secondRandom); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSkipMethod($collection) { $data = new $collection([1, 2, 3, 4, 5, 6]); @@ -519,9 +464,7 @@ public function testSkipMethod($collection) $this->assertSame([], $data->skip(10)->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSkipUntil($collection) { $data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]); @@ -557,9 +500,7 @@ public function testSkipUntil($collection) $this->assertSame([], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSkipWhile($collection) { $data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]); @@ -595,9 +536,7 @@ public function testSkipWhile($collection) $this->assertSame([3, 3, 4, 4], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetArrayableItems($collection) { $data = new $collection; @@ -635,9 +574,7 @@ public function testGetArrayableItems($collection) $this->assertSame(['foo' => 'bar'], $array); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testToArrayCallsToArrayOnEachItemInCollection($collection) { $item1 = m::mock(Arrayable::class); @@ -662,9 +599,7 @@ public function testLazyReturnsLazyCollection() $this->assertSame([1, 2, 3, 4, 5], $lazy->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testJsonSerializeCallsToArrayOrJsonSerializeOnEachItemInCollection($collection) { $item1 = m::mock(JsonSerializable::class); @@ -677,9 +612,7 @@ public function testJsonSerializeCallsToArrayOrJsonSerializeOnEachItemInCollecti $this->assertEquals(['foo.json', 'bar.array'], $results); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testToJsonEncodesTheJsonSerializeResult($collection) { $c = $this->getMockBuilder($collection)->onlyMethods(['jsonSerialize'])->getMock(); @@ -688,9 +621,7 @@ public function testToJsonEncodesTheJsonSerializeResult($collection) $this->assertJsonStringEqualsJsonString(json_encode(['foo']), $results); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCastingToStringJsonEncodesTheToArrayResult($collection) { $c = $this->getMockBuilder($collection)->onlyMethods(['jsonSerialize'])->getMock(); @@ -811,18 +742,14 @@ public function testForgetCollectionOfKeys() $this->assertTrue(isset($c['name'])); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCountable($collection) { $c = new $collection(['foo', 'bar']); $this->assertCount(2, $c); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCountByStandalone($collection) { $c = new $collection(['foo', 'foo', 'foo', 'bar', 'bar', 'foobar']); @@ -835,9 +762,7 @@ public function testCountByStandalone($collection) $this->assertEquals([1 => 3, 5 => 3], $c->countBy()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCountByWithKey($collection) { $c = new $collection([ @@ -847,9 +772,7 @@ public function testCountByWithKey($collection) $this->assertEquals(['a' => 4, 'b' => 3], $c->countBy('key')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCountableByWithCallback($collection) { $c = new $collection(['alice', 'aaron', 'bob', 'carla']); @@ -875,9 +798,7 @@ public function testAdd() $this->assertEquals([1, 2, '', null, false, [], 'name'], $c->add('name')->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContainsOneItem($collection) { $this->assertFalse((new $collection([]))->containsOneItem()); @@ -892,18 +813,14 @@ public function testIterable() $this->assertEquals(['foo'], $c->getIterator()->getArrayCopy()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCachingIterator($collection) { $c = new $collection(['foo']); $this->assertInstanceOf(CachingIterator::class, $c->getCachingIterator()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFilter($collection) { $c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]); @@ -923,9 +840,7 @@ public function testFilter($collection) $this->assertEquals([1, 2, 3], $c->filter()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderKeyBy($collection) { $c = new $collection([ @@ -936,9 +851,7 @@ public function testHigherOrderKeyBy($collection) $this->assertEquals(['id1' => 'first', 'id2' => 'second'], $c->keyBy->id->map->name->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderUnique($collection) { $c = new $collection([ @@ -949,9 +862,7 @@ public function testHigherOrderUnique($collection) $this->assertCount(1, $c->unique->id); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderFilter($collection) { $c = new $collection([ @@ -978,9 +889,7 @@ public function active() $this->assertCount(1, $c->filter->active()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhere($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); @@ -1118,9 +1027,7 @@ public function testWhere($collection) $this->assertEquals([['v' => 2, 'g' => null]], $c->where('v', 2)->whereNull('g')->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereStrict($collection) { $c = new $collection([['v' => 3], ['v' => '3']]); @@ -1131,9 +1038,7 @@ public function testWhereStrict($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereInstanceOf($collection) { $c = new $collection([new stdClass, new stdClass, new $collection, new stdClass, new Str]); @@ -1142,9 +1047,7 @@ public function testWhereInstanceOf($collection) $this->assertCount(4, $c->whereInstanceOf([stdClass::class, Str::class])); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereIn($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); @@ -1153,18 +1056,14 @@ public function testWhereIn($collection) $this->assertEquals([['v' => 1]], $c->whereIn('v', [1])->whereIn('v', [1, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereInStrict($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); $this->assertEquals([['v' => 1], ['v' => 3]], $c->whereInStrict('v', [1, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotIn($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); @@ -1172,18 +1071,14 @@ public function testWhereNotIn($collection) $this->assertEquals([['v' => 4]], $c->whereNotIn('v', [2])->whereNotIn('v', [1, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotInStrict($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); $this->assertEquals([['v' => 2], ['v' => '3'], ['v' => 4]], $c->whereNotInStrict('v', [1, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testValues($collection) { $c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]); @@ -1192,18 +1087,14 @@ public function testValues($collection) })->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testValuesResetKey($collection) { $data = new $collection([1 => 'a', 2 => 'b', 3 => 'c']); $this->assertEquals([0 => 'a', 1 => 'b', 2 => 'c'], $data->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testValue($collection) { $c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]); @@ -1221,9 +1112,7 @@ public function testValue($collection) $this->assertEquals('bar', $c->where('id', 2)->value('pivot.value')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testBetween($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); @@ -1234,9 +1123,7 @@ public function testBetween($collection) $this->assertEquals([['v' => 3], ['v' => '3']], $c->whereBetween('v', [3, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotBetween($collection) { $c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]); @@ -1246,9 +1133,7 @@ public function testWhereNotBetween($collection) $this->assertEquals([['v' => 1], ['v' => '2'], ['v' => '4']], $c->whereNotBetween('v', [3, 3])->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFlatten($collection) { // Flat arrays are unaffected @@ -1284,9 +1169,7 @@ public function testFlatten($collection) $this->assertEquals(['#foo', '#bar', '#zap', '#baz'], $c->flatten()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFlattenWithDepth($collection) { // No depth flattens recursively @@ -1301,9 +1184,7 @@ public function testFlattenWithDepth($collection) $this->assertEquals(['#foo', '#bar', ['#baz'], '#zap'], $c->flatten(2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFlattenIgnoresKeys($collection) { // No depth ignores keys @@ -1315,54 +1196,42 @@ public function testFlattenIgnoresKeys($collection) $this->assertEquals(['#foo', '#bar', '#baz', '#zap'], $c->flatten(1)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeNull($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello'], $c->merge(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeArray($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello', 'id' => 1], $c->merge(['id' => 1])->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeCollection($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'World', 'id' => 1], $c->merge(new $collection(['name' => 'World', 'id' => 1]))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeRecursiveNull($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello'], $c->mergeRecursive(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeRecursiveArray($collection) { $c = new $collection(['name' => 'Hello', 'id' => 1]); $this->assertEquals(['name' => 'Hello', 'id' => [1, 2]], $c->mergeRecursive(['id' => 2])->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMergeRecursiveCollection($collection) { $c = new $collection(['name' => 'Hello', 'id' => 1, 'meta' => ['tags' => ['a', 'b'], 'roles' => 'admin']]); @@ -1372,18 +1241,14 @@ public function testMergeRecursiveCollection($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceNull($collection) { $c = new $collection(['a', 'b', 'c']); $this->assertEquals(['a', 'b', 'c'], $c->replace(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceArray($collection) { $c = new $collection(['a', 'b', 'c']); @@ -1396,9 +1261,7 @@ public function testReplaceArray($collection) $this->assertEquals(['name' => 'taylor', 'family' => 'otwell', 'age' => 26], $c->replace(['name' => 'taylor', 'age' => 26])->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceCollection($collection) { $c = new $collection(['a', 'b', 'c']); @@ -1420,18 +1283,14 @@ public function testReplaceCollection($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceRecursiveNull($collection) { $c = new $collection(['a', 'b', ['c', 'd']]); $this->assertEquals(['a', 'b', ['c', 'd']], $c->replaceRecursive(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceRecursiveArray($collection) { $c = new $collection(['a', 'b', ['c', 'd']]); @@ -1441,9 +1300,7 @@ public function testReplaceRecursiveArray($collection) $this->assertEquals(['z', 'b', ['c', 'e'], 'f'], $c->replaceRecursive(['z', 2 => [1 => 'e'], 'f'])->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReplaceRecursiveCollection($collection) { $c = new $collection(['a', 'b', ['c', 'd']]); @@ -1453,45 +1310,35 @@ public function testReplaceRecursiveCollection($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnionNull($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello'], $c->union(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnionArray($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello', 'id' => 1], $c->union(['id' => 1])->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnionCollection($collection) { $c = new $collection(['name' => 'Hello']); $this->assertEquals(['name' => 'Hello', 'id' => 1], $c->union(new $collection(['name' => 'World', 'id' => 1]))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffCollection($collection) { $c = new $collection(['id' => 1, 'first_word' => 'Hello']); $this->assertEquals(['id' => 1], $c->diff(new $collection(['first_word' => 'Hello', 'last_word' => 'World']))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffUsingWithCollection($collection) { $c = new $collection(['en_GB', 'fr', 'HR']); @@ -1501,27 +1348,21 @@ public function testDiffUsingWithCollection($collection) $this->assertEquals(['fr'], $c->diffUsing(new $collection(['en_gb', 'hr']), 'strcasecmp')->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffUsingWithNull($collection) { $c = new $collection(['en_GB', 'fr', 'HR']); $this->assertEquals(['en_GB', 'fr', 'HR'], $c->diffUsing(null, 'strcasecmp')->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffNull($collection) { $c = new $collection(['id' => 1, 'first_word' => 'Hello']); $this->assertEquals(['id' => 1, 'first_word' => 'Hello'], $c->diff(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffKeys($collection) { $c1 = new $collection(['id' => 1, 'first_word' => 'Hello']); @@ -1529,9 +1370,7 @@ public function testDiffKeys($collection) $this->assertEquals(['first_word' => 'Hello'], $c1->diffKeys($c2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffKeysUsing($collection) { $c1 = new $collection(['id' => 1, 'first_word' => 'Hello']); @@ -1542,9 +1381,7 @@ public function testDiffKeysUsing($collection) $this->assertEquals(['first_word' => 'Hello'], $c1->diffKeysUsing($c2, 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffAssoc($collection) { $c1 = new $collection(['id' => 1, 'first_word' => 'Hello', 'not_affected' => 'value']); @@ -1552,9 +1389,7 @@ public function testDiffAssoc($collection) $this->assertEquals(['id' => 1, 'first_word' => 'Hello'], $c1->diffAssoc($c2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDiffAssocUsing($collection) { $c1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']); @@ -1565,9 +1400,7 @@ public function testDiffAssocUsing($collection) $this->assertEquals(['b' => 'brown', 'c' => 'blue', 'red'], $c1->diffAssocUsing($c2, 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDuplicates($collection) { $duplicates = $collection::make([1, 2, 1, 'laravel', null, 'laravel', 'php', null])->duplicates()->all(); @@ -1587,9 +1420,7 @@ public function testDuplicates($collection) $this->assertSame([1 => $expected, 2 => $expected, 5 => '2'], $duplicates); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDuplicatesWithKey($collection) { $items = [['framework' => 'vue'], ['framework' => 'laravel'], ['framework' => 'laravel']]; @@ -1602,9 +1433,7 @@ public function testDuplicatesWithKey($collection) $this->assertSame([2 => 'vue'], $duplicates); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDuplicatesWithCallback($collection) { $items = [['framework' => 'vue'], ['framework' => 'laravel'], ['framework' => 'laravel']]; @@ -1614,9 +1443,7 @@ public function testDuplicatesWithCallback($collection) $this->assertSame([2 => 'laravel'], $duplicates); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDuplicatesWithStrict($collection) { $duplicates = $collection::make([1, 2, 1, 'laravel', null, 'laravel', 'php', null])->duplicatesStrict()->all(); @@ -1636,9 +1463,7 @@ public function testDuplicatesWithStrict($collection) $this->assertSame([2 => $expected, 5 => '2'], $duplicates); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEach($collection) { $c = new $collection($original = [1, 2, 'foo' => 'bar', 'bam' => 'baz']); @@ -1659,9 +1484,7 @@ public function testEach($collection) $this->assertEquals([1, 2, 'foo' => 'bar'], $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEachSpread($collection) { $c = new $collection([[1, 'a'], [2, 'b']]); @@ -1694,27 +1517,21 @@ public function testEachSpread($collection) $this->assertEquals([[1, 'a', 0], [2, 'b', 1]], $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectNull($collection) { $c = new $collection(['id' => 1, 'first_word' => 'Hello']); $this->assertEquals([], $c->intersect(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectCollection($collection) { $c = new $collection(['id' => 1, 'first_word' => 'Hello']); $this->assertEquals(['first_word' => 'Hello'], $c->intersect(new $collection(['first_world' => 'Hello', 'last_word' => 'World']))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectUsingWithNull($collection) { $collect = new $collection(['green', 'brown', 'blue']); @@ -1722,9 +1539,7 @@ public function testIntersectUsingWithNull($collection) $this->assertEquals([], $collect->intersectUsing(null, 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectUsingCollection($collection) { $collect = new $collection(['green', 'brown', 'blue']); @@ -1732,9 +1547,7 @@ public function testIntersectUsingCollection($collection) $this->assertEquals(['green', 'brown'], $collect->intersectUsing(new $collection(['GREEN', 'brown', 'yellow']), 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectAssocWithNull($collection) { $array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']); @@ -1742,9 +1555,7 @@ public function testIntersectAssocWithNull($collection) $this->assertEquals([], $array1->intersectAssoc(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectAssocCollection($collection) { $array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']); @@ -1753,9 +1564,7 @@ public function testIntersectAssocCollection($collection) $this->assertEquals(['a' => 'green'], $array1->intersectAssoc($array2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectAssocUsingWithNull($collection) { $array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']); @@ -1763,9 +1572,7 @@ public function testIntersectAssocUsingWithNull($collection) $this->assertEquals([], $array1->intersectAssocUsing(null, 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectAssocUsingCollection($collection) { $array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']); @@ -1774,18 +1581,14 @@ public function testIntersectAssocUsingCollection($collection) $this->assertEquals(['b' => 'brown'], $array1->intersectAssocUsing($array2, 'strcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectByKeysNull($collection) { $c = new $collection(['name' => 'Mateus', 'age' => 18]); $this->assertEquals([], $c->intersectByKeys(null)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testIntersectByKeys($collection) { $c = new $collection(['name' => 'Mateus', 'age' => 18]); @@ -1795,9 +1598,7 @@ public function testIntersectByKeys($collection) $this->assertEquals(['name' => 'taylor', 'family' => 'otwell'], $c->intersectByKeys(new $collection(['height' => 180, 'name' => 'amir', 'family' => 'moharami']))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnique($collection) { $c = new $collection(['Hello', 'World', 'World']); @@ -1807,9 +1608,7 @@ public function testUnique($collection) $this->assertEquals([[1, 2], [2, 3], [3, 4]], $c->unique()->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUniqueWithCallback($collection) { $c = new $collection([ @@ -1842,9 +1641,7 @@ public function testUniqueWithCallback($collection) })->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUniqueStrict($collection) { $c = new $collection([ @@ -1874,27 +1671,21 @@ public function testUniqueStrict($collection) ], $c->uniqueStrict('id')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollapse($collection) { $data = new $collection([[$object1 = new stdClass], [$object2 = new stdClass]]); $this->assertEquals([$object1, $object2], $data->collapse()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollapseWithNestedCollections($collection) { $data = new $collection([new $collection([1, 2, 3]), new $collection([4, 5, 6])]); $this->assertEquals([1, 2, 3, 4, 5, 6], $data->collapse()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testJoin($collection) { $this->assertSame('a, b, c', (new $collection(['a', 'b', 'c']))->join(', ')); @@ -1908,9 +1699,7 @@ public function testJoin($collection) $this->assertSame('', (new $collection([]))->join(', ', ' and ')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCrossJoin($collection) { // Cross join with an array @@ -1940,9 +1729,7 @@ public function testCrossJoin($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSort($collection) { $data = (new $collection([5, 3, 1, 2, 4]))->sort(); @@ -1961,9 +1748,7 @@ public function testSort($collection) $this->assertEquals(['T1', 'T2', 'T10'], $data->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortDesc($collection) { $data = (new $collection([5, 3, 1, 2, 4]))->sortDesc(); @@ -1982,9 +1767,7 @@ public function testSortDesc($collection) $this->assertEquals(['T10', 'T2', 'T1'], $data->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortWithCallback($collection) { $data = (new $collection([5, 3, 1, 2, 4]))->sort(function ($a, $b) { @@ -1998,9 +1781,7 @@ public function testSortWithCallback($collection) $this->assertEquals(range(1, 5), array_values($data->all())); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortBy($collection) { $data = new $collection(['taylor', 'dayle']); @@ -2018,9 +1799,7 @@ public function testSortBy($collection) $this->assertEquals(['taylor', 'dayle'], array_values($data->all())); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortByString($collection) { $data = new $collection([['name' => 'taylor'], ['name' => 'dayle']]); @@ -2034,9 +1813,7 @@ public function testSortByString($collection) $this->assertEquals([['name' => 'dayle'], ['name' => 'taylor']], array_values($data->all())); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortByCallableString($collection) { $data = new $collection([['sort' => 2], ['sort' => 1]]); @@ -2045,9 +1822,7 @@ public function testSortByCallableString($collection) $this->assertEquals([['sort' => 1], ['sort' => 2]], array_values($data->all())); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortByAlwaysReturnsAssoc($collection) { $data = new $collection(['a' => 'taylor', 'b' => 'dayle']); @@ -2075,9 +1850,7 @@ public function testSortByAlwaysReturnsAssoc($collection) $this->assertEquals([1 => ['sort' => 1], 0 => ['sort' => 2]], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortKeys($collection) { $data = new $collection(['b' => 'dayle', 'a' => 'taylor']); @@ -2085,9 +1858,7 @@ public function testSortKeys($collection) $this->assertSame(['a' => 'taylor', 'b' => 'dayle'], $data->sortKeys()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortKeysDesc($collection) { $data = new $collection(['a' => 'taylor', 'b' => 'dayle']); @@ -2095,9 +1866,7 @@ public function testSortKeysDesc($collection) $this->assertSame(['b' => 'dayle', 'a' => 'taylor'], $data->sortKeysDesc()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSortKeysUsing($collection) { $data = new $collection(['B' => 'dayle', 'a' => 'taylor']); @@ -2105,9 +1874,7 @@ public function testSortKeysUsing($collection) $this->assertSame(['a' => 'taylor', 'B' => 'dayle'], $data->sortKeysUsing('strnatcasecmp')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReverse($collection) { $data = new $collection(['zaeed', 'alan']); @@ -2121,18 +1888,14 @@ public function testReverse($collection) $this->assertSame(['framework' => 'laravel', 'name' => 'taylor'], $reversed->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFlip($collection) { $data = new $collection(['name' => 'taylor', 'framework' => 'laravel']); $this->assertEquals(['taylor' => 'name', 'laravel' => 'framework'], $data->flip()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testChunk($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -2145,9 +1908,7 @@ public function testChunk($collection) $this->assertEquals([9 => 10], $data->get(3)->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testChunkWhenGivenZeroAsSize($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -2158,9 +1919,7 @@ public function testChunkWhenGivenZeroAsSize($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testChunkWhenGivenLessThanZero($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -2171,9 +1930,7 @@ public function testChunkWhenGivenLessThanZero($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitIn($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -2187,9 +1944,7 @@ public function testSplitIn($collection) $this->assertEquals([9, 10], $data->get(2)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testChunkWhileOnEqualElements($collection) { $data = (new $collection(['A', 'A', 'B', 'B', 'C', 'C', 'C'])) @@ -2204,9 +1959,7 @@ public function testChunkWhileOnEqualElements($collection) $this->assertEquals([4 => 'C', 5 => 'C', 6 => 'C'], $data->last()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testChunkWhileOnContiguouslyIncreasingIntegers($collection) { $data = (new $collection([1, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21])) @@ -2223,9 +1976,7 @@ public function testChunkWhileOnContiguouslyIncreasingIntegers($collection) $this->assertEquals([8 => 19, 9 => 20, 10 => 21], $data->last()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEvery($collection) { $c = new $collection([]); @@ -2255,9 +2006,7 @@ public function testEvery($collection) $this->assertFalse($c->concat([['active' => false]])->every->active); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testExcept($collection) { $data = new $collection(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']); @@ -2272,18 +2021,14 @@ public function testExcept($collection) $this->assertEquals(['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], $data->except(collect(['last']))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testExceptSelf($collection) { $data = new $collection(['first' => 'Taylor', 'last' => 'Otwell']); $this->assertEquals(['first' => 'Taylor', 'last' => 'Otwell'], $data->except($data)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckWithArrayAndObjectValues($collection) { $data = new $collection([(object) ['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]); @@ -2291,9 +2036,7 @@ public function testPluckWithArrayAndObjectValues($collection) $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckWithArrayAccessValues($collection) { $data = new $collection([ @@ -2305,9 +2048,7 @@ public function testPluckWithArrayAccessValues($collection) $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckWithDotNotation($collection) { $data = new $collection([ @@ -2328,9 +2069,7 @@ public function testPluckWithDotNotation($collection) $this->assertEquals([['php', 'python'], ['php', 'asp', 'java']], $data->pluck('skill.backend')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckDuplicateKeysExist($collection) { $data = new collection([ @@ -2343,9 +2082,7 @@ public function testPluckDuplicateKeysExist($collection) $this->assertEquals(['Tesla' => 'black', 'Pagani' => 'orange'], $data->pluck('color', 'brand')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHas($collection) { $data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']); @@ -2356,9 +2093,7 @@ public function testHas($collection) $this->assertTrue($data->has('first', 'second')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHasAny($collection) { $data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']); @@ -2372,9 +2107,7 @@ public function testHasAny($collection) $this->assertFalse($data->hasAny([])); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testImplode($collection) { $data = new $collection([['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]); @@ -2402,9 +2135,7 @@ public function testImplode($collection) $this->assertSame('taylor-foo,dayle-bar', $data->implode(fn ($user) => $user['name'].'-'.$user['email'], ',')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTake($collection) { $data = new $collection(['taylor', 'dayle', 'shawn']); @@ -2457,9 +2188,7 @@ public function testPutWithNoKey() $this->assertEquals(['taylor', 'shawn', 'dayle'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRandom($collection) { $data = new $collection([1, 2, 3, 4, 5, 6]); @@ -2507,9 +2236,7 @@ public function testRandom($collection) $this->assertCount(5, array_intersect_assoc($random->all(), $data->all())); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRandomOnEmptyCollection($collection) { $data = new $collection; @@ -2523,9 +2250,7 @@ public function testRandomOnEmptyCollection($collection) $this->assertCount(0, $random); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeLast($collection) { $data = new $collection(['taylor', 'dayle', 'shawn']); @@ -2533,9 +2258,7 @@ public function testTakeLast($collection) $this->assertEquals([1 => 'dayle', 2 => 'shawn'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeUntilUsingValue($collection) { $data = new $collection([1, 2, 3, 4]); @@ -2545,9 +2268,7 @@ public function testTakeUntilUsingValue($collection) $this->assertSame([1, 2], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeUntilUsingCallback($collection) { $data = new $collection([1, 2, 3, 4]); @@ -2559,9 +2280,7 @@ public function testTakeUntilUsingCallback($collection) $this->assertSame([1, 2], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeUntilReturnsAllItemsForUnmetValue($collection) { $data = new $collection([1, 2, 3, 4]); @@ -2577,9 +2296,7 @@ public function testTakeUntilReturnsAllItemsForUnmetValue($collection) $this->assertSame($data->toArray(), $actual->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeUntilCanBeProxied($collection) { $data = new $collection([ @@ -2595,9 +2312,7 @@ public function testTakeUntilCanBeProxied($collection) $this->assertSame('Taylor', $actual->get(1)->name); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeWhileUsingValue($collection) { $data = new $collection([1, 1, 2, 2, 3, 3]); @@ -2607,9 +2322,7 @@ public function testTakeWhileUsingValue($collection) $this->assertSame([1, 1], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeWhileUsingCallback($collection) { $data = new $collection([1, 2, 3, 4]); @@ -2621,9 +2334,7 @@ public function testTakeWhileUsingCallback($collection) $this->assertSame([1, 2], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeWhileReturnsNoItemsForUnmetValue($collection) { $data = new $collection([1, 2, 3, 4]); @@ -2639,9 +2350,7 @@ public function testTakeWhileReturnsNoItemsForUnmetValue($collection) $this->assertSame([], $actual->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeWhileCanBeProxied($collection) { $data = new $collection([ @@ -2658,9 +2367,7 @@ public function testTakeWhileCanBeProxied($collection) $this->assertSame('Adam', $actual->get(1)->name); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMacroable($collection) { // Foo() macro : unique values starting with A @@ -2677,9 +2384,7 @@ public function testMacroable($collection) $this->assertSame(['a', 'aa', 'aaa'], $c->foo()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCanAddMethodsToProxy($collection) { $collection::macro('adults', function ($callback) { @@ -2695,18 +2400,14 @@ public function testCanAddMethodsToProxy($collection) $this->assertSame([['age' => 18], ['age' => 56]], $c->adults->age->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMakeMethod($collection) { $data = $collection::make('foo'); $this->assertEquals(['foo'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMakeMethodFromNull($collection) { $data = $collection::make(null); @@ -2716,9 +2417,7 @@ public function testMakeMethodFromNull($collection) $this->assertEquals([], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMakeMethodFromCollection($collection) { $firstCollection = $collection::make(['foo' => 'bar']); @@ -2726,72 +2425,56 @@ public function testMakeMethodFromCollection($collection) $this->assertEquals(['foo' => 'bar'], $secondCollection->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMakeMethodFromArray($collection) { $data = $collection::make(['foo' => 'bar']); $this->assertEquals(['foo' => 'bar'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithScalar($collection) { $data = $collection::wrap('foo'); $this->assertEquals(['foo'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithArray($collection) { $data = $collection::wrap(['foo']); $this->assertEquals(['foo'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithArrayable($collection) { $data = $collection::wrap($o = new TestArrayableObject); $this->assertEquals([$o], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithJsonable($collection) { $data = $collection::wrap($o = new TestJsonableObject); $this->assertEquals([$o], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithJsonSerialize($collection) { $data = $collection::wrap($o = new TestJsonSerializeObject); $this->assertEquals([$o], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithCollectionClass($collection) { $data = $collection::wrap($collection::make(['foo'])); $this->assertEquals(['foo'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWrapWithCollectionSubclass($collection) { $data = TestCollectionSubclass::wrap($collection::make(['foo'])); @@ -2799,34 +2482,26 @@ public function testWrapWithCollectionSubclass($collection) $this->assertInstanceOf(TestCollectionSubclass::class, $data); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnwrapCollection($collection) { $data = new $collection(['foo']); $this->assertEquals(['foo'], $collection::unwrap($data)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnwrapCollectionWithArray($collection) { $this->assertEquals(['foo'], $collection::unwrap(['foo'])); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnwrapCollectionWithScalar($collection) { $this->assertSame('foo', $collection::unwrap('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEmptyMethod($collection) { $collection = $collection::empty(); @@ -2834,9 +2509,7 @@ public function testEmptyMethod($collection) $this->assertCount(0, $collection->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTimesMethod($collection) { $two = $collection::times(2, function ($number) { @@ -2859,9 +2532,7 @@ public function testTimesMethod($collection) $this->assertEquals(range(1, 5), $range->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRangeMethod($collection) { $this->assertSame( @@ -2895,9 +2566,7 @@ public function testRangeMethod($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMakeFromObject($collection) { $object = new stdClass; @@ -2906,18 +2575,14 @@ public function testConstructMakeFromObject($collection) $this->assertEquals(['foo' => 'bar'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethod($collection) { $data = new $collection('foo'); $this->assertEquals(['foo'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethodFromNull($collection) { $data = new $collection(null); @@ -2927,9 +2592,7 @@ public function testConstructMethodFromNull($collection) $this->assertEquals([], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethodFromCollection($collection) { $firstCollection = new $collection(['foo' => 'bar']); @@ -2937,18 +2600,14 @@ public function testConstructMethodFromCollection($collection) $this->assertEquals(['foo' => 'bar'], $secondCollection->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethodFromArray($collection) { $data = new $collection(['foo' => 'bar']); $this->assertEquals(['foo' => 'bar'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethodFromObject($collection) { $object = new stdClass; @@ -2985,9 +2644,7 @@ public function testSplice() $this->assertEquals(['foo', 'bar', 'baz'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetPluckValueWithAccessors($collection) { $model = new TestAccessorEloquentTestStub(['some' => 'foo']); @@ -2997,9 +2654,7 @@ public function testGetPluckValueWithAccessors($collection) $this->assertEquals(['foo', 'bar'], $data->pluck('some')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMap($collection) { $data = new $collection([1, 2, 3]); @@ -3016,9 +2671,7 @@ public function testMap($collection) $this->assertEquals(['first' => 'first-rolyat', 'last' => 'last-llewto'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapSpread($collection) { $c = new $collection([[1, 'a'], [2, 'b']]); @@ -3040,9 +2693,7 @@ public function testMapSpread($collection) $this->assertEquals(['1-a-0', '2-b-1'], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFlatMap($collection) { $data = new $collection([ @@ -3055,9 +2706,7 @@ public function testFlatMap($collection) $this->assertEquals(['programming', 'basketball', 'music', 'powerlifting'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapToDictionary($collection) { $data = new $collection([ @@ -3076,9 +2725,7 @@ public function testMapToDictionary($collection) $this->assertIsArray($groups->get('A')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapToDictionaryWithNumericKeys($collection) { $data = new $collection([1, 2, 3, 2, 1]); @@ -3090,9 +2737,7 @@ public function testMapToDictionaryWithNumericKeys($collection) $this->assertEquals([1 => [0, 4], 2 => [1, 3], 3 => [2]], $groups->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapToGroups($collection) { $data = new $collection([ @@ -3111,9 +2756,7 @@ public function testMapToGroups($collection) $this->assertInstanceOf($collection, $groups->get('A')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapToGroupsWithNumericKeys($collection) { $data = new $collection([1, 2, 3, 2, 1]); @@ -3126,9 +2769,7 @@ public function testMapToGroupsWithNumericKeys($collection) $this->assertEquals([1, 2, 3, 2, 1], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeys($collection) { $data = new $collection([ @@ -3145,9 +2786,7 @@ public function testMapWithKeys($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysIntegerKeys($collection) { $data = new $collection([ @@ -3164,9 +2803,7 @@ public function testMapWithKeysIntegerKeys($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysMultipleRows($collection) { $data = new $collection([ @@ -3190,9 +2827,7 @@ public function testMapWithKeysMultipleRows($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysCallbackKey($collection) { $data = new $collection([ @@ -3209,9 +2844,7 @@ public function testMapWithKeysCallbackKey($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapInto($collection) { $data = new $collection([ @@ -3224,9 +2857,7 @@ public function testMapInto($collection) $this->assertSame('second', $data->get(1)->value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testNth($collection) { $data = new $collection([ @@ -3251,9 +2882,7 @@ public function testNth($collection) $this->assertEquals(['e'], $data->nth(2, -2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysOverwritingKeys($collection) { $data = new $collection([ @@ -3282,9 +2911,7 @@ public function testTransform() $this->assertEquals(['first' => 'first-rolyat', 'last' => 'last-llewto'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByAttribute($collection) { $data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]); @@ -3296,9 +2923,7 @@ public function testGroupByAttribute($collection) $this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByAttributeWithStringableKey($collection) { $data = new $collection($payload = [ @@ -3320,9 +2945,7 @@ public function __toString() $this->assertEquals(['1' => [$payload[0], $payload[1]], '2' => [$payload[2]]], $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByCallable($collection) { $data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]); @@ -3344,9 +2967,7 @@ public function sortByUrl(array $value) return $value['url']; } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByAttributeWithBackedEnumKey($collection) { $data = new $collection([ @@ -3358,9 +2979,7 @@ public function testGroupByAttributeWithBackedEnumKey($collection) $this->assertEquals([TestBackedEnum::A->value => [['rating' => TestBackedEnum::A, 'url' => '1']], TestBackedEnum::B->value => [['rating' => TestBackedEnum::B, 'url' => '1']]], $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByAttributePreservingKeys($collection) { $data = new $collection([10 => ['rating' => 1, 'url' => '1'], 20 => ['rating' => 1, 'url' => '1'], 30 => ['rating' => 2, 'url' => '2']]); @@ -3375,9 +2994,7 @@ public function testGroupByAttributePreservingKeys($collection) $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveSingleGroup($collection) { $data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]); @@ -3389,9 +3006,7 @@ public function testGroupByClosureWhereItemsHaveSingleGroup($collection) $this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($collection) { $data = new $collection([10 => ['rating' => 1, 'url' => '1'], 20 => ['rating' => 1, 'url' => '1'], 30 => ['rating' => 2, 'url' => '2']]); @@ -3408,9 +3023,7 @@ public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($colle $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveMultipleGroups($collection) { $data = new $collection([ @@ -3440,9 +3053,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroups($collection) $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($collection) { $data = new $collection([ @@ -3472,9 +3083,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($co $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByMultiLevelAndClosurePreservingKeys($collection) { $data = new $collection([ @@ -3517,9 +3126,7 @@ function ($item) { $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeyByAttribute($collection) { $data = new $collection([['rating' => 1, 'name' => '1'], ['rating' => 2, 'name' => '2'], ['rating' => 3, 'name' => '3']]); @@ -3533,9 +3140,7 @@ public function testKeyByAttribute($collection) $this->assertEquals([2 => ['rating' => 1, 'name' => '1'], 4 => ['rating' => 2, 'name' => '2'], 6 => ['rating' => 3, 'name' => '3']], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeyByClosure($collection) { $data = new $collection([ @@ -3551,9 +3156,7 @@ public function testKeyByClosure($collection) ], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeyByObject($collection) { $data = new $collection([ @@ -3569,9 +3172,7 @@ public function testKeyByObject($collection) ], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContains($collection) { $c = new $collection([1, 3, 5]); @@ -3630,9 +3231,7 @@ public function testContains($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDoesntContain($collection) { $c = new $collection([1, 3, 5]); @@ -3691,9 +3290,7 @@ public function testDoesntContain($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSome($collection) { $c = new $collection([1, 3, 5]); @@ -3732,9 +3329,7 @@ public function testSome($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContainsStrict($collection) { $c = new $collection([1, 3, 5, '02']); @@ -3780,9 +3375,7 @@ public function testContainsStrict($collection) $this->assertTrue($c->containsStrict('')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContainsWithOperator($collection) { $c = new $collection([['v' => 1], ['v' => 3], ['v' => '4'], ['v' => 5]]); @@ -3793,9 +3386,7 @@ public function testContainsWithOperator($collection) $this->assertTrue($c->contains('v', '>', 4)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGettingSumFromCollection($collection) { $c = new $collection([(object) ['foo' => 50], (object) ['foo' => 50]]); @@ -3807,27 +3398,21 @@ public function testGettingSumFromCollection($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCanSumValuesWithoutACallback($collection) { $c = new $collection([1, 2, 3, 4, 5]); $this->assertEquals(15, $c->sum()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGettingSumFromEmptyCollection($collection) { $c = new $collection; $this->assertEquals(0, $c->sum('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testValueRetrieverAcceptsDotNotation($collection) { $c = new $collection([ @@ -3894,9 +3479,7 @@ public function testPullReturnsDefault() $this->assertSame('foo', $value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRejectRemovesElementsPassingTruthTest($collection) { $c = new $collection(['foo', 'bar']); @@ -3924,9 +3507,7 @@ public function testRejectRemovesElementsPassingTruthTest($collection) })->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRejectWithoutAnArgumentRemovesTruthyValues($collection) { $data1 = new $collection([ @@ -3947,9 +3528,7 @@ public function testRejectWithoutAnArgumentRemovesTruthyValues($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSearchReturnsIndexOfFirstFoundItem($collection) { $c = new $collection([1, 2, 3, 4, 5, 2, 5, 'foo' => 'bar']); @@ -3965,9 +3544,7 @@ public function testSearchReturnsIndexOfFirstFoundItem($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSearchInStrictMode($collection) { $c = new $collection([false, 0, 1, [], '']); @@ -3980,9 +3557,7 @@ public function testSearchInStrictMode($collection) $this->assertEquals(4, $c->search('', true)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSearchReturnsFalseWhenItemIsNotFound($collection) { $c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']); @@ -3997,9 +3572,7 @@ public function testSearchReturnsFalseWhenItemIsNotFound($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeys($collection) { $c = new $collection(['name' => 'taylor', 'framework' => 'laravel']); @@ -4009,9 +3582,7 @@ public function testKeys($collection) $this->assertEquals([0, 1], $c->keys()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPaginate($collection) { $c = new $collection(['one', 'two', 'three', 'four']); @@ -4087,9 +3658,7 @@ public function testPushWithMultipleItems() $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testZip($collection) { $c = new $collection([1, 2, 3]); @@ -4118,9 +3687,7 @@ public function testZip($collection) $this->assertEquals([3, 6, null], $c->get(2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPadPadsArrayWithValue($collection) { $c = new $collection([1, 2, 3]); @@ -4140,9 +3707,7 @@ public function testPadPadsArrayWithValue($collection) $this->assertEquals([1, 2, 3, 4, 5], $c->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGettingMaxItemsFromCollection($collection) { $c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]); @@ -4163,9 +3728,7 @@ public function testGettingMaxItemsFromCollection($collection) $this->assertNull($c->max()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGettingMinItemsFromCollection($collection) { $c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]); @@ -4196,9 +3759,7 @@ public function testGettingMinItemsFromCollection($collection) $this->assertNull($c->min()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testOnly($collection) { $data = new $collection(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']); @@ -4213,9 +3774,7 @@ public function testOnly($collection) $this->assertEquals(['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], $data->only(collect(['first', 'email']))->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGettingAvgItemsFromCollection($collection) { $c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]); @@ -4257,9 +3816,7 @@ public function testGettingAvgItemsFromCollection($collection) $this->assertEquals(3, $c->avg('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testJsonSerialize($collection) { $c = new $collection([ @@ -4279,9 +3836,7 @@ public function testJsonSerialize($collection) ], $c->jsonSerialize()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCombineWithArray($collection) { $c = new $collection([1, 2, 3]); @@ -4322,9 +3877,7 @@ public function testCombineWithArray($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCombineWithCollection($collection) { $expected = [ @@ -4340,9 +3893,7 @@ public function testCombineWithCollection($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConcatWithArray($collection) { $expected = [ @@ -4368,9 +3919,7 @@ public function testConcatWithArray($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConcatWithCollection($collection) { $expected = [ @@ -4398,9 +3947,7 @@ public function testConcatWithCollection($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDump($collection) { $log = new Collection; @@ -4416,9 +3963,7 @@ public function testDump($collection) VarDumper::setHandler(null); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduce($collection) { $data = new $collection([1, 2, 3]); @@ -4435,9 +3980,7 @@ public function testReduce($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduceSpread($collection) { $data = new $collection([-1, 0, 1, 2, 3, 4, 5]); @@ -4455,9 +3998,7 @@ public function testReduceSpread($collection) $this->assertEquals(-1, $min); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($collection) { $data = new $collection([1]); @@ -4469,9 +4010,7 @@ public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($ }, null); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($collection) { $this->expectException(InvalidArgumentException::class); @@ -4480,9 +4019,7 @@ public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($ $data->random(4); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipe($collection) { $data = new $collection([1, 2, 3]); @@ -4492,9 +4029,7 @@ public function testPipe($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipeInto($collection) { $data = new $collection([ @@ -4506,9 +4041,7 @@ public function testPipeInto($collection) $this->assertSame($data, $instance->value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipeThrough($collection) { $data = new $collection([1, 2, 3]); @@ -4525,9 +4058,7 @@ function ($data) { $this->assertEquals(15, $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianValueWithArrayCollection($collection) { $data = new $collection([1, 2, 2, 4]); @@ -4535,9 +4066,7 @@ public function testMedianValueWithArrayCollection($collection) $this->assertEquals(2, $data->median()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianValueByKey($collection) { $data = new $collection([ @@ -4549,9 +4078,7 @@ public function testMedianValueByKey($collection) $this->assertEquals(2, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianOnCollectionWithNull($collection) { $data = new $collection([ @@ -4563,9 +4090,7 @@ public function testMedianOnCollectionWithNull($collection) $this->assertEquals(2, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEvenMedianCollection($collection) { $data = new $collection([ @@ -4575,9 +4100,7 @@ public function testEvenMedianCollection($collection) $this->assertEquals(1.5, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianOutOfOrderCollection($collection) { $data = new $collection([ @@ -4588,27 +4111,21 @@ public function testMedianOutOfOrderCollection($collection) $this->assertEquals(3, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianOnEmptyCollectionReturnsNull($collection) { $data = new $collection; $this->assertNull($data->median()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testModeOnNullCollection($collection) { $data = new $collection; $this->assertNull($data->mode()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMode($collection) { $data = new $collection([1, 2, 3, 4, 4, 5]); @@ -4616,9 +4133,7 @@ public function testMode($collection) $this->assertEquals([4], $data->mode()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testModeValueByKey($collection) { $data = new $collection([ @@ -4637,108 +4152,84 @@ public function testModeValueByKey($collection) $this->assertEquals($data2->mode('foo'), $data->mode('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWithMultipleModeValues($collection) { $data = new $collection([1, 2, 2, 1]); $this->assertEquals([1, 2], $data->mode()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceOffset($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([4, 5, 6, 7, 8], $data->slice(3)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceNegativeOffset($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([6, 7, 8], $data->slice(-3)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceOffsetAndLength($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([4, 5, 6], $data->slice(3, 3)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceOffsetAndNegativeLength($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([4, 5, 6, 7], $data->slice(3, -1)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceNegativeOffsetAndLength($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([4, 5, 6], $data->slice(-5, 3)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSliceNegativeOffsetAndNegativeLength($collection) { $data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]); $this->assertEquals([3, 4, 5, 6], $data->slice(-6, -2)->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionFromTraversable($collection) { $data = new $collection(new ArrayObject([1, 2, 3])); $this->assertEquals([1, 2, 3], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionFromTraversableWithKeys($collection) { $data = new $collection(new ArrayObject(['foo' => 1, 'bar' => 2, 'baz' => 3])); $this->assertEquals(['foo' => 1, 'bar' => 2, 'baz' => 3], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionFromEnum($collection) { $data = new $collection(TestEnum::A); $this->assertEquals([TestEnum::A], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionFromBackedEnum($collection) { $data = new $collection(TestBackedEnum::A); $this->assertEquals([TestBackedEnum::A], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionWithADivisibleCount($collection) { $data = new $collection(['a', 'b', 'c', 'd']); @@ -4769,9 +4260,7 @@ public function testSplitCollectionWithADivisibleCount($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionWithAnUndivisableCount($collection) { $data = new $collection(['a', 'b', 'c']); @@ -4788,9 +4277,7 @@ public function testSplitCollectionWithAnUndivisableCount($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionWithCountLessThenDivisor($collection) { $data = new $collection(['a']); @@ -4807,9 +4294,7 @@ public function testSplitCollectionWithCountLessThenDivisor($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionIntoThreeWithCountOfFour($collection) { $data = new $collection(['a', 'b', 'c', 'd']); @@ -4827,9 +4312,7 @@ public function testSplitCollectionIntoThreeWithCountOfFour($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionIntoThreeWithCountOfFive($collection) { $data = new $collection(['a', 'b', 'c', 'd', 'e']); @@ -4847,9 +4330,7 @@ public function testSplitCollectionIntoThreeWithCountOfFive($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionIntoSixWithCountOfTen($collection) { $data = new $collection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']); @@ -4870,9 +4351,7 @@ public function testSplitCollectionIntoSixWithCountOfTen($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitEmptyCollection($collection) { $data = new $collection; @@ -4889,9 +4368,7 @@ public function testSplitEmptyCollection($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderCollectionGroupBy($collection) { $data = new $collection([ @@ -4912,9 +4389,7 @@ public function testHigherOrderCollectionGroupBy($collection) ], $data->groupBy->uppercase()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderCollectionMap($collection) { $person1 = (object) ['name' => 'Taylor']; @@ -4929,9 +4404,7 @@ public function testHigherOrderCollectionMap($collection) $this->assertEquals(['TAYLOR', 'TAYLOR'], $data->each->uppercase()->map->name->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderCollectionMapFromArrays($collection) { $person1 = ['name' => 'Taylor']; @@ -4946,9 +4419,7 @@ public function testHigherOrderCollectionMapFromArrays($collection) $this->assertEquals(['TAYLOR', 'TAYLOR'], $data->each->uppercase()->map->name->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartition($collection) { $data = new $collection(range(1, 10)); @@ -4961,9 +4432,7 @@ public function testPartition($collection) $this->assertEquals([6, 7, 8, 9, 10], $secondPartition->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionCallbackWithKey($collection) { $data = new $collection(['zero', 'one', 'two', 'three']); @@ -4976,9 +4445,7 @@ public function testPartitionCallbackWithKey($collection) $this->assertEquals(['one', 'three'], $odd->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionByKey($collection) { $courses = new $collection([ @@ -4991,9 +4458,7 @@ public function testPartitionByKey($collection) $this->assertSame([['free' => false, 'title' => 'Premium']], $premium->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionWithOperators($collection) { $data = new $collection([ @@ -5028,9 +4493,7 @@ public function testPartitionWithOperators($collection) ], $minors->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionPreservesKeys($collection) { $courses = new $collection([ @@ -5043,9 +4506,7 @@ public function testPartitionPreservesKeys($collection) $this->assertSame(['b' => ['free' => false]], $premium->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionEmptyCollection($collection) { $data = new $collection; @@ -5055,9 +4516,7 @@ public function testPartitionEmptyCollection($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderPartition($collection) { $courses = new $collection([ @@ -5071,9 +4530,7 @@ public function testHigherOrderPartition($collection) $this->assertSame(['b' => ['free' => false]], $premium->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTap($collection) { $data = new $collection([1, 2, 3]); @@ -5090,9 +4547,7 @@ public function testTap($collection) $this->assertSame([1, 2, 3], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhen($collection) { $data = new $collection(['michael', 'tom']); @@ -5112,9 +4567,7 @@ public function testWhen($collection) $this->assertSame(['michael', 'tom'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5128,9 +4581,7 @@ public function testWhenDefault($collection) $this->assertSame(['michael', 'tom', 'taylor'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenEmpty($collection) { $data = new $collection(['michael', 'tom']); @@ -5150,9 +4601,7 @@ public function testWhenEmpty($collection) $this->assertSame(['adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5166,9 +4615,7 @@ public function testWhenEmptyDefault($collection) $this->assertSame(['michael', 'tom', 'taylor'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenNotEmpty($collection) { $data = new $collection(['michael', 'tom']); @@ -5188,9 +4635,7 @@ public function testWhenNotEmpty($collection) $this->assertSame([], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenNotEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5204,9 +4649,7 @@ public function testWhenNotEmptyDefault($collection) $this->assertSame(['michael', 'tom', 'adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderWhenAndUnless($collection) { $data = new $collection(['michael', 'tom']); @@ -5228,9 +4671,7 @@ public function testHigherOrderWhenAndUnless($collection) $this->assertSame(['michael', 'tom', 'chris', 'adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderWhenAndUnlessWithProxy($collection) { $data = new $collection(['michael', 'tom']); @@ -5252,9 +4693,7 @@ public function testHigherOrderWhenAndUnlessWithProxy($collection) $this->assertSame(['michael', 'tom', 'chris', 'adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnless($collection) { $data = new $collection(['michael', 'tom']); @@ -5274,9 +4713,7 @@ public function testUnless($collection) $this->assertSame(['michael', 'tom'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5290,9 +4727,7 @@ public function testUnlessDefault($collection) $this->assertSame(['michael', 'tom', 'taylor'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessEmpty($collection) { $data = new $collection(['michael', 'tom']); @@ -5312,9 +4747,7 @@ public function testUnlessEmpty($collection) $this->assertSame([], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5328,9 +4761,7 @@ public function testUnlessEmptyDefault($collection) $this->assertSame(['michael', 'tom', 'adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessNotEmpty($collection) { $data = new $collection(['michael', 'tom']); @@ -5350,9 +4781,7 @@ public function testUnlessNotEmpty($collection) $this->assertSame(['adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessNotEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5366,9 +4795,7 @@ public function testUnlessNotEmptyDefault($collection) $this->assertSame(['michael', 'tom', 'taylor'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHasReturnsValidResults($collection) { $data = new $collection(['foo' => 'one', 'bar' => 'two', 1 => 'three']); @@ -5390,9 +4817,7 @@ public function testPutAddsItemToCollection() $this->assertSame(['foo' => 3, 'bar' => ['nested' => 'two']], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collection) { $data = new $collection; @@ -5401,27 +4826,21 @@ public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collecti $data->foo; } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetWithNullReturnsNull($collection) { $data = new $collection([1, 2, 3]); $this->assertNull($data->get(null)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetWithDefaultValue($collection) { $data = new $collection(['name' => 'taylor', 'framework' => 'laravel']); $this->assertEquals('34', $data->get('age', 34)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetWithCallbackAsDefaultValue($collection) { $data = new $collection(['name' => 'taylor', 'framework' => 'laravel']); @@ -5431,9 +4850,7 @@ public function testGetWithCallbackAsDefaultValue($collection) $this->assertEquals('taylor@example.com', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNull($collection) { $data = new $collection([ @@ -5451,9 +4868,7 @@ public function testWhereNull($collection) $this->assertSame([], $data->whereNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNullWithoutKey($collection) { $collection = new $collection([1, null, 3, 'null', false, true]); @@ -5462,9 +4877,7 @@ public function testWhereNullWithoutKey($collection) ], $collection->whereNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotNull($collection) { $data = new $collection($originalData = [ @@ -5485,9 +4898,7 @@ public function testWhereNotNull($collection) $this->assertSame($originalData, $data->whereNotNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotNullWithoutKey($collection) { $data = new $collection([1, null, 3, 'null', false, true]); @@ -5501,9 +4912,7 @@ public function testWhereNotNullWithoutKey($collection) ], $data->whereNotNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollect($collection) { $data = $collection::make([ @@ -5521,9 +4930,7 @@ public function testCollect($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUndot($collection) { $data = $collection::make([ @@ -5557,9 +4964,7 @@ public function testUndot($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDot($collection) { $data = $collection::make([ @@ -5593,9 +4998,7 @@ public function testDot($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForScalar($collection) { $data = $collection::make([1, 2, 3]); @@ -5606,9 +5009,7 @@ public function testEnsureForScalar($collection) $data->ensure('int'); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForObjects($collection) { $data = $collection::make([new stdClass, new stdClass, new stdClass]); @@ -5619,9 +5020,7 @@ public function testEnsureForObjects($collection) $data->ensure(stdClass::class); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForInheritance($collection) { $data = $collection::make([new \Error, new \Error]); @@ -5632,9 +5031,7 @@ public function testEnsureForInheritance($collection) $data->ensure(\Throwable::class); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPercentageWithFlatCollection($collection) { $collection = new $collection([1, 1, 2, 2, 2, 3]); @@ -5645,9 +5042,7 @@ public function testPercentageWithFlatCollection($collection) $this->assertSame(0.0, $collection->percentage(fn ($value) => $value === 5)); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPercentageWithNestedCollection($collection) { $collection = new $collection([ @@ -5663,9 +5058,7 @@ public function testPercentageWithNestedCollection($collection) $this->assertSame(0.0, $collection->percentage(fn ($value) => $value['foo'] === 'test')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHighOrderPercentage($collection) { $collection = new $collection([ @@ -5678,9 +5071,7 @@ public function testHighOrderPercentage($collection) $this->assertSame(75.00, $collection->percentage->active); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPercentageReturnsNullForEmptyCollections($collection) { $collection = new $collection([]); diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index b91fa854e43b..c0fddec3ec76 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -15,6 +15,7 @@ use IteratorAggregate; use LogicException; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ReflectionClass; use RuntimeException; @@ -1045,9 +1046,7 @@ public static function providesPregReplaceArrayData() ]; } - /** - * @dataProvider providesPregReplaceArrayData - */ + #[DataProvider('providesPregReplaceArrayData')] public function testPregReplaceArray($pattern, $replacements, $subject, $expectedOutput) { $this->assertSame( diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 35c975145fd8..a2f6e47457de 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Str; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\UuidInterface; use ReflectionClass; @@ -287,17 +288,13 @@ public function testStrAfterLast() $this->assertSame('foo', Str::afterLast('----foo', '---')); } - /** - * @dataProvider strContainsProvider - */ + #[DataProvider('strContainsProvider')] public function testStrContains($haystack, $needles, $expected, $ignoreCase = false) { $this->assertEquals($expected, Str::contains($haystack, $needles, $ignoreCase)); } - /** - * @dataProvider strContainsAllProvider - */ + #[DataProvider('strContainsAllProvider')] public function testStrContainsAll($haystack, $needles, $expected, $ignoreCase = false) { $this->assertEquals($expected, Str::containsAll($haystack, $needles, $ignoreCase)); @@ -435,17 +432,13 @@ public function testIsUrl() $this->assertFalse(Str::isUrl('invalid url')); } - /** - * @dataProvider validUuidList - */ + #[DataProvider('validUuidList')] public function testIsUuidWithValidUuid($uuid) { $this->assertTrue(Str::isUuid($uuid)); } - /** - * @dataProvider invalidUuidList - */ + #[DataProvider('invalidUuidList')] public function testIsUuidWithInvalidUuid($uuid) { $this->assertFalse(Str::isUuid($uuid)); @@ -1081,9 +1074,7 @@ public function testRepeat() $this->assertSame('', Str::repeat('', 5)); } - /** - * @dataProvider specialCharacterProvider - */ + #[DataProvider('specialCharacterProvider')] public function testTransliterate(string $value, string $expected): void { $this->assertSame($expected, Str::transliterate($value)); @@ -1109,9 +1100,7 @@ public function testTransliterateOverrideUnknown(): void $this->assertSame('Hello', Str::transliterate('🎂', 'Hello')); } - /** - * @dataProvider specialCharacterProvider - */ + #[DataProvider('specialCharacterProvider')] public function testTransliterateStrict(string $value, string $expected): void { $this->assertSame($expected, Str::transliterate($value, '?', true)); diff --git a/tests/Testing/Concerns/TestDatabasesTest.php b/tests/Testing/Concerns/TestDatabasesTest.php index 5e6e9e19956f..e5e1f110d884 100644 --- a/tests/Testing/Concerns/TestDatabasesTest.php +++ b/tests/Testing/Concerns/TestDatabasesTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Testing\Concerns\TestDatabases; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ReflectionMethod; @@ -46,9 +47,7 @@ public function testSwitchToDatabaseWithoutUrl() $this->switchToDatabase('my_database_test_1'); } - /** - * @dataProvider databaseUrls - */ + #[DataProvider('databaseUrls')] public function testSwitchToDatabaseWithUrl($testDatabase, $url, $testUrl) { DB::shouldReceive('purge')->once(); diff --git a/tests/Testing/ParallelTestingTest.php b/tests/Testing/ParallelTestingTest.php index 521cf16cfb15..ba3853d67ddc 100644 --- a/tests/Testing/ParallelTestingTest.php +++ b/tests/Testing/ParallelTestingTest.php @@ -4,6 +4,7 @@ use Illuminate\Container\Container; use Illuminate\Testing\ParallelTesting; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ParallelTestingTest extends TestCase @@ -17,9 +18,7 @@ protected function setUp(): void $_SERVER['LARAVEL_PARALLEL_TESTING'] = 1; } - /** - * @dataProvider callbacks - */ + #[DataProvider('callbacks')] public function testCallbacks($callback) { $parallelTesting = new ParallelTesting(Container::getInstance()); diff --git a/tests/Translation/TranslationMessageSelectorTest.php b/tests/Translation/TranslationMessageSelectorTest.php index cd53a7de17f0..5aae067445a9 100755 --- a/tests/Translation/TranslationMessageSelectorTest.php +++ b/tests/Translation/TranslationMessageSelectorTest.php @@ -3,13 +3,12 @@ namespace Illuminate\Tests\Translation; use Illuminate\Translation\MessageSelector; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class TranslationMessageSelectorTest extends TestCase { - /** - * @dataProvider chooseTestData - */ + #[DataProvider('chooseTestData')] public function testChoose($expected, $id, $number) { $selector = new MessageSelector; diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 51133741082d..a57688f81baf 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -31,6 +31,8 @@ use InvalidArgumentException; use Mockery as m; use Mockery\MockInterface; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -1663,7 +1665,7 @@ public function testProhibits() $this->assertFalse($v->messages()->has('foo.1.email')); } - /** @dataProvider prohibitedRulesData */ + #[DataProvider('prohibitedRulesData')] public function testProhibitedRulesAreConsistent($rules, $data, $result) { $trans = $this->getIlluminateArrayTranslator(); @@ -3226,9 +3228,8 @@ public function testValidateMax() * @param mixed $input * @param mixed $allowed * @param bool $passes - * - * @dataProvider multipleOfDataProvider */ + #[DataProvider('multipleOfDataProvider')] public function testValidateMultipleOf($input, $allowed, $passes) { $trans = $this->getIlluminateArrayTranslator(); @@ -3964,9 +3965,7 @@ public function testValidateUrlWithProtocols() $this->assertFalse($v->passes()); } - /** - * @dataProvider validUrls - */ + #[DataProvider('validUrls')] public function testValidateUrlWithValidUrls($validUrl) { $trans = $this->getIlluminateArrayTranslator(); @@ -3974,9 +3973,7 @@ public function testValidateUrlWithValidUrls($validUrl) $this->assertTrue($v->passes()); } - /** - * @dataProvider invalidUrls - */ + #[DataProvider('invalidUrls')] public function testValidateUrlWithInvalidUrls($invalidUrl) { $trans = $this->getIlluminateArrayTranslator(); @@ -4248,9 +4245,7 @@ public static function invalidUrls() ]; } - /** - * @dataProvider activeUrlDataProvider - */ + #[DataProvider('activeUrlDataProvider')] public function testValidateActiveUrl($data, $outcome) { $trans = $this->getIlluminateArrayTranslator(); @@ -4556,9 +4551,7 @@ public function testValidateMimeEnforcesPhpCheck() $this->assertTrue($v->passes()); } - /** - * @requires extension fileinfo - */ + #[RequiresPhpExtension('fileinfo')] public function testValidateFile() { $trans = $this->getIlluminateArrayTranslator(); @@ -7807,9 +7800,7 @@ public function testMultiplePassesCalls() $this->assertFalse($v->passes()); } - /** - * @dataProvider validUuidList - */ + #[DataProvider('validUuidList')] public function testValidateWithValidUuid($uuid) { $trans = $this->getIlluminateArrayTranslator(); @@ -7817,9 +7808,7 @@ public function testValidateWithValidUuid($uuid) $this->assertTrue($v->passes()); } - /** - * @dataProvider invalidUuidList - */ + #[DataProvider('invalidUuidList')] public function testValidateWithInvalidUuid($uuid) { $trans = $this->getIlluminateArrayTranslator(); @@ -8097,9 +8086,7 @@ public static function providesPassingExcludeIfData() ]; } - /** - * @dataProvider providesPassingExcludeIfData - */ + #[DataProvider('providesPassingExcludeIfData')] public function testExcludeIf($rules, $data, $expectedValidatedData) { $validator = new Validator( @@ -8212,9 +8199,7 @@ public static function providesFailingExcludeIfData() ]; } - /** - * @dataProvider providesFailingExcludeIfData - */ + #[DataProvider('providesFailingExcludeIfData')] public function testExcludeIfWhenValidationFails($rules, $data, $expectedMessages) { $validator = new Validator( @@ -8254,9 +8239,7 @@ public static function providesPassingExcludeData() ]; } - /** - * @dataProvider providesPassingExcludeData - */ + #[DataProvider('providesPassingExcludeData')] public function testExclude($rules, $data, $expectedValidatedData) { $validator = new Validator( @@ -8824,7 +8807,7 @@ public function testItTrimsSpaceFromParameters() ], $validator->messages()->keys()); } - /** @dataProvider outsideRangeExponents */ + #[DataProvider('outsideRangeExponents')] public function testItLimitsLengthOfScientificNotationExponent($value) { $trans = $this->getIlluminateArrayTranslator(); @@ -8848,7 +8831,7 @@ public static function outsideRangeExponents() ]; } - /** @dataProvider withinRangeExponents */ + #[DataProvider('withinRangeExponents')] public function testItAllowsScientificNotationWithinRange($value, $rule) { $trans = $this->getIlluminateArrayTranslator(); diff --git a/tests/View/Blade/BladeEchoHandlerTest.php b/tests/View/Blade/BladeEchoHandlerTest.php index 16ac1aaea354..55e48b005c6a 100644 --- a/tests/View/Blade/BladeEchoHandlerTest.php +++ b/tests/View/Blade/BladeEchoHandlerTest.php @@ -5,6 +5,7 @@ use Exception; use Illuminate\Support\Fluent; use Illuminate\Support\Str; +use PHPUnit\Framework\Attributes\DataProvider; class BladeEchoHandlerTest extends AbstractBladeTestCase { @@ -49,9 +50,7 @@ public function testWhitespaceIsPreservedCorrectly() ); } - /** - * @dataProvider handlerLogicDataProvider - */ + #[DataProvider('handlerLogicDataProvider')] public function testHandlerLogicWorksCorrectly($blade) { $this->expectException(Exception::class); @@ -80,9 +79,7 @@ public static function handlerLogicDataProvider() ]; } - /** - * @dataProvider nonStringableDataProvider - */ + #[DataProvider('nonStringableDataProvider')] public function testHandlerWorksWithNonStringables($blade, $expectedOutput) { app()->singleton('blade.compiler', function () { diff --git a/tests/View/Blade/BladeForeachStatementsTest.php b/tests/View/Blade/BladeForeachStatementsTest.php index 1b4ce8ada8f3..80ba9736767b 100644 --- a/tests/View/Blade/BladeForeachStatementsTest.php +++ b/tests/View/Blade/BladeForeachStatementsTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\View\Blade; use Illuminate\Contracts\View\ViewCompilationException; +use PHPUnit\Framework\Attributes\DataProvider; class BladeForeachStatementsTest extends AbstractBladeTestCase { @@ -94,9 +95,7 @@ public function testLoopContentHolderIsExtractedFromForeachStatements() $this->assertEquals($expected, $this->compiler->compileString($string)); } - /** - * @dataProvider invalidForeachStatementsDataProvider - */ + #[DataProvider('invalidForeachStatementsDataProvider')] public function testForeachStatementsThrowHumanizedMessageWhenInvalidStatement($initialStatement) { $this->expectException(ViewCompilationException::class); diff --git a/tests/View/Blade/BladeForelseStatementsTest.php b/tests/View/Blade/BladeForelseStatementsTest.php index 3fb033b4e964..67e0cd38f355 100644 --- a/tests/View/Blade/BladeForelseStatementsTest.php +++ b/tests/View/Blade/BladeForelseStatementsTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\View\Blade; use Illuminate\Contracts\View\ViewCompilationException; +use PHPUnit\Framework\Attributes\DataProvider; class BladeForelseStatementsTest extends AbstractBladeTestCase { @@ -80,9 +81,7 @@ public function testNestedForelseStatementsAreCompiled() $this->assertEquals($expected, $this->compiler->compileString($string)); } - /** - * @dataProvider invalidForelseStatementsDataProvider - */ + #[DataProvider('invalidForelseStatementsDataProvider')] public function testForelseStatementsThrowHumanizedMessageWhenInvalidStatement($initialStatement) { $this->expectException(ViewCompilationException::class); diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php index b7ef8a4ea75c..c3955945e8ff 100644 --- a/tests/View/ViewBladeCompilerTest.php +++ b/tests/View/ViewBladeCompilerTest.php @@ -6,6 +6,7 @@ use Illuminate\View\Compilers\BladeCompiler; use InvalidArgumentException; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ViewBladeCompilerTest extends TestCase @@ -122,11 +123,10 @@ public function testRawTagsCanBeSetToLegacyValues() } /** - * @dataProvider appendViewPathDataProvider - * * @param string $content * @param string $compiled */ + #[DataProvider('appendViewPathDataProvider')] public function testIncludePathToTemplate($content, $compiled) { $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); diff --git a/tests/View/ViewFileViewFinderTest.php b/tests/View/ViewFileViewFinderTest.php index 80d0ad3f90f8..92f8e954bf9d 100755 --- a/tests/View/ViewFileViewFinderTest.php +++ b/tests/View/ViewFileViewFinderTest.php @@ -6,6 +6,7 @@ use Illuminate\View\FileViewFinder; use InvalidArgumentException; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ViewFileViewFinderTest extends TestCase @@ -153,9 +154,7 @@ public static function pathsProvider() ]; } - /** - * @dataProvider pathsProvider - */ + #[DataProvider('pathsProvider')] public function testNormalizedPaths($originalPath, $exceptedPath) { $finder = $this->getFinder(); From 800e26488114bf9f944481cb3d18ebaec8898264 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 27 Oct 2023 05:35:40 +1100 Subject: [PATCH 58/99] Per-second rate limiting (#48498) --- .../Cache/RateLimiting/GlobalLimit.php | 6 +- src/Illuminate/Cache/RateLimiting/Limit.php | 31 ++-- .../Foundation/Exceptions/Handler.php | 2 +- .../Queue/Middleware/RateLimited.php | 4 +- .../Queue/Middleware/RateLimitedWithRedis.php | 8 +- .../Queue/Middleware/ThrottlesExceptions.php | 12 +- .../ThrottlesExceptionsWithRedis.php | 2 +- .../Routing/Middleware/ThrottleRequests.php | 6 +- .../Middleware/ThrottleRequestsWithRedis.php | 8 +- tests/Cache/LimitTest.php | 41 +++++ .../Integration/Http/ThrottleRequestsTest.php | 126 +++++++++++++ tests/Integration/Queue/RateLimitedTest.php | 92 ++++++++++ .../Queue/ThrottlesExceptionsTest.php | 168 +++++++++++++++++- .../ThrottlesExceptionsWithRedisTest.php | 4 +- 14 files changed, 472 insertions(+), 38 deletions(-) create mode 100644 tests/Cache/LimitTest.php diff --git a/src/Illuminate/Cache/RateLimiting/GlobalLimit.php b/src/Illuminate/Cache/RateLimiting/GlobalLimit.php index 4f084eb1095f..965352ba78d9 100644 --- a/src/Illuminate/Cache/RateLimiting/GlobalLimit.php +++ b/src/Illuminate/Cache/RateLimiting/GlobalLimit.php @@ -8,11 +8,11 @@ class GlobalLimit extends Limit * Create a new limit instance. * * @param int $maxAttempts - * @param int $decayMinutes + * @param int $decaySeconds * @return void */ - public function __construct(int $maxAttempts, int $decayMinutes = 1) + public function __construct(int $maxAttempts, int $decaySeconds = 60) { - parent::__construct('', $maxAttempts, $decayMinutes); + parent::__construct('', $maxAttempts, $decaySeconds); } } diff --git a/src/Illuminate/Cache/RateLimiting/Limit.php b/src/Illuminate/Cache/RateLimiting/Limit.php index 9bf058bb0728..00dc0c898b1e 100644 --- a/src/Illuminate/Cache/RateLimiting/Limit.php +++ b/src/Illuminate/Cache/RateLimiting/Limit.php @@ -12,18 +12,18 @@ class Limit public $key; /** - * The maximum number of attempts allowed within the given number of minutes. + * The maximum number of attempts allowed within the given number of seconds. * * @var int */ public $maxAttempts; /** - * The number of minutes until the rate limit is reset. + * The number of seconds until the rate limit is reset. * * @var int */ - public $decayMinutes; + public $decaySeconds; /** * The response generator callback. @@ -37,14 +37,25 @@ class Limit * * @param mixed $key * @param int $maxAttempts - * @param int $decayMinutes + * @param int $decaySeconds * @return void */ - public function __construct($key = '', int $maxAttempts = 60, int $decayMinutes = 1) + public function __construct($key = '', int $maxAttempts = 60, int $decaySeconds = 60) { $this->key = $key; $this->maxAttempts = $maxAttempts; - $this->decayMinutes = $decayMinutes; + $this->decaySeconds = $decaySeconds; + } + + /** + * Create a new rate limit. + * + * @param int $maxAttempts + * @return static + */ + public static function perSecond($maxAttempts) + { + return new static('', $maxAttempts, 1); } /** @@ -55,7 +66,7 @@ public function __construct($key = '', int $maxAttempts = 60, int $decayMinutes */ public static function perMinute($maxAttempts) { - return new static('', $maxAttempts); + return new static('', $maxAttempts, 60); } /** @@ -67,7 +78,7 @@ public static function perMinute($maxAttempts) */ public static function perMinutes($decayMinutes, $maxAttempts) { - return new static('', $maxAttempts, $decayMinutes); + return new static('', $maxAttempts, 60 * $decayMinutes); } /** @@ -79,7 +90,7 @@ public static function perMinutes($decayMinutes, $maxAttempts) */ public static function perHour($maxAttempts, $decayHours = 1) { - return new static('', $maxAttempts, 60 * $decayHours); + return new static('', $maxAttempts, 60 * 60 * $decayHours); } /** @@ -91,7 +102,7 @@ public static function perHour($maxAttempts, $decayHours = 1) */ public static function perDay($maxAttempts, $decayDays = 1) { - return new static('', $maxAttempts, 60 * 24 * $decayDays); + return new static('', $maxAttempts, 60 * 60 * 24 * $decayDays); } /** diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 498be561b7dc..c877e772df3c 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -360,7 +360,7 @@ protected function shouldntReport(Throwable $e) with($throttle->key ?: 'illuminate:foundation:exceptions:'.$e::class, fn ($key) => $this->hashThrottleKeys ? md5($key) : $key), $throttle->maxAttempts, fn () => true, - 60 * $throttle->decayMinutes + $throttle->decaySeconds ); }), rescue: false, report: false); } diff --git a/src/Illuminate/Queue/Middleware/RateLimited.php b/src/Illuminate/Queue/Middleware/RateLimited.php index c8546338386e..4ebdc2677e93 100644 --- a/src/Illuminate/Queue/Middleware/RateLimited.php +++ b/src/Illuminate/Queue/Middleware/RateLimited.php @@ -69,7 +69,7 @@ public function handle($job, $next) return (object) [ 'key' => md5($this->limiterName.$limit->key), 'maxAttempts' => $limit->maxAttempts, - 'decayMinutes' => $limit->decayMinutes, + 'decaySeconds' => $limit->decaySeconds, ]; })->all() ); @@ -92,7 +92,7 @@ protected function handleJob($job, $next, array $limits) : false; } - $this->limiter->hit($limit->key, $limit->decayMinutes * 60); + $this->limiter->hit($limit->key, $limit->decaySeconds); } return $next($job); diff --git a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php index e919786f27c6..cbc809549333 100644 --- a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php +++ b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php @@ -49,7 +49,7 @@ public function __construct($limiterName) protected function handleJob($job, $next, array $limits) { foreach ($limits as $limit) { - if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) { + if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) { return $this->shouldRelease ? $job->release($this->getTimeUntilNextRetry($limit->key)) : false; @@ -64,13 +64,13 @@ protected function handleJob($job, $next, array $limits) * * @param string $key * @param int $maxAttempts - * @param int $decayMinutes + * @param int $decaySeconds * @return bool */ - protected function tooManyAttempts($key, $maxAttempts, $decayMinutes) + protected function tooManyAttempts($key, $maxAttempts, $decaySeconds) { $limiter = new DurationLimiter( - $this->redis, $key, $maxAttempts, $decayMinutes * 60 + $this->redis, $key, $maxAttempts, $decaySeconds ); return tap(! $limiter->acquire(), function () use ($key, $limiter) { diff --git a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php index 61ddf343e499..60ba1801c7d8 100644 --- a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php +++ b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php @@ -30,11 +30,11 @@ class ThrottlesExceptions protected $maxAttempts; /** - * The number of minutes until the maximum attempts are reset. + * The number of seconds until the maximum attempts are reset. * * @var int */ - protected $decayMinutes; + protected $decaySeconds; /** * The number of minutes to wait before retrying the job after an exception. @@ -68,13 +68,13 @@ class ThrottlesExceptions * Create a new middleware instance. * * @param int $maxAttempts - * @param int $decayMinutes + * @param int $decaySeconds * @return void */ - public function __construct($maxAttempts = 10, $decayMinutes = 10) + public function __construct($maxAttempts = 10, $decaySeconds = 600) { $this->maxAttempts = $maxAttempts; - $this->decayMinutes = $decayMinutes; + $this->decaySeconds = $decaySeconds; } /** @@ -101,7 +101,7 @@ public function handle($job, $next) throw $throwable; } - $this->limiter->hit($jobKey, $this->decayMinutes * 60); + $this->limiter->hit($jobKey, $this->decaySeconds); return $job->release($this->retryAfterMinutes * 60); } diff --git a/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php b/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php index 38790e353e2d..0dbc9443b4b0 100644 --- a/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php +++ b/src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php @@ -38,7 +38,7 @@ public function handle($job, $next) $this->redis = Container::getInstance()->make(Redis::class); $this->limiter = new DurationLimiter( - $this->redis, $this->getKey($job), $this->maxAttempts, $this->decayMinutes * 60 + $this->redis, $this->getKey($job), $this->maxAttempts, $this->decaySeconds ); if ($this->limiter->tooManyAttempts()) { diff --git a/src/Illuminate/Routing/Middleware/ThrottleRequests.php b/src/Illuminate/Routing/Middleware/ThrottleRequests.php index bf6cb92aefa2..227a7579c5ab 100644 --- a/src/Illuminate/Routing/Middleware/ThrottleRequests.php +++ b/src/Illuminate/Routing/Middleware/ThrottleRequests.php @@ -94,7 +94,7 @@ public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes (object) [ 'key' => $prefix.$this->resolveRequestSignature($request), 'maxAttempts' => $this->resolveMaxAttempts($request, $maxAttempts), - 'decayMinutes' => $decayMinutes, + 'decaySeconds' => 60 * $decayMinutes, 'responseCallback' => null, ], ] @@ -129,7 +129,7 @@ protected function handleRequestUsingNamedLimiter($request, Closure $next, $limi return (object) [ 'key' => self::$shouldHashKeys ? md5($limiterName.$limit->key) : $limiterName.':'.$limit->key, 'maxAttempts' => $limit->maxAttempts, - 'decayMinutes' => $limit->decayMinutes, + 'decaySeconds' => $limit->decaySeconds, 'responseCallback' => $limit->responseCallback, ]; })->all() @@ -153,7 +153,7 @@ protected function handleRequest($request, Closure $next, array $limits) throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); } - $this->limiter->hit($limit->key, $limit->decayMinutes * 60); + $this->limiter->hit($limit->key, $limit->decaySeconds); } $response = $next($request); diff --git a/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php b/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php index e818c69f14c7..20afd95dd9b8 100644 --- a/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php +++ b/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php @@ -57,7 +57,7 @@ public function __construct(RateLimiter $limiter, Redis $redis) protected function handleRequest($request, Closure $next, array $limits) { foreach ($limits as $limit) { - if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) { + if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) { throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); } } @@ -80,13 +80,13 @@ protected function handleRequest($request, Closure $next, array $limits) * * @param string $key * @param int $maxAttempts - * @param int $decayMinutes + * @param int $decaySeconds * @return mixed */ - protected function tooManyAttempts($key, $maxAttempts, $decayMinutes) + protected function tooManyAttempts($key, $maxAttempts, $decaySeconds) { $limiter = new DurationLimiter( - $this->getRedisConnection(), $key, $maxAttempts, $decayMinutes * 60 + $this->getRedisConnection(), $key, $maxAttempts, $decaySeconds ); return tap(! $limiter->acquire(), function () use ($key, $limiter) { diff --git a/tests/Cache/LimitTest.php b/tests/Cache/LimitTest.php new file mode 100644 index 000000000000..165ab7799001 --- /dev/null +++ b/tests/Cache/LimitTest.php @@ -0,0 +1,41 @@ +assertSame(1, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = Limit::perSecond(3); + $this->assertSame(1, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = Limit::perMinute(3); + $this->assertSame(60, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = Limit::perMinutes(2, 3); + $this->assertSame(120, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = Limit::perHour(3); + $this->assertSame(3600, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = Limit::perDay(3); + $this->assertSame(86400, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + + $limit = new GlobalLimit(3); + $this->assertSame(60, $limit->decaySeconds); + $this->assertSame(3, $limit->maxAttempts); + } +} diff --git a/tests/Integration/Http/ThrottleRequestsTest.php b/tests/Integration/Http/ThrottleRequestsTest.php index 6ddd2a0ef51e..d0f43260eaa2 100644 --- a/tests/Integration/Http/ThrottleRequestsTest.php +++ b/tests/Integration/Http/ThrottleRequestsTest.php @@ -4,6 +4,7 @@ use Illuminate\Cache\RateLimiter; use Illuminate\Cache\RateLimiting\GlobalLimit; +use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Container\Container; use Illuminate\Http\Exceptions\ThrottleRequestsException; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -116,4 +117,129 @@ public function testItCanGenerateDefinitionViaStaticMethod() $signature = (string) ThrottleRequests::with(prefix: 'foo'); $this->assertSame('Illuminate\Routing\Middleware\ThrottleRequests:60,1,foo', $signature); } + + public static function perMinuteThrottlingDataSet() + { + return [ + [ThrottleRequests::using('test')], + [ThrottleRequests::with(maxAttempts: 3, decayMinutes: 1)], + ['throttle:3,1'], + ]; + } + + /** @dataProvider perMinuteThrottlingDataSet */ + public function testItCanThrottlePerMinute(string $middleware) + { + $rateLimiter = Container::getInstance()->make(RateLimiter::class); + $rateLimiter->for('test', fn () => Limit::perMinute(3)); + Route::get('/', fn () => 'ok')->middleware($middleware); + + Carbon::setTestNow('2000-01-01 00:00:00.000'); + + // Make 3 requests, each a second apart, that should all be successful. + + for ($i = 0; $i < 3; $i++) { + match ($i) { + 0 => $this->assertSame('2000-01-01 00:00:00.000', now()->toDateTimeString('m')), + 1 => $this->assertSame('2000-01-01 00:00:01.000', now()->toDateTimeString('m')), + 2 => $this->assertSame('2000-01-01 00:00:02.000', now()->toDateTimeString('m')), + }; + + $response = $this->get('/'); + $response->assertOk(); + $response->assertContent('ok'); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 3 - ($i + 1)); + + Carbon::setTestNow(now()->addSecond()); + } + + // It is now 3 seconds past and we will make another request that + // should be rate limited. + + $this->assertSame('2000-01-01 00:00:03.000', now()->toDateTimeString('m')); + + $response = $this->get('/'); + $response->assertStatus(429); + $response->assertHeader('Retry-After', 57); + $response->assertHeader('X-RateLimit-Reset', now()->addSeconds(57)->timestamp); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 0); + + // We will now make it the very end of the minute, to check boundaries, + // and make another request that should be rate limited and tell us to + // try again in 1 second. + Carbon::setTestNow('2000-01-01 00:00:59.999'); + + $response = $this->get('/'); + $response->assertStatus(429); + $response->assertHeader('Retry-After', 1); + $response->assertHeader('X-RateLimit-Reset', now()->addSeconds(1)->timestamp); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 0); + + // We now tick over into the next second. We should now be able to make + // requests again. + Carbon::setTestNow('2000-01-01 00:01:00.000'); + + $response = $this->get('/'); + $response->assertOk(); + } + + public function testItCanThrottlePerSecond() + { + $rateLimiter = Container::getInstance()->make(RateLimiter::class); + $rateLimiter->for('test', fn () => Limit::perSecond(3)); + Route::get('/', fn () => 'ok')->middleware(ThrottleRequests::using('test')); + + Carbon::setTestNow('2000-01-01 00:00:00.000'); + + // Make 3 requests, each a 100ms apart, that should all be successful. + + for ($i = 0; $i < 3; $i++) { + match ($i) { + 0 => $this->assertSame('2000-01-01 00:00:00.000', now()->toDateTimeString('m')), + 1 => $this->assertSame('2000-01-01 00:00:00.100', now()->toDateTimeString('m')), + 2 => $this->assertSame('2000-01-01 00:00:00.200', now()->toDateTimeString('m')), + }; + + $response = $this->get('/'); + $response->assertOk(); + $response->assertContent('ok'); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 3 - ($i + 1)); + + Carbon::setTestNow(now()->addMilliseconds(100)); + } + + // It is now 300 milliseconds past and we will make another request + // that should be rate limited. + + $this->assertSame('2000-01-01 00:00:00.300', now()->toDateTimeString('m')); + + $response = $this->get('/'); + $response->assertStatus(429); + $response->assertHeader('Retry-After', 1); + $response->assertHeader('X-RateLimit-Reset', now()->addSecond()->timestamp); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 0); + + // We will now make it the very end of the minute, to check boundaries, + // and make another request that should be rate limited and tell us to + // try again in 1 second. + Carbon::setTestNow('2000-01-01 00:00:00.999'); + + $response = $this->get('/'); + $response->assertHeader('Retry-After', 1); + $response->assertHeader('X-RateLimit-Reset', now()->addSecond()->timestamp); + $response->assertHeader('X-RateLimit-Limit', 3); + $response->assertHeader('X-RateLimit-Remaining', 0); + + // We now tick over into the next second. We should now be able to make + // requests again. + Carbon::setTestNow('2000-01-01 00:00:01.000'); + + $response = $this->get('/'); + $response->assertOk(); + } } diff --git a/tests/Integration/Queue/RateLimitedTest.php b/tests/Integration/Queue/RateLimitedTest.php index 80fc594fcd91..c199a0096292 100644 --- a/tests/Integration/Queue/RateLimitedTest.php +++ b/tests/Integration/Queue/RateLimitedTest.php @@ -4,13 +4,17 @@ use Illuminate\Bus\Dispatcher; use Illuminate\Bus\Queueable; +use Illuminate\Cache\ArrayStore; use Illuminate\Cache\RateLimiter; use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Cache\Repository; +use Illuminate\Container\Container; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Queue\Job; use Illuminate\Queue\CallQueuedHandler; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\RateLimited; +use Illuminate\Support\Carbon; use Mockery as m; use Orchestra\Testbench\TestCase; @@ -188,6 +192,94 @@ protected function assertJobWasSkipped($class) $this->assertFalse($class::$handled); } + + public function testItCanLimitPerMinute() + { + Container::getInstance()->instance(RateLimiter::class, $limiter = new RateLimiter(new Repository(new ArrayStore))); + $limiter->for('test', fn () => Limit::perMinute(3)); + $jobFactory = fn () => new class + { + public $released = false; + + public function release() + { + $this->released = true; + } + }; + $next = fn ($job) => $job; + + $middleware = new RateLimited('test'); + + Carbon::setTestNow('2000-00-00 00:00:00.000'); + + for ($i = 0; $i < 3; $i++) { + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertFalse($job->released); + + Carbon::setTestNow(now()->addSeconds(1)); + } + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertNull($result); + $this->assertTrue($job->released); + + Carbon::setTestNow('2000-00-00 00:00:59.999'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertNull($result); + $this->assertTrue($job->released); + + Carbon::setTestNow('2000-00-00 00:01:00.000'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertFalse($job->released); + } + + public function testItCanLimitPerSecond() + { + Container::getInstance()->instance(RateLimiter::class, $limiter = new RateLimiter(new Repository(new ArrayStore))); + $limiter->for('test', fn () => Limit::perSecond(3)); + $jobFactory = fn () => new class + { + public $released = false; + + public function release() + { + $this->released = true; + } + }; + $next = fn ($job) => $job; + + $middleware = new RateLimited('test'); + + Carbon::setTestNow('2000-00-00 00:00:00.000'); + + for ($i = 0; $i < 3; $i++) { + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertFalse($job->released); + + Carbon::setTestNow(now()->addMilliseconds(100)); + } + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertNull($result); + $this->assertTrue($job->released); + + Carbon::setTestNow('2000-00-00 00:00:00.999'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertNull($result); + $this->assertTrue($job->released); + + Carbon::setTestNow('2000-00-00 00:00:01.000'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertFalse($job->released); + } } class RateLimitedTestJob diff --git a/tests/Integration/Queue/ThrottlesExceptionsTest.php b/tests/Integration/Queue/ThrottlesExceptionsTest.php index 6eff31a6aabd..1bb75e50619a 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsTest.php @@ -9,8 +9,10 @@ use Illuminate\Queue\CallQueuedHandler; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\ThrottlesExceptions; +use Illuminate\Support\Carbon; use Mockery as m; use Orchestra\Testbench\TestCase; +use RuntimeException; class ThrottlesExceptionsTest extends TestCase { @@ -105,6 +107,168 @@ protected function assertJobRanSuccessfully($class) $this->assertTrue($class::$handled); } + + public function testItCanLimitPerMinute() + { + $jobFactory = fn () => new class + { + public $released = false; + + public $handled = false; + + public function release() + { + $this->released = true; + + return $this; + } + }; + $next = function ($job) { + $job->handled = true; + + throw new RuntimeException('Whoops!'); + }; + + $middleware = new ThrottlesExceptions(3, 60); + + Carbon::setTestNow('2000-00-00 00:00:00.000'); + + for ($i = 0; $i < 3; $i++) { + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + + Carbon::setTestNow(now()->addSeconds(1)); + } + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:00:59.999'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:01:00.000'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + } + + public function testItCanLimitPerSecond() + { + $jobFactory = fn () => new class + { + public $released = false; + + public $handled = false; + + public function release() + { + $this->released = true; + + return $this; + } + }; + $next = function ($job) { + $job->handled = true; + + throw new RuntimeException('Whoops!'); + }; + + $middleware = new ThrottlesExceptions(3, 1); + + Carbon::setTestNow('2000-00-00 00:00:00.000'); + + for ($i = 0; $i < 3; $i++) { + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + + Carbon::setTestNow(now()->addMilliseconds(100)); + } + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:00:00.999'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:00:01.000'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + } + + public function testLimitingWithDefaultValues() + { + $jobFactory = fn () => new class + { + public $released = false; + + public $handled = false; + + public function release() + { + $this->released = true; + + return $this; + } + }; + $next = function ($job) { + $job->handled = true; + + throw new RuntimeException('Whoops!'); + }; + + $middleware = new ThrottlesExceptions(); + + Carbon::setTestNow('2000-00-00 00:00:00.000'); + + for ($i = 0; $i < 10; $i++) { + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + + Carbon::setTestNow(now()->addSeconds(1)); + } + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:09:59.999'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertFalse($job->handled); + + Carbon::setTestNow('2000-00-00 00:10:00.000'); + + $result = $middleware->handle($job = $jobFactory(), $next); + $this->assertSame($job, $result); + $this->assertTrue($job->released); + $this->assertTrue($job->handled); + } } class CircuitBreakerTestJob @@ -122,7 +286,7 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptions(2, 10))->by('test')]; + return [(new ThrottlesExceptions(2, 10 * 60))->by('test')]; } } @@ -139,6 +303,6 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptions(2, 10))->by('test')]; + return [(new ThrottlesExceptions(2, 10 * 60))->by('test')]; } } diff --git a/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php b/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php index 358d0a8b2513..6cb102c48ffe 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php @@ -144,7 +144,7 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptionsWithRedis(2, 10))->by($this->key)]; + return [(new ThrottlesExceptionsWithRedis(2, 10 * 60))->by($this->key)]; } } @@ -168,6 +168,6 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptionsWithRedis(2, 10))->by($this->key)]; + return [(new ThrottlesExceptionsWithRedis(2, 10 * 60))->by($this->key)]; } } From c948905636f60e79cf47365c82fda3773776be85 Mon Sep 17 00:00:00 2001 From: Moshe Brodsky <44633930+moshe-autoleadstar@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:27:47 +0200 Subject: [PATCH 59/99] fix Exception: Cannot traverse an already closed generator when running Arr::first with an empty generator and no callback (#48851) * fix Exception: Cannot traverse an already closed generator when running Arr::first with an empty generator and no callback * styling * should use default return --- src/Illuminate/Collections/Arr.php | 2 ++ tests/Support/SupportArrTest.php | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 3cb9a2ca180a..c14465c6b3fa 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -195,6 +195,8 @@ public static function first($array, callable $callback = null, $default = null) foreach ($array as $item) { return $item; } + + return value($default); } foreach ($array as $key => $value) { diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index f87a36364b79..2c5f6c224e67 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -214,6 +214,13 @@ public function testFirst() $this->assertSame('bar', $value3); $this->assertSame('baz', $value4); $this->assertEquals(100, $value5); + + $cursor = (function () { + while (false) { + yield 1; + } + })(); + $this->assertNull(Arr::first($cursor)); } public function testJoin() From 444141b759b34b48f63d2a16c83978c1377faefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateus=20Guimar=C3=A3es?= Date: Wed, 1 Nov 2023 16:35:43 -0300 Subject: [PATCH 60/99] [11.x] Allow `SyncQueue` to dispatch jobs after a transaction is committed (#48860) * Fix after commit jobs using the sync queue * Additional test * Remove unnecessary check * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Queue/Queue.php | 2 +- src/Illuminate/Queue/SyncQueue.php | 22 ++++++++++++++ tests/Queue/QueueSyncQueueTest.php | 48 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index 0ce7ad1ac1ce..69fe00c4ddd1 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -326,7 +326,7 @@ function () use ($payload, $queue, $delay, $callback, $job) { */ protected function shouldDispatchAfterCommit($job) { - if (is_object($job) && $job instanceof ShouldQueueAfterCommit) { + if ($job instanceof ShouldQueueAfterCommit) { return true; } diff --git a/src/Illuminate/Queue/SyncQueue.php b/src/Illuminate/Queue/SyncQueue.php index f416b0232fd3..c687635ee3ea 100755 --- a/src/Illuminate/Queue/SyncQueue.php +++ b/src/Illuminate/Queue/SyncQueue.php @@ -34,6 +34,28 @@ public function size($queue = null) * @throws \Throwable */ public function push($job, $data = '', $queue = null) + { + if ($this->shouldDispatchAfterCommit($job) && + $this->container->bound('db.transactions')) { + return $this->container->make('db.transactions')->addCallback( + fn () => $this->executeJob($job, $data, $queue) + ); + } + + return $this->executeJob($job, $data, $queue); + } + + /** + * Execute a given job synchronously. + * + * @param string $job + * @param mixed $data + * @param string|null $queue + * @return int + * + * @throws \Throwable + */ + protected function executeJob($job, $data = '', $queue = null) { $queueJob = $this->resolveJob($this->createPayload($job, $queue, $data), $queue); diff --git a/tests/Queue/QueueSyncQueueTest.php b/tests/Queue/QueueSyncQueueTest.php index c833b87a30ca..a361bf5e773b 100755 --- a/tests/Queue/QueueSyncQueueTest.php +++ b/tests/Queue/QueueSyncQueueTest.php @@ -7,6 +7,8 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Queue\QueueableEntity; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; +use Illuminate\Database\DatabaseTransactionsManager; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Jobs\SyncJob; use Illuminate\Queue\SyncQueue; @@ -77,6 +79,32 @@ public function testCreatesPayloadObject() $this->assertSame('extraValue', $e->getMessage()); } } + + public function testItAddsATransactionCallbackForAfterCommitJobs() + { + $sync = new SyncQueue; + $container = new Container; + $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); + $transactionManager = m::mock(DatabaseTransactionsManager::class); + $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $container->instance('db.transactions', $transactionManager); + + $sync->setContainer($container); + $sync->push(new SyncQueueAfterCommitJob()); + } + + public function testItAddsATransactionCallbackForInterfaceBasedAfterCommitJobs() + { + $sync = new SyncQueue; + $container = new Container; + $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); + $transactionManager = m::mock(DatabaseTransactionsManager::class); + $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $container->instance('db.transactions', $transactionManager); + + $sync->setContainer($container); + $sync->push(new SyncQueueAfterCommitInterfaceJob()); + } } class SyncQueueTestEntity implements QueueableEntity @@ -134,3 +162,23 @@ public function getValueFromJob($key) return $payload['data'][$key] ?? null; } } + +class SyncQueueAfterCommitJob +{ + use InteractsWithQueue; + + public $afterCommit = true; + + public function handle() + { + } +} + +class SyncQueueAfterCommitInterfaceJob implements ShouldQueueAfterCommit +{ + use InteractsWithQueue; + + public function handle() + { + } +} From be2175758bd88eb1a53c21a020988bbb2642c52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Debrauwer?= Date: Thu, 9 Nov 2023 00:14:53 +0100 Subject: [PATCH 61/99] [11.x] Support retry and throw on async http client request (in a http client request pool) (#48906) * Support throw and retry in async request * Small fixes * Fix linting issues * Fix another linting issue * Throw after last retry * Provide attempt and exception to retryDelay closure * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Http/Client/PendingRequest.php | 61 +++- tests/Http/HttpClientTest.php | 270 ++++++++++++++++++ 2 files changed, 330 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 0ddc69f6bc35..984f8d25a658 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -997,9 +997,10 @@ protected function parseMultipartBodyFormat(array $data) * @param string $method * @param string $url * @param array $options + * @param int $attempt * @return \GuzzleHttp\Promise\PromiseInterface */ - protected function makePromise(string $method, string $url, array $options = []) + protected function makePromise(string $method, string $url, array $options = [], int $attempt = 1) { return $this->promise = $this->sendRequest($method, $url, $options) ->then(function (MessageInterface $message) { @@ -1011,12 +1012,70 @@ protected function makePromise(string $method, string $url, array $options = []) ->otherwise(function (TransferException $e) { if ($e instanceof ConnectException) { $this->dispatchConnectionFailedEvent(); + + return new ConnectionException($e->getMessage(), 0, $e); } return $e instanceof RequestException && $e->hasResponse() ? $this->populateResponse($this->newResponse($e->getResponse())) : $e; + }) + ->then(function (Response|ConnectionException|TransferException $response) use ($method, $url, $options, $attempt) { + return $this->handlePromiseResponse($response, $method, $url, $options, $attempt); }); } + /** + * Handle the response of an asynchronous request. + * + * @param \Illuminate\Http\Client\Response $response + * @param string $method + * @param string $url + * @param array $options + * @param int $attempt + * @return mixed + */ + protected function handlePromiseResponse(Response|ConnectionException|TransferException $response, $method, $url, $options, $attempt) + { + if ($response instanceof Response && $response->successful()) { + return $response; + } + + if ($response instanceof RequestException) { + $response = $this->populateResponse($this->newResponse($response->getResponse())); + } + + try { + $shouldRetry = $this->retryWhenCallback ? call_user_func( + $this->retryWhenCallback, + $response instanceof Response ? $response->toException() : $response, + $this + ) : true; + } catch (Exception $exception) { + return $exception; + } + + if ($attempt < $this->tries && $shouldRetry) { + $options['delay'] = value($this->retryDelay, $attempt, $response->toException()); + + return $this->makePromise($method, $url, $options, $attempt + 1); + } + + if ($response instanceof Response && + $this->throwCallback && + ($this->throwIfCallback === null || call_user_func($this->throwIfCallback, $response))) { + try { + $response->throw($this->throwCallback); + } catch (Exception $exception) { + return $exception; + } + } + + if ($this->tries > 1 && $this->retryThrow) { + return $response instanceof Response ? $response->toException() : $response; + } + + return $response; + } + /** * Send a request either synchronously or asynchronously. * diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 7d59b2c3822d..e32f01e6a68b 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1837,6 +1837,132 @@ public function testRequestsWillBeWaitingSleepMillisecondsReceivedBeforeRetry() ]); } + public function testRequestExceptionReturnedWhenRetriesExhaustedInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, null, true)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + + $this->factory->assertSentCount(2); + } + + public function testRequestExceptionIsReturnedWithoutRetriesIfRetryNotNecessaryInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + $whenAttempts = collect(); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, function ($exception) use ($whenAttempts) { + $whenAttempts->push($exception); + + return $exception->response->status() === 403; + }, true)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + + $this->assertCount(1, $whenAttempts); + + $this->factory->assertSentCount(1); + } + + public function testRequestExceptionIsNotReturnedWhenDisabledAndRetriesExhaustedInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, null, false)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($response); + $this->assertInstanceOf(Response::class, $response); + $this->assertTrue($response->failed()); + + $this->factory->assertSentCount(2); + } + + public function testRequestExceptionIsNotReturnedWithoutRetriesIfRetryNotNecessaryInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + $whenAttempts = collect(); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, function ($exception) use ($whenAttempts) { + $whenAttempts->push($exception); + + return $exception->response->status() === 403; + }, false)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($response); + $this->assertInstanceOf(Response::class, $response); + $this->assertTrue($response->failed()); + + $this->assertCount(1, $whenAttempts); + + $this->factory->assertSentCount(1); + } + + public function testRequestCanBeModifiedInRetryCallbackInPool() + { + $this->factory->fake([ + '*' => $this->factory->sequence() + ->push(['error'], 500) + ->push(['ok'], 200), + ]); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, function ($exception, $request) { + $this->assertInstanceOf(PendingRequest::class, $request); + + $request->withHeaders(['Foo' => 'Bar']); + + return true; + }, false)->get('http://foo.com/get'), + ]); + + $this->assertTrue($response->successful()); + + $this->factory->assertSent(function (Request $request) { + return $request->hasHeader('Foo') && $request->header('Foo') === ['Bar']; + }); + } + + public function testExceptionThrownInRetryCallbackIsReturnedWithoutRetryingInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->retry(2, 1000, function ($exception) { + throw new Exception('Foo bar'); + }, false)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(Exception::class, $exception); + $this->assertEquals('Foo bar', $exception->getMessage()); + + $this->factory->assertSentCount(1); + } + public function testMiddlewareRunsWhenFaked() { $this->factory->fake(function (Request $request) { @@ -2081,6 +2207,150 @@ public function testRequestExceptionIsNotThrownIfTheRequestDoesNotFail() $this->assertSame('{"result":{"foo":"bar"}}', $response->body()); } + public function testRequestExceptionIsNotReturnedIfThePendingRequestIsSetToThrowOnFailureButTheResponseIsSuccessfulInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['success'], 200), + ]); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->throw()->get('http://foo.com/get'), + ]); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->status()); + } + + public function testRequestExceptionIsReturnedIfThePendingRequestIsSetToThrowOnFailureInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->throw()->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + } + + public function testRequestExceptionIsReturnedIfTheThrowIfOnThePendingRequestIsSetToTrueOnFailureInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->throwIf(true)->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + } + + public function testRequestExceptionIsNotReturnedIfTheThrowIfOnThePendingRequestIsSetToFalseOnFailureInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->throwIf(false)->get('http://foo.com/get'), + ]); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(403, $response->status()); + } + + public function testRequestExceptionIsReturnedIfTheThrowIfClosureOnThePendingRequestReturnsTrueInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + $hitThrowCallback = collect(); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->throwIf(function ($response) { + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(403, $response->status()); + + return true; + }, function ($response, $e) use (&$hitThrowCallback) { + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(403, $response->status()); + + $this->assertInstanceOf(RequestException::class, $e); + + $hitThrowCallback->push(true); + })->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + $this->assertCount(1, $hitThrowCallback); + } + + public function testRequestExceptionIsNotReturnedIfTheThrowIfClosureOnThePendingRequestReturnsFalseInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + $hitThrowCallback = collect(); + + [$response] = $this->factory->pool(fn ($pool) => [ + $pool->throwIf(function ($response) { + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(403, $response->status()); + + return false; + }, function ($response, $e) use (&$hitThrowCallback) { + $hitThrowCallback->push(true); + })->get('http://foo.com/get'), + ]); + + $this->assertCount(0, $hitThrowCallback); + $this->assertSame(403, $response->status()); + } + + public function testRequestExceptionIsReturnedWithCallbackIfThePendingRequestIsSetToThrowOnFailureInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + $flag = collect(); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->throw(function ($exception) use (&$flag) { + $flag->push(true); + })->get('http://foo.com/get'), + ]); + + $this->assertCount(1, $flag); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + } + + public function testRequestExceptionIsReturnedAfterLastRetryInPool() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + [$exception] = $this->factory->pool(fn ($pool) => [ + $pool->retry(3)->throw()->get('http://foo.com/get'), + ]); + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + + $this->factory->assertSentCount(3); + } + public function testRequestExceptionIsThrowIfConditionIsSatisfied() { $this->factory->fake([ From b166e82d6fc1d1f63de24a998cdd90839942caca Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sun, 26 Nov 2023 23:53:20 +0800 Subject: [PATCH 62/99] Apply `#[\Override]` attribute on methods extended from Symfony (#49130) * Apply `#[\Override]` attribute when applicable. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/Application.php | 2 ++ src/Illuminate/Console/BufferedConsoleOutput.php | 1 + src/Illuminate/Console/Command.php | 4 ++++ src/Illuminate/Console/OutputStyle.php | 4 ++++ src/Illuminate/Console/QuestionHelper.php | 1 + src/Illuminate/Http/JsonResponse.php | 3 +++ src/Illuminate/Http/Request.php | 4 ++++ src/Illuminate/Http/Response.php | 1 + 8 files changed, 20 insertions(+) diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index f594258e9e76..9bfe73b9b43e 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -208,6 +208,7 @@ public function output() * @param \Symfony\Component\Console\Command\Command $command * @return \Symfony\Component\Console\Command\Command|null */ + #[\Override] public function add(SymfonyCommand $command): ?SymfonyCommand { if ($command instanceof Command) { @@ -285,6 +286,7 @@ public function setContainerCommandLoader() * * @return \Symfony\Component\Console\Input\InputDefinition */ + #[\Override] protected function getDefaultInputDefinition(): InputDefinition { return tap(parent::getDefaultInputDefinition(), function ($definition) { diff --git a/src/Illuminate/Console/BufferedConsoleOutput.php b/src/Illuminate/Console/BufferedConsoleOutput.php index d4ee3954f393..0cb40487ba27 100644 --- a/src/Illuminate/Console/BufferedConsoleOutput.php +++ b/src/Illuminate/Console/BufferedConsoleOutput.php @@ -28,6 +28,7 @@ public function fetch() /** * {@inheritdoc} */ + #[\Override] protected function doWrite(string $message, bool $newline): void { $this->buffer .= $message; diff --git a/src/Illuminate/Console/Command.php b/src/Illuminate/Console/Command.php index 9e0290ebcc6d..4fc256a796ad 100755 --- a/src/Illuminate/Console/Command.php +++ b/src/Illuminate/Console/Command.php @@ -166,6 +166,7 @@ protected function configureIsolation() * @param \Symfony\Component\Console\Output\OutputInterface $output * @return int */ + #[\Override] public function run(InputInterface $input, OutputInterface $output): int { $this->output = $output instanceof OutputStyle ? $output : $this->laravel->make( @@ -191,6 +192,7 @@ public function run(InputInterface $input, OutputInterface $output): int * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output */ + #[\Override] protected function execute(InputInterface $input, OutputInterface $output): int { if ($this instanceof Isolatable && $this->option('isolated') !== false && @@ -257,6 +259,7 @@ protected function resolveCommand($command) * * @return bool */ + #[\Override] public function isHidden(): bool { return $this->hidden; @@ -265,6 +268,7 @@ public function isHidden(): bool /** * {@inheritdoc} */ + #[\Override] public function setHidden(bool $hidden = true): static { parent::setHidden($this->hidden = $hidden); diff --git a/src/Illuminate/Console/OutputStyle.php b/src/Illuminate/Console/OutputStyle.php index b0d6f94e8b17..65b918c685d1 100644 --- a/src/Illuminate/Console/OutputStyle.php +++ b/src/Illuminate/Console/OutputStyle.php @@ -52,6 +52,7 @@ public function __construct(InputInterface $input, OutputInterface $output) /** * {@inheritdoc} */ + #[\Override] public function askQuestion(Question $question): mixed { try { @@ -64,6 +65,7 @@ public function askQuestion(Question $question): mixed /** * {@inheritdoc} */ + #[\Override] public function write(string|iterable $messages, bool $newline = false, int $options = 0): void { $this->newLinesWritten = $this->trailingNewLineCount($messages) + (int) $newline; @@ -75,6 +77,7 @@ public function write(string|iterable $messages, bool $newline = false, int $opt /** * {@inheritdoc} */ + #[\Override] public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void { $this->newLinesWritten = $this->trailingNewLineCount($messages) + 1; @@ -86,6 +89,7 @@ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORM /** * {@inheritdoc} */ + #[\Override] public function newLine(int $count = 1): void { $this->newLinesWritten += $count; diff --git a/src/Illuminate/Console/QuestionHelper.php b/src/Illuminate/Console/QuestionHelper.php index a3bf62a520ab..6d8a648fde70 100644 --- a/src/Illuminate/Console/QuestionHelper.php +++ b/src/Illuminate/Console/QuestionHelper.php @@ -17,6 +17,7 @@ class QuestionHelper extends SymfonyQuestionHelper * * @return void */ + #[\Override] protected function writePrompt(OutputInterface $output, Question $question): void { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); diff --git a/src/Illuminate/Http/JsonResponse.php b/src/Illuminate/Http/JsonResponse.php index 25006c3f65bb..dd5ce0ef5f25 100755 --- a/src/Illuminate/Http/JsonResponse.php +++ b/src/Illuminate/Http/JsonResponse.php @@ -37,6 +37,7 @@ public function __construct($data = null, $status = 200, $headers = [], $options * * @return static */ + #[\Override] public static function fromJsonString(?string $data = null, int $status = 200, array $headers = []): static { return new static($data, $status, $headers, 0, true); @@ -70,6 +71,7 @@ public function getData($assoc = false, $depth = 512) * * @return static */ + #[\Override] public function setData($data = []): static { $this->original = $data; @@ -116,6 +118,7 @@ protected function hasValidJson($jsonError) * * @return static */ + #[\Override] public function setEncodingOptions($options): static { $this->encodingOptions = (int) $options; diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index b7d2c02f3b07..4cbe95c94161 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -389,6 +389,7 @@ public function replace(array $input) * @param mixed $default * @return mixed */ + #[\Override] public function get(string $key, mixed $default = null): mixed { return parent::get($key, $default); @@ -499,6 +500,7 @@ public static function createFromBase(SymfonyRequest $request) * * @return static */ + #[\Override] public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null): static { return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server); @@ -532,6 +534,7 @@ protected function filterFiles($files) /** * {@inheritdoc} */ + #[\Override] public function hasSession(bool $skipIfUninitialized = false): bool { return $this->session instanceof SymfonySessionDecorator; @@ -540,6 +543,7 @@ public function hasSession(bool $skipIfUninitialized = false): bool /** * {@inheritdoc} */ + #[\Override] public function getSession(): SessionInterface { return $this->hasSession() diff --git a/src/Illuminate/Http/Response.php b/src/Illuminate/Http/Response.php index dad783fcd67a..7e3f0d480e2e 100755 --- a/src/Illuminate/Http/Response.php +++ b/src/Illuminate/Http/Response.php @@ -45,6 +45,7 @@ public function __construct($content = '', $status = 200, array $headers = []) * * @throws \InvalidArgumentException */ + #[\Override] public function setContent(mixed $content): static { $this->original = $content; From 2f746b2ce9e720c5103f6835ead39ad737b55f3f Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 27 Nov 2023 22:45:44 +0800 Subject: [PATCH 63/99] [11.x] Add `symfony/polyfill-php83` to component using `#[\Override]` (#49140) Signed-off-by: Mior Muhammad Zaki --- composer.json | 1 + src/Illuminate/Console/composer.json | 1 + src/Illuminate/Http/composer.json | 1 + 3 files changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 439d904e1aa3..84c53c70428d 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ "symfony/http-kernel": "^7.0", "symfony/mailer": "^7.0", "symfony/mime": "^7.0", + "symfony/polyfill-php83": "^1.28", "symfony/process": "^7.0", "symfony/routing": "^7.0", "symfony/uid": "^7.0", diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 68e50920d317..c26420bcc122 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -24,6 +24,7 @@ "laravel/prompts": "^0.1.12", "nunomaduro/termwind": "^2.0", "symfony/console": "^7.0", + "symfony/polyfill-php83": "^1.28", "symfony/process": "^7.0" }, "autoload": { diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 768d72bc6637..f683ad8e8390 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -24,6 +24,7 @@ "illuminate/support": "^11.0", "symfony/http-foundation": "^7.0", "symfony/http-kernel": "^7.0", + "symfony/polyfill-php83": "^1.28", "symfony/mime": "^7.0" }, "autoload": { From 1d878ccac4ed1e2a0cc337bf32fac4e52f893dfb Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 28 Nov 2023 14:26:24 -0600 Subject: [PATCH 64/99] [11.x] Slim skeleton support (#47309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Configuration All configuration files now have framework counterparts and application level configuration is merged with the framework defaults. The default configuration files have also received many more environment variables so that more options can be changed from the application’s `.env` file. The `LoadConfiguration` bootstrap class has been added to support framework configuration cascading. A new `config:publish` command has been introduced to publish framework configuration files. ### Middleware The `Authenticate` middleware and `AuthenticationException` exception now have `redirectUsing` methods that accept a closure. This closure will be invoked to determine where to redirect unauthenticated users. Helper methods for setting this closure are available on the `Middleware` application builder class. The `Authenticate` middleware also now does not return a redirect location if the incoming request expected JSON. The `RedirectIfAuthenticated` middleware has been added to the framework. This middleware also includes a `redirectUsing` method to customize the “guest” redirection behavior. The default behavior is to redirect to `/dashboard`. Helper methods for setting this closure are available on the `Middleware` application builder class. The `AuthenticateSession` middleware has received a `redirectUsing` helper to control the redirection behavior. Helper methods for setting this closure are available on the `Middleware` application builder class. The `TrimStrings` middleware has received an `except` method that may be used to specify which strings should not be trimmed. The `ValidateCsrfToken` middleware has received an `except` method that may be used to specify which paths should not receive CSRF token validation. The `ValidateSignature` middleware has receive an `except` method that may be used to specify which parameters should not be included in signature validation. `ValidateCsrfToken` has been added as an alias of `VerifyCsrfToken`. The `TrustHosts` middleware has been updated to allow all subdomains of the application’s configured URL by default. The `TrustProxies` middleware has been updated to trust all proxies by default. The `EncryptCookies` middleware has received a static `except` method which may be used in a service provider to specify the cookies that should not be encrypted. ### Events The foundation `EventServiceProvider` has been updated to discover events by default. In addition, the email verification listener to send email verification notifications is now configured automatically if no `Registered` event listeners exist or the `SendEmailVerificatioNotification` listener is not in the list of listeners for the `Registered` event. ### Notificiations A `slack` configuration array has been added to the framework's copy of the `services.php` configuration file. ### Artisan Commands The `cache:table` command has received a `make:cache-table` alias to move all generation commands under the `make` namespace. The `notifications:table` command has receive a `make:notifications-table` alias to move all generation commands under the `make` namespace. The `queue:batches-table` command has received a `make:queue-batches-table` alias for the same reason as above. The `queue:failed-table` command has received a `make:queue-failed-table` alias for the same reason as above. In addition, `queue:table` has received a `make:queue-table` alias. Also, `session:table` has received a `make:session-table` alias. A `schedule` command has been added to closure commands, allowing the fluent scheduling of closure commands in the console routes file. The console scheduler is now available via a `Schedule` facade. The `optimize` command now also caches views and events. ### Service Providers The `RegisterProviders` bootstrap class has been updated to support the loading of additional providers from the `bootstrap/providers.php` array file. The `make:provider` command has been updated to add the new service provider to the `bootstrap/providers.php` file if it exists. The `ServiceProvider` class has received a new static `addProviderToBootstrapFile` method that will add a service provider class to the `bootstrap/providers.php` file if it exists. ### Application Configuration The `Application` class has received several new methods and helpers. A new `registered` listener method has been added to allow code to react to the registration service providers. A new `getBootstrapProvidersPath` method has been added that returns the location to the bootstrap providers file. New `handleRequest` and `handleCommands` method have been added in order to clean up and simplify the application level bootstrap / index files. A new `configure` method has been added to the `Application` class in order to allow the fluent configuration of multiple framework features, including routing and container bindings. A new `ApplicationBuilder` class has been introduced to allow the easy configuration of a variety of core framework functionality, including routing, commands, middleware, exception handling, booting / booted callbacks, and more. A new `Middleware` application configuration class has been introduced that allows the easy definition of new middleware groups, prepending and appending of middleware to existing groups, replacing middleware in existing groups, and fluent methods for enabling middleware like `TrustHosts` and `EnsureFrontendRequestsAreStateful`. Helper methods have been added to the exception handler for `dontReport`, `dontReportDuplicates`, `dontFlash`, `buildContextUsing`. ### Installers A new `install:api` command has been added. This command installs `laravel/sanctum` and uncomments the “API” routes definition in the bootstrap file. A new `install:broadcasting` has been added which uncomments the “channels” routes definition in the bootstrap file. In addition, a Laravel Echo file is written to the `resources/js` directory which contains the Echo configuration. A directive to include this file is injected into the main `bootstrap.js` file. --- .github/workflows/databases.yml | 3 +- composer.json | 2 +- config/app.php | 184 ++++++ config/auth.php | 115 ++++ config/broadcasting.php | 71 +++ config/cache.php | 111 ++++ config/cors.php | 34 + config/database.php | 166 +++++ config/filesystems.php | 76 +++ config/hashing.php | 54 ++ config/logging.php | 131 ++++ config/mail.php | 126 ++++ config/queue.php | 111 ++++ config/services.php | 41 ++ config/session.php | 202 ++++++ config/view.php | 36 ++ .../Auth/AuthenticationException.php | 30 +- .../Auth/Middleware/Authenticate.php | 26 +- .../Middleware/RedirectIfAuthenticated.php | 57 ++ .../Cache/Console/CacheTableCommand.php | 11 +- .../Cookie/Middleware/EncryptCookies.php | 23 +- src/Illuminate/Foundation/Application.php | 86 +++ .../Bootstrap/LoadConfiguration.php | 56 +- .../Bootstrap/RegisterProviders.php | 63 ++ .../Configuration/ApplicationBuilder.php | 337 ++++++++++ .../Foundation/Configuration/Exceptions.php | 137 ++++ .../Foundation/Configuration/Middleware.php | 601 ++++++++++++++++++ .../Foundation/Console/ApiInstallCommand.php | 106 +++ .../Console/BroadcastingInstallCommand.php | 94 +++ .../Foundation/Console/ClosureCommand.php | 32 + .../Console/ConfigPublishCommand.php | 88 +++ .../Console/InteractsWithComposerPackages.php | 44 ++ src/Illuminate/Foundation/Console/Kernel.php | 144 ++++- .../Console/OptimizeClearCommand.php | 8 +- .../Foundation/Console/OptimizeCommand.php | 6 +- .../Console/ProviderMakeCommand.php | 24 + .../Foundation/Console/stubs/api-routes.stub | 20 + .../Console/stubs/broadcasting-routes.stub | 18 + .../Console/stubs/echo-bootstrap-js.stub | 7 + .../Foundation/Console/stubs/echo-js.stub | 15 + .../Foundation/Exceptions/Handler.php | 96 ++- src/Illuminate/Foundation/Http/Kernel.php | 55 ++ .../Http/Middleware/TrimStrings.php | 41 +- .../Http/Middleware/ValidateCsrfToken.php | 11 + .../Http/Middleware/ValidatePostSize.php | 47 +- .../Http/Middleware/VerifyCsrfToken.php | 23 +- .../Providers/ArtisanServiceProvider.php | 18 + .../Providers/FoundationServiceProvider.php | 15 + .../Providers/EventServiceProvider.php | 22 +- .../Providers/RouteServiceProvider.php | 22 +- src/Illuminate/Http/Middleware/TrustHosts.php | 9 +- .../Http/Middleware/TrustProxies.php | 8 +- .../Http/Middleware/ValidatePostSize.php | 52 ++ .../Console/NotificationTableCommand.php | 11 +- .../Queue/Console/BatchesTableCommand.php | 11 +- .../Queue/Console/FailedTableCommand.php | 11 +- src/Illuminate/Queue/Console/TableCommand.php | 11 +- .../Routing/Middleware/ValidateSignature.php | 22 +- .../Session/Console/SessionTableCommand.php | 11 +- .../Middleware/AuthenticateSession.php | 22 +- src/Illuminate/Support/Facades/App.php | 5 + src/Illuminate/Support/Facades/Artisan.php | 4 + src/Illuminate/Support/Facades/Facade.php | 1 + src/Illuminate/Support/Facades/Schedule.php | 35 + src/Illuminate/Support/ServiceProvider.php | 34 + .../View/Engines/CompilerEngine.php | 5 +- tests/Auth/AuthenticateMiddlewareTest.php | 2 + .../Cookie/Middleware/EncryptCookiesTest.php | 17 +- tests/Http/Middleware/TrimStringsTest.php | 17 + .../Scheduling/ScheduleListCommandTest.php | 25 + .../Database/SchemaBuilderTest.php | 7 +- .../Generators/CacheTableCommandTest.php | 4 +- .../NotificationTableCommandTest.php | 4 +- .../QueueBatchesTableCommandTest.php | 4 +- .../QueueFailedTableCommandTest.php | 4 +- .../Generators/QueueTableCommandTest.php | 4 +- .../Generators/SessionTableCommandTest.php | 4 +- .../Middleware/VerifyCsrfTokenExceptTest.php | 7 + tests/Integration/Routing/UrlSigningTest.php | 22 +- 79 files changed, 4085 insertions(+), 134 deletions(-) create mode 100644 config/app.php create mode 100644 config/auth.php create mode 100644 config/broadcasting.php create mode 100644 config/cache.php create mode 100644 config/cors.php create mode 100644 config/database.php create mode 100644 config/filesystems.php create mode 100644 config/hashing.php create mode 100644 config/logging.php create mode 100644 config/mail.php create mode 100644 config/queue.php create mode 100644 config/services.php create mode 100644 config/session.php create mode 100644 config/view.php create mode 100644 src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php create mode 100644 src/Illuminate/Foundation/Configuration/ApplicationBuilder.php create mode 100644 src/Illuminate/Foundation/Configuration/Exceptions.php create mode 100644 src/Illuminate/Foundation/Configuration/Middleware.php create mode 100644 src/Illuminate/Foundation/Console/ApiInstallCommand.php create mode 100644 src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php create mode 100644 src/Illuminate/Foundation/Console/ConfigPublishCommand.php create mode 100644 src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php create mode 100644 src/Illuminate/Foundation/Console/stubs/api-routes.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/echo-bootstrap-js.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/echo-js.stub create mode 100644 src/Illuminate/Foundation/Http/Middleware/ValidateCsrfToken.php create mode 100644 src/Illuminate/Http/Middleware/ValidatePostSize.php create mode 100644 src/Illuminate/Support/Facades/Schedule.php diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 37ffbe3c2cae..06040a997ab2 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -52,6 +52,7 @@ jobs: env: DB_CONNECTION: mysql DB_USERNAME: root + MYSQL_COLLATION: utf8mb4_unicode_ci mysql_8: runs-on: ubuntu-22.04 @@ -140,7 +141,7 @@ jobs: - name: Execute tests run: vendor/bin/phpunit tests/Integration/Database env: - DB_CONNECTION: mysql + DB_CONNECTION: mariadb DB_USERNAME: root pgsql: diff --git a/composer.json b/composer.json index 84c53c70428d..ee285ac68d1a 100644 --- a/composer.json +++ b/composer.json @@ -106,7 +106,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.0", + "orchestra/testbench-core": "dev-next/slim-skeleton", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.1", diff --git a/config/app.php b/config/app.php new file mode 100644 index 000000000000..cd591805bccd --- /dev/null +++ b/config/app.php @@ -0,0 +1,184 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + 'frontend_url' => env('FRONTEND_URL', 'http://localhost:3000'), + + 'asset_url' => env('ASSET_URL'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the default one + | is not available. You may change the value to correspond to any of + | the languages which are currently supported by your application. + | + */ + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string or all of the encrypted strings are + | not secure. You should do this prior to deploying the application. + | + */ + + 'key' => env('APP_KEY'), + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'redis'), + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on any + | requests to your application. You may add your own services to the + | arrays below to provide additional features to this application. + | + */ + + 'providers' => ServiceProvider::defaultProviders()->merge([ + // Package Service Providers... + ])->merge([ + // Application Service Providers... + // App\Providers\AppServiceProvider::class, + ])->merge([ + // Added Service Providers (Do not remove this line)... + ])->toArray(), + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. You may add any additional class aliases which should + | be loaded to the array. For speed, all aliases are lazy loaded. + | + */ + + 'aliases' => Facade::defaultAliases()->merge([ + // 'Example' => App\Facades\Example::class, + ])->toArray(), + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 000000000000..7bf212ad24a4 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,115 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', App\Models\User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_reset_tokens', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 000000000000..de5dd4e5ce8c --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,71 @@ + env('BROADCAST_CONNECTION', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_BROADCASTING_CONNECTION', 'default'), + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 000000000000..4d67e5de8a49 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,111 @@ + env('CACHE_STORE', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'connection' => env('DB_CACHE_CONNECTION', null), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION', null), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, or DynamoDB cache + | stores there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + +]; diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 000000000000..8a39e6daa63d --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 000000000000..ff5880bd2e8e --- /dev/null +++ b/config/database.php @@ -0,0 +1,166 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to assist your development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_0900_ai_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_uca1400_ai_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 000000000000..21ad5c8bdb40 --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,76 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been set up for each driver as an example of the required values. + | + | Supported Drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'throw' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 000000000000..2925f725b487 --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,54 @@ + env('HASH_DRIVER', 'bcrypt'), + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 12), + 'verify' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => env('ARGON_MEMORY', 65536), + 'threads' => env('ARGON_THREADS', 1), + 'time' => env('ARGON_TIME', 4), + 'verify' => true, + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 000000000000..06a666ac78c0 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,131 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 000000000000..07e449a5fa23 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,126 @@ + env('MAIL_MAILER', 'smtp'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers to be used while + | delivering an email. You may specify which one you're using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "log", "array", "failover" + | + */ + + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'mailgun' => [ + 'transport' => 'mailgun', + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => null, + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => env('MAIL_MARKDOWN_THEME', 'default'), + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 000000000000..70c9fcf83a4d --- /dev/null +++ b/config/queue.php @@ -0,0 +1,111 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 000000000000..a12fbb266b0b --- /dev/null +++ b/config/services.php @@ -0,0 +1,41 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', + ], + + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 000000000000..b29b8b783815 --- /dev/null +++ b/config/session.php @@ -0,0 +1,202 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, we need a spot where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + | Affects: "apc", "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" since this is a secure default value. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 000000000000..22b8a18d3258 --- /dev/null +++ b/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + +]; diff --git a/src/Illuminate/Auth/AuthenticationException.php b/src/Illuminate/Auth/AuthenticationException.php index 66808c3b8151..c4f835c5e6c0 100644 --- a/src/Illuminate/Auth/AuthenticationException.php +++ b/src/Illuminate/Auth/AuthenticationException.php @@ -3,6 +3,7 @@ namespace Illuminate\Auth; use Exception; +use Illuminate\Http\Request; class AuthenticationException extends Exception { @@ -20,6 +21,13 @@ class AuthenticationException extends Exception */ protected $redirectTo; + /** + * The callback that should be used to generate the authentication redirect path. + * + * @var callable + */ + protected static $redirectToCallback; + /** * Create a new authentication exception. * @@ -49,10 +57,28 @@ public function guards() /** * Get the path the user should be redirected to. * + * @param \Illuminate\Http\Request $request * @return string|null */ - public function redirectTo() + public function redirectTo(Request $request) + { + if ($this->redirectTo) { + return $this->redirectTo; + } + + if (static::$redirectToCallback) { + return call_user_func(static::$redirectToCallback, $request); + } + } + + /** + * Specify the callback that should be used to generate the redirect path. + * + * @param callable $redirectToCallback + * @return void + */ + public static function redirectUsing(callable $redirectToCallback) { - return $this->redirectTo; + static::$redirectToCallback = $redirectToCallback; } } diff --git a/src/Illuminate/Auth/Middleware/Authenticate.php b/src/Illuminate/Auth/Middleware/Authenticate.php index b1b4cf90abc2..ef1a3f2ea030 100644 --- a/src/Illuminate/Auth/Middleware/Authenticate.php +++ b/src/Illuminate/Auth/Middleware/Authenticate.php @@ -17,6 +17,13 @@ class Authenticate implements AuthenticatesRequests */ protected $auth; + /** + * The callback that should be used to generate the authentication redirect path. + * + * @var callable + */ + protected static $redirectToCallback; + /** * Create a new middleware instance. * @@ -93,7 +100,9 @@ protected function authenticate($request, array $guards) protected function unauthenticated($request, array $guards) { throw new AuthenticationException( - 'Unauthenticated.', $guards, $this->redirectTo($request) + 'Unauthenticated.', + $guards, + $request->expectsJson() ? null : $this->redirectTo($request), ); } @@ -105,6 +114,19 @@ protected function unauthenticated($request, array $guards) */ protected function redirectTo(Request $request) { - // + if (static::$redirectToCallback) { + return call_user_func(static::$redirectToCallback, $request); + } + } + + /** + * Specify the callback that should be used to generate the redirect path. + * + * @param callable $redirectToCallback + * @return void + */ + public static function redirectUsing(callable $redirectToCallback) + { + static::$redirectToCallback = $redirectToCallback; } } diff --git a/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php b/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php new file mode 100644 index 000000000000..c436ef16ee0a --- /dev/null +++ b/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,57 @@ +check()) { + return redirect($this->redirectTo($request)); + } + } + + return $next($request); + } + + /** + * Get the path the user should be redirected to when they are authenticated. + */ + protected function redirectTo(Request $request): ?string + { + return static::$redirectToCallback + ? call_user_func(static::$redirectToCallback, $request) + : '/dashboard'; + } + + /** + * Specify the callback that should be used to generate the redirect path. + * + * @param callable $redirectToCallback + * @return void + */ + public static function redirectUsing(callable $redirectToCallback) + { + static::$redirectToCallback = $redirectToCallback; + } +} diff --git a/src/Illuminate/Cache/Console/CacheTableCommand.php b/src/Illuminate/Cache/Console/CacheTableCommand.php index b44794f95856..ab7b2d613b06 100644 --- a/src/Illuminate/Cache/Console/CacheTableCommand.php +++ b/src/Illuminate/Cache/Console/CacheTableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'cache:table')] +#[AsCommand(name: 'make:cache-table')] class CacheTableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class CacheTableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'cache:table'; + protected $name = 'make:cache-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['cache:table']; /** * The console command description. diff --git a/src/Illuminate/Cookie/Middleware/EncryptCookies.php b/src/Illuminate/Cookie/Middleware/EncryptCookies.php index 53a914e70a0a..784bd13b82e7 100644 --- a/src/Illuminate/Cookie/Middleware/EncryptCookies.php +++ b/src/Illuminate/Cookie/Middleware/EncryptCookies.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract; use Illuminate\Cookie\CookieValuePrefix; +use Illuminate\Support\Arr; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -26,6 +27,13 @@ class EncryptCookies */ protected $except = []; + /** + * The globally ignored cookies that should not be encrypted. + * + * @var array + */ + protected static $neverEncrypt = []; + /** * Indicates if cookies should be serialized. * @@ -210,7 +218,20 @@ protected function duplicate(Cookie $cookie, $value) */ public function isDisabled($name) { - return in_array($name, $this->except); + return in_array($name, array_merge($this->except, static::$neverEncrypt)); + } + + /** + * Indicate that the given cookies should never be encrypted. + * + * @param array|string $cookies + * @return void + */ + public static function except($cookies) + { + static::$neverEncrypt = array_values(array_unique( + array_merge(static::$neverEncrypt, Arr::wrap($cookies)) + )); } /** diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index b95ccc31e476..7679ed757c94 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Container\Container; +use Illuminate\Contracts\Console\Kernel as ConsoleKernelContract; use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Contracts\Foundation\CachesConfiguration; use Illuminate\Contracts\Foundation\CachesRoutes; @@ -23,6 +24,8 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -47,6 +50,13 @@ class Application extends Container implements ApplicationContract, CachesConfig */ protected $basePath; + /** + * The array of registered callbacks. + * + * @var callable[] + */ + protected $registeredCallbacks = []; + /** * Indicates if the application has been bootstrapped before. * @@ -204,6 +214,24 @@ public function __construct($basePath = null) $this->registerCoreContainerAliases(); } + /** + * Begin configuring a new Laravel application instance. + * + * @param string|null $baseDirectory + * @return \Illuminate\Foundation\ApplicationBuilder + */ + public static function configure(string $baseDirectory = null) + { + $baseDirectory = $ENV['APP_BASE_PATH'] ?? ($baseDirectory ?: dirname(dirname( + debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] + ))); + + return (new Configuration\ApplicationBuilder(new static($baseDirectory))) + ->withKernels() + ->withEvents() + ->withCommands(); + } + /** * Get the version number of the application. * @@ -402,6 +430,16 @@ public function bootstrapPath($path = '') return $this->joinPaths($this->bootstrapPath, $path); } + /** + * Get the path to the service provider list in the bootstrap directory. + * + * @return string + */ + public function getBootstrapProvidersPath() + { + return $this->bootstrapPath('providers.php'); + } + /** * Set the bootstrap file directory. * @@ -747,6 +785,17 @@ public function hasDebugModeEnabled() return (bool) $this['config']->get('app.debug'); } + /** + * Register a new registered listener. + * + * @param callable $callback + * @return void + */ + public function registered($callback) + { + $this->registeredCallbacks[] = $callback; + } + /** * Register all of the configured providers. * @@ -761,6 +810,8 @@ public function registerConfiguredProviders() (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray()); + + $this->fireAppCallbacks($this->registeredCallbacks); } /** @@ -1084,6 +1135,41 @@ public function handle(SymfonyRequest $request, int $type = self::MAIN_REQUEST, return $this[HttpKernelContract::class]->handle(Request::createFromBase($request)); } + /** + * Handle the incoming HTTP request and send the response to the browser. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function handleRequest(Request $request) + { + $kernel = $this->make(HttpKernelContract::class); + + $response = $kernel->handle($request)->send(); + + $kernel->terminate($request, $response); + } + + /** + * Handle the incoming Artisan command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @return int + */ + public function handleCommand(InputInterface $input) + { + $kernel = $this->make(ConsoleKernelContract::class); + + $status = $kernel->handle( + $input, + new ConsoleOutput + ); + + $kernel->terminate($input, $status); + + return $status; + } + /** * Determine if middleware has been disabled for the application. * diff --git a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php index ae3f73881e5d..8d096031988c 100644 --- a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php +++ b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php @@ -27,7 +27,7 @@ public function bootstrap(Application $app) if (file_exists($cached = $app->getCachedConfigPath())) { $items = require $cached; - $loadedFromCache = true; + $app->instance('config_loaded_from_cache', $loadedFromCache = true); } // Next we will spin through all of the configuration files in the configuration @@ -62,15 +62,45 @@ protected function loadConfigurationFiles(Application $app, RepositoryContract $ { $files = $this->getConfigurationFiles($app); - if (! isset($files['app'])) { - throw new Exception('Unable to load the "app" configuration file.'); + // if (! isset($files['app'])) { + // throw new Exception('Unable to load the "app" configuration file.'); + // } + + $base = $this->getBaseConfiguration(); + + foreach ($files as $name => $path) { + $base = $this->loadConfigurationFile($repository, $name, $path, $base); } - foreach ($files as $key => $path) { - $repository->set($key, require $path); + foreach ($base as $name => $config) { + $repository->set($name, $config); } } + /** + * Load the given configuration file. + * + * @param \Illuminate\Contracts\Config\Repository $repository + * @param string $name + * @param string $path + * @param array $base + * @return array + */ + protected function loadConfigurationFile(RepositoryContract $repository, $name, $path, array $base) + { + $config = require $path; + + if (isset($base[$name])) { + $config = array_merge($base[$name], $config); + + unset($base[$name]); + } + + $repository->set($name, $config); + + return $base; + } + /** * Get all of the configuration files for the application. * @@ -111,4 +141,20 @@ protected function getNestedDirectory(SplFileInfo $file, $configPath) return $nested; } + + /** + * Get the base configuration files. + * + * @return array + */ + protected function getBaseConfiguration() + { + $config = []; + + foreach (Finder::create()->files()->name('*.php')->in(__DIR__.'/../../../../config') as $file) { + $config[basename($file->getRealPath(), '.php')] = require $file->getRealPath(); + } + + return $config; + } } diff --git a/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php index f18375cf3c7d..ddad0ffabcc5 100644 --- a/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php +++ b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php @@ -6,6 +6,20 @@ class RegisterProviders { + /** + * The service providers that should be merged before registration. + * + * @var array + */ + protected static $merge = []; + + /** + * The path to the bootstrap provider configuration file. + * + * @var string|null + */ + protected static $bootstrapProviderPath; + /** * Bootstrap the given application. * @@ -14,6 +28,55 @@ class RegisterProviders */ public function bootstrap(Application $app) { + if (! $app->bound('config_loaded_from_cache') || + $app->make('config_loaded_from_cache') === false) { + $this->mergeAdditionalProviders($app); + } + $app->registerConfiguredProviders(); } + + /** + * Merge the additional configured providers into the configuration. + * + * @param \Illuminate\Foundation\Application $app + */ + protected function mergeAdditionalProviders(Application $app) + { + if (static::$bootstrapProviderPath && + file_exists(static::$bootstrapProviderPath)) { + $packageProviders = require static::$bootstrapProviderPath; + + foreach ($packageProviders as $index => $provider) { + if (! class_exists($provider)) { + unset($packageProviders[$index]); + } + } + } + + $app->make('config')->set( + 'app.providers', + array_merge( + $app->make('config')->get('app.providers'), + static::$merge, + array_values($packageProviders ?? []), + ), + ); + } + + /** + * Merge the given providers into the provider configuration before registration. + * + * @param array $providers + * @param string|null $bootstrapProviderPath + * @return void + */ + public static function merge(array $providers, ?string $bootstrapProviderPath = null) + { + static::$bootstrapProviderPath = $bootstrapProviderPath; + + static::$merge = array_values(array_filter(array_unique( + array_merge(static::$merge, $providers) + ))); + } } diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php new file mode 100644 index 000000000000..dd213b977743 --- /dev/null +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -0,0 +1,337 @@ +app->singleton( + \Illuminate\Contracts\Http\Kernel::class, + \Illuminate\Foundation\Http\Kernel::class, + ); + + $this->app->singleton( + \Illuminate\Contracts\Console\Kernel::class, + \Illuminate\Foundation\Console\Kernel::class, + ); + + return $this; + } + + /** + * Register additional service providers. + * + * @param array $providers + * @param bool $withBootstrapProviders + * @return $this + */ + public function withProviders(array $providers = [], bool $withBootstrapProviders = true) + { + RegisterProviders::merge( + $providers, + $withBootstrapProviders + ? $this->app->getBootstrapProvidersPath() + : null + ); + + return $this; + } + + /** + * Register the core event service provider for the application. + * + * @return $this + */ + public function withEvents() + { + $this->app->booting(function () { + $this->app->register(AppEventServiceProvider::class); + }); + + return $this; + } + + /** + * Register the braodcasting services for the application. + * + * @param string $channels + * @return $this + */ + public function withBroadcasting(string $channels) + { + $this->app->booted(function () use ($channels) { + Broadcast::routes(); + + if (file_exists($channels)) { + require $channels; + } + }); + + return $this; + } + + /** + * Register the routing services for the application. + * + * @param \Closure|null $using + * @param string|null $web + * @param string|null $api + * @param string|null $commands + * @param string|null $channels + * @param string|null $pages + * @param string|null $apiPrefix + * @param callable|null $then + * @return $this + */ + public function withRouting(?Closure $using = null, + ?string $web = null, + ?string $api = null, + ?string $commands = null, + ?string $channels = null, + ?string $pages = null, + string $apiPrefix = 'api', + ?callable $then = null) + { + if (is_null($using) && (is_string($web) || is_string($api))) { + $using = $this->buildRoutingCallback($web, $api, $pages, $apiPrefix, $then); + } + + AppRouteServiceProvider::loadRoutesUsing($using); + + $this->app->booting(function () { + $this->app->register(AppRouteServiceProvider::class); + }); + + if (is_string($commands) && realpath($commands) !== false) { + $this->withCommands([$commands]); + } + + if (is_string($channels) && realpath($channels) !== false) { + $this->withBroadcasting($channels); + } + + return $this; + } + + /** + * Create the routing callback for the application. + * + * @param string|null $web + * @param string|null $api + * @param string|null $pages + * @param string $apiPrefix + * @param callable|null $then + * @return \Closure + */ + protected function buildRoutingCallback(?string $web, + ?string $api, + ?string $pages, + string $apiPrefix, + ?callable $then) + { + return function () use ($web, $api, $pages, $apiPrefix, $then) { + if (is_string($api) && realpath($api) !== false) { + Route::middleware('api')->prefix($apiPrefix)->group($api); + } + + if (is_string($web) && realpath($web) !== false) { + Route::middleware('web')->group($web); + } + + if (is_string($pages) && + realpath($pages) !== false && + class_exists(Folio::class)) { + Folio::route($pages, middleware: $this->pageMiddleware); + } + + if (is_callable($then)) { + $then(); + } + }; + } + + /** + * Register the global middleware, middleware groups, and middleware aliases for the application. + * + * @param callable $callback + * @return $this + */ + public function withMiddleware(callable $callback) + { + $this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) { + $middleware = (new Middleware) + ->auth(redirectTo: fn () => route('login')) + ->guest(redirectTo: fn () => route('dashboard')); + + $callback($middleware); + + $this->pageMiddleware = $middleware->getPageMiddleware(); + $kernel->setGlobalMiddleware($middleware->getGlobalMiddleware()); + $kernel->setMiddlewareGroups($middleware->getMiddlewareGroups()); + $kernel->setMiddlewareAliases($middleware->getMiddlewareAliases()); + }); + + return $this; + } + + /** + * Register additional Artisan commands with the application. + * + * @param array $commands + * @return $this + */ + public function withCommands(array $commands = []) + { + if (empty($commands)) { + $commands = [$this->app->path('Console/Commands')]; + } + + $this->app->afterResolving(ConsoleKernel::class, function ($kernel) use ($commands) { + [$commands, $paths] = collect($commands)->partition(fn ($command) => class_exists($command)); + [$routes, $paths] = $paths->partition(fn ($path) => is_file($path)); + + $kernel->addCommands($commands->all()); + $kernel->addCommandPaths($paths->all()); + $kernel->addCommandRoutePaths($routes->all()); + }); + + return $this; + } + + /** + * Register additional Artisan route paths. + * + * @param array $paths + * @return $this + */ + protected function withCommandRouting(array $paths) + { + $this->app->afterResolving(ConsoleKernel::class, function ($kernel) use ($paths) { + $kernel->setCommandRoutePaths($paths); + }); + } + + /** + * Register and configure the application's exception handler. + * + * @param callable|null $using + * @return $this + */ + public function withExceptions(?callable $using = null) + { + $this->app->singleton( + \Illuminate\Contracts\Debug\ExceptionHandler::class, + \Illuminate\Foundation\Exceptions\Handler::class + ); + + $using ??= fn () => true; + + $this->app->afterResolving( + \Illuminate\Foundation\Exceptions\Handler::class, + fn ($handler) => $using(new Exceptions($handler)), + ); + + return $this; + } + + /** + * Register an array of container bindings to be bound when the application is booting. + * + * @param array $bindings + * @return $this + */ + public function withBindings(array $bindings) + { + return $this->registered(function ($app) use ($bindings) { + foreach ($bindings as $abstract => $concrete) { + $app->bind($abstract, $concrete); + } + }); + } + + /** + * Register an array of singleton container bindings to be bound when the application is booting. + * + * @param array $singletons + * @return $this + */ + public function withSingletons(array $singletons) + { + return $this->registered(function ($app) use ($singletons) { + foreach ($singletons as $abstract => $concrete) { + if (is_string($abstract)) { + $app->singleton($abstract, $concrete); + } else { + $app->singleton($concrete); + } + } + }); + } + + /** + * Register a callback to be invoked when the application is "booting". + * + * @param callable $callback + * @return $this + */ + public function booting(callable $callback) + { + $this->app->booting($callback); + + return $this; + } + + /** + * Register a callback to be invoked when the application is "booted". + * + * @param callable $callback + * @return $this + */ + public function booted(callable $callback) + { + $this->app->booted($callback); + + return $this; + } + + /** + * Get the application instance. + * + * @return \Illuminate\Foundation\Application + */ + public function create() + { + return $this->app; + } +} diff --git a/src/Illuminate/Foundation/Configuration/Exceptions.php b/src/Illuminate/Foundation/Configuration/Exceptions.php new file mode 100644 index 000000000000..b38bd2a20278 --- /dev/null +++ b/src/Illuminate/Foundation/Configuration/Exceptions.php @@ -0,0 +1,137 @@ +handler->reportable($reportUsing); + } + + /** + * Register a renderable callback. + * + * @param callable $renderUsing + * @return $this + */ + public function renderable(callable $renderUsing) + { + $this->handler->renderable($renderUsing); + + return $this; + } + + /** + * Specify the callback that should be used to throttle reportable exceptions. + * + * @param callable $throttleUsing + * @return $this + */ + public function throttle(callable $throttleUsing) + { + $this->handler->throttleUsing($throttleUsing); + + return $this; + } + + /** + * Register a new exception mapping. + * + * @param \Closure|string $from + * @param \Closure|string|null $to + * @return $this + * + * @throws \InvalidArgumentException + */ + public function map($from, $to = null) + { + $this->handler->map($from, $to); + + return $this; + } + + /** + * Set the log level for the given exception type. + * + * @param class-string<\Throwable> $type + * @param \Psr\Log\LogLevel::* $level + * @return $this + */ + public function level($type, $level) + { + $this->handler->level($type, $level); + + return $this; + } + + /** + * Register a closure that should be used to build exception context data. + * + * @param \Closure $contextCallback + * @return $this + */ + public function context(Closure $contextCallback) + { + $this->handler->buildContextUsing($contextCallback); + + return $this; + } + + /** + * Indicate that the given exception type should not be reported. + * + * @param string $class + * @return $this + */ + public function dontReport(string $class) + { + $this->handler->dontReport($class); + + return $this; + } + + /** + * Do not report duplicate exceptions. + * + * @return $this + */ + public function dontReportDuplicates() + { + $this->handler->dontReportDuplicates(); + + return $this; + } + + /** + * Indicate that the given attributes should never be flashed to the session on validation errors. + * + * @param array|string $attributes + * @return $this + */ + public function dontFlash($attributes) + { + $this->handler->dontFlash($attributes); + + return $this; + } +} diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php new file mode 100644 index 000000000000..949dfe4b5806 --- /dev/null +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -0,0 +1,601 @@ + \Illuminate\Auth\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \Illuminate\Auth\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'subscribed' => \Spark\Http\Middleware\VerifyBillableIsSubscribed::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; + + /** + * The custom middleware aliases. + * + * @var array + */ + protected $customAliases = []; + + /** + * Prepend middleware to the application's global middleware stack. + * + * @param array|string $middleware + * @return $this + */ + public function prepend(array|string $middleware) + { + $this->prepends = array_merge( + Arr::wrap($middleware), + $this->prepends + ); + + return $this; + } + + /** + * Append middleware to the application's global middleware stack. + * + * @param array|string $middleware + * @return $this + */ + public function append(array|string $middleware) + { + $this->appends = array_merge( + $this->appends, + Arr::wrap($middleware) + ); + + return $this; + } + + /** + * Remove middleware from the application's global middleware stack. + * + * @param array|string $middleware + * @return $this + */ + public function remove(array|string $middleware) + { + $this->removals = array_merge( + $this->removals, + Arr::wrap($middleware) + ); + + return $this; + } + + /** + * Specify a middleware that should be replaced with another middleware. + * + * @param string $search + * @param string $replace + * @return $this + */ + public function replace(string $search, string $replace) + { + $this->replacements[$search] = $replace; + + return $this; + } + + /** + * Define the global middleware for the application. + * + * @param array $middleware + * @return $this + */ + public function use(array $middleware) + { + $this->global = $middleware; + + return $this; + } + + /** + * Define a middleware group. + * + * @param string $group + * @param array $middleware + * @return $this + */ + public function group(string $group, array $middleware) + { + $this->groups[$group] = $middleware; + + return $this; + } + + /** + * Prepend the given middleware to the specified group. + * + * @param string $group + * @param array|string $middleware + * @return $this + */ + public function prependToGroup(string $group, array|string $middleware) + { + $this->groupPrepends[$group] = array_merge( + Arr::wrap($middleware), + $this->groupPrepends[$group] ?? [] + ); + + return $this; + } + + /** + * Append the given middleware to the specified group. + * + * @param string $group + * @param array|string $middleware + * @return $this + */ + public function appendToGroup(string $group, array|string $middleware) + { + $this->groupAppends[$group] = array_merge( + Arr::wrap($middleware), + $this->groupAppends[$group] ?? [] + ); + + return $this; + } + + /** + * Remove the given middleware from the specified group. + * + * @param string $group + * @param array|string $middleware + * @return $this + */ + public function removeFromGroup(string $group, array|string $middleware) + { + $this->groupRemovals[$group] = array_merge( + Arr::wrap($middleware), + $this->groupRemovals[$group] ?? [] + ); + + return $this; + } + + /** + * Replace the given middleware in the specified group with another middleware. + * + * @param string $group + * @param string $search + * @param string $replace + * @return $this + */ + public function replaceInGroup(string $group, string $search, string $replace) + { + $this->groupReplacements[$group][$search] = $replace; + + return $this; + } + + /** + * Modify the middleware in the "web" group. + * + * @param string $group + * @param array|string $append + * @param array|string $prepend + * @param array|string $remove + * @param array $replace + * @return $this + */ + public function web(array|string $append = [], array|string $prepend = [], array|string $remove = [], array $replace = []) + { + return $this->modifyGroup('web', $append, $prepend, $remove, $replace); + } + + /** + * Modify the middleware in the "api" group. + * + * @param string $group + * @param array|string $append + * @param array|string $prepend + * @param array|string $remove + * @param array $replace + * @return $this + */ + public function api(array|string $append = [], array|string $prepend = [], array|string $remove = [], array $replace = []) + { + return $this->modifyGroup('api', $append, $prepend, $remove, $replace); + } + + /** + * Modify the middleware in the given group. + * + * @param string $group + * @param array|string $append + * @param array|string $prepend + * @param array|string $remove + * @param array $replace + * @return $this + */ + protected function modifyGroup(string $group, array|string $append, array|string $prepend, array|string $remove, array $replace) + { + if (! empty($append)) { + $this->appendToGroup($group, $append); + } + + if (! empty($prepend)) { + $this->prependToGroup($group, $prepend); + } + + if (! empty($remove)) { + $this->removeFromGroup($group, $remove); + } + + if (! empty($replace)) { + foreach ($replace as $search => $replace) { + $this->replaceInGroup($group, $search, $replace); + } + } + + return $this; + } + + /** + * Register the Folio / page middleware for the application. + * + * @param array $middleware + * @return $this + */ + public function pages(array $middleware) + { + $this->pageMiddleware = $middleware; + + return $this; + } + + /** + * Register additional middleware aliases. + * + * @param array $aliases + * @return $this + */ + public function alias(array $aliases) + { + $this->customAliases = $aliases; + + return $this; + } + + /** + * Get the global middleware. + * + * @return array + */ + public function getGlobalMiddleware() + { + $middleware = $this->global ?: array_values(array_filter([ + $this->trustHosts ? \Illuminate\Http\Middleware\TrustHosts::class : null, + \Illuminate\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Http\Middleware\ValidatePostSize::class, + \Illuminate\Foundation\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ])); + + $middleware = array_map(function ($middleware) { + return isset($this->replacements[$middleware]) + ? $this->replacements[$middleware] + : $middleware; + }, $middleware); + + return array_values(array_filter( + array_diff( + array_unique(array_merge($this->prepends, $middleware, $this->appends)), + $this->removals + ) + )); + } + + /** + * Get the middleware groups. + * + * @return array + */ + public function getMiddlewareGroups() + { + $middleware = [ + 'web' => [ + \Illuminate\Cookie\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => array_values(array_filter([ + $this->statefulApi ? \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class : null, + $this->apiLimiter ? 'throttle:'.$this->apiLimiter : null, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ])), + ]; + + $middleware = array_merge($middleware, $this->groups); + + foreach ($middleware as $group => $groupedMiddleware) { + foreach ($groupedMiddleware as $index => $groupMiddleware) { + if (isset($this->groupReplacements[$group][$groupMiddleware])) { + $middleware[$group][$index] = $this->groupReplacements[$group][$groupMiddleware]; + } + } + } + + foreach ($this->groupRemovals as $group => $removals) { + $middleware[$group] = array_values(array_filter( + array_diff($middleware[$group] ?? [], $removals) + )); + } + + foreach ($this->groupPrepends as $group => $prepends) { + $middleware[$group] = array_values(array_filter( + array_unique(array_merge($prepends, $middleware[$group] ?? [])) + )); + } + + foreach ($this->groupAppends as $group => $appends) { + $middleware[$group] = array_values(array_filter( + array_unique(array_merge($middleware[$group] ?? [], $appends)) + )); + } + + return $middleware; + } + + /** + * Configure the behavior of the authentication middleware. + * + * @param callable $redirectTo + * @return $this + */ + public function auth(callable $redirectTo) + { + Authenticate::redirectUsing($redirectTo); + AuthenticateSession::redirectUsing($redirectTo); + AuthenticationException::redirectUsing($redirectTo); + + return $this; + } + + /** + * Configure the behavior of the "guest" middleware. + * + * @param callable $redirectTo + * @return $this + */ + public function guest(callable $redirectTo) + { + RedirectIfAuthenticated::redirectUsing($redirectTo); + + return $this; + } + + /** + * Indicate that the trusted host middleware should be enabled. + * + * @return $this + */ + public function withTrustedHosts() + { + $this->trustHosts = true; + + return $this; + } + + /** + * Indicate that Sanctum's frontend state middleware should be enabled. + * + * @return $this + */ + public function withStatefulApi() + { + $this->statefulApi = true; + + return $this; + } + + /** + * Indicate that the API middleware group's throttling middleware should be enabled. + * + * @param string $limiter + * @param bool $redis + * @return $this + */ + public function withThrottledApi($limiter = 'api', $redis = false) + { + $this->apiLimiter = $limiter; + + if ($redis) { + $this->throttleWithRedis(); + } + + return $this; + } + + /** + * Indicate that Laravel's throttling middleware should use Redis. + * + * @return $this + */ + public function throttleWithRedis() + { + $this->throttleWithRedis = true; + + return $this; + } + + /** + * Get the Folio / page middleware for the application. + * + * @return array + */ + public function getPageMiddleware() + { + return $this->pageMiddleware; + } + + /** + * Get the middleware aliases. + * + * @return array + */ + public function getMiddlewareAliases() + { + return array_merge($this->defaultAliases(), $this->customAliases); + } + + /** + * Get the default middleware aliases. + * + * @return array + */ + protected function defaultAliases() + { + return [ + 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \Illuminate\Auth\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'subscribed' => \Spark\Http\Middleware\VerifyBillableIsSubscribed::class, + 'throttle' => $this->throttleWithRedis + ? \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class + : \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; + } +} diff --git a/src/Illuminate/Foundation/Console/ApiInstallCommand.php b/src/Illuminate/Foundation/Console/ApiInstallCommand.php new file mode 100644 index 000000000000..2a7a072a0461 --- /dev/null +++ b/src/Illuminate/Foundation/Console/ApiInstallCommand.php @@ -0,0 +1,106 @@ +installSanctum(); + + if (file_exists($apiRoutesPath = $this->laravel->basePath('routes/api.php')) && + ! $this->option('force')) { + $this->components->error('API routes file already exists.'); + } else { + $this->components->info('Published API routes file.'); + + copy(__DIR__.'/stubs/api-routes.stub', $apiRoutesPath); + + $this->uncommentApiRoutesFile(); + } + + $this->components->info('API scaffolding installed. Please add the "Laravel\Sanctum\HasApiTokens" trait to your User model.'); + } + + /** + * Uncomment the API routes file in the application bootstrap file. + * + * @return void + */ + protected function uncommentApiRoutesFile() + { + $appBootstrapPath = $this->laravel->bootstrapPath('app.php'); + + $content = file_get_contents($appBootstrapPath); + + if (str_contains($content, '// api: ')) { + (new Filesystem)->replaceInFile( + '// api: ', + 'api: ', + $appBootstrapPath, + ); + } elseif (str_contains($content, 'web: __DIR__.\'/../routes/web.php\',')) { + (new Filesystem)->replaceInFile( + 'web: __DIR__.\'/../routes/web.php\',', + 'web: __DIR__.\'/../routes/web.php\','.PHP_EOL.' api: __DIR__.\'/../routes/api.php\',', + $appBootstrapPath, + ); + } else { + $this->components->warn('Unable to automatically add API route definition to bootstrap file. API route file should be registered manually.'); + + return; + } + } + + /** + * Install Laravel Sanctum into the application. + * + * @return void + */ + protected function installSanctum() + { + $this->requireComposerPackages($this->option('composer'), [ + 'laravel/sanctum:dev-master', + ]); + + $php = (new PhpExecutableFinder())->find(false) ?: 'php'; + + $result = Process::run([ + $php, + defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan', + 'vendor:publish', + '--provider', + 'Laravel\\Sanctum\\SanctumServiceProvider', + ]); + } +} diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php new file mode 100644 index 000000000000..d0b606e4e74e --- /dev/null +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -0,0 +1,94 @@ +laravel->basePath('routes/channels.php')) && + ! $this->option('force')) { + $this->components->error('Broadcasting routes file already exists.'); + } else { + $this->components->info('Published broadcasting routes file.'); + + copy(__DIR__.'/stubs/broadcasting-routes.stub', $broadcastingRoutesPath); + + $this->uncommentChannelsRoutesFile(); + } + + // Install bootstrapping... + if (! file_exists($echoScriptPath = $this->laravel->resourcePath('js/echo.js'))) { + copy(__DIR__.'/stubs/echo-js.stub', $echoScriptPath); + } + + if (file_exists($bootstrapScriptPath = $this->laravel->resourcePath('js/bootstrap.js'))) { + $bootstrapScript = file_get_contents( + $bootstrapScriptPath + ); + + if (! str_contains($bootstrapScript, 'echo.js')) { + file_put_contents( + $bootstrapScriptPath, + $bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub') + ); + } + } + } + + /** + * Uncomment the "channels" routes file in the application bootstrap file. + * + * @return void + */ + protected function uncommentChannelsRoutesFile() + { + $appBootstrapPath = $this->laravel->bootstrapPath('app.php'); + + $content = file_get_contents($appBootstrapPath); + + if (str_contains($content, '// channels: ')) { + (new Filesystem)->replaceInFile( + '// channels: ', + 'channels: ', + $appBootstrapPath, + ); + } elseif (str_contains($content, 'commands: __DIR__.\'/../routes/console.php\',')) { + (new Filesystem)->replaceInFile( + 'commands: __DIR__.\'/../routes/console.php\',', + 'commands: __DIR__.\'/../routes/console.php\','.PHP_EOL.' channels: __DIR__.\'/../routes/channels.php\',', + $appBootstrapPath, + ); + } else { + $this->components->warn('Unable to automatically add channel route definition to bootstrap file. Channel route file should be registered manually.'); + + return; + } + } +} diff --git a/src/Illuminate/Foundation/Console/ClosureCommand.php b/src/Illuminate/Foundation/Console/ClosureCommand.php index c5be75ecc103..ae51801fe0a0 100644 --- a/src/Illuminate/Foundation/Console/ClosureCommand.php +++ b/src/Illuminate/Foundation/Console/ClosureCommand.php @@ -4,12 +4,19 @@ use Closure; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Schedule; +use Illuminate\Support\Traits\ForwardsCalls; use ReflectionFunction; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +/** + * @mixin \Illuminate\Console\Scheduling\Event + */ class ClosureCommand extends Command { + use ForwardsCalls; + /** * The command callback. * @@ -79,4 +86,29 @@ public function describe($description) return $this; } + + /** + * Create a new scheduled event for the command. + * + * @param array $parameters + * @return \Illuminate\Console\Scheduling\Event + */ + public function schedule($parameters = []) + { + return Schedule::command($this->name, $parameters); + } + + /** + * Dynamically proxy calls to a new scheduled event. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + return $this->forwardCallTo($this->schedule(), $method, $parameters); + } } diff --git a/src/Illuminate/Foundation/Console/ConfigPublishCommand.php b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php new file mode 100644 index 000000000000..65eb8c592968 --- /dev/null +++ b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php @@ -0,0 +1,88 @@ +getBaseConfigurationFiles(); + + $name = $this->argument('name'); + + if (! is_null($name) && ! isset($config[$name])) { + $this->components->error('Unrecognized configuration file.'); + + return 1; + } + + foreach ($config as $key => $file) { + if ($key === $name || is_null($name)) { + $this->publish($key, $file, $this->laravel->configPath().'/'.$key.'.php'); + } + } + } + + /** + * Publish the given file to the given destination. + * + * @param string $name + * @param string $file + * @param string $destination + * @return void + */ + protected function publish(string $name, string $file, string $destination) + { + if (file_exists($destination) && ! $this->option('force')) { + $this->components->error("The '{$name}' configuration file already exists."); + + return; + } + + copy($file, $destination); + + $this->components->info("Published '{$name}' configuration file."); + } + + /** + * Get an array containing the base configuration files. + * + * @return array + */ + protected function getBaseConfigurationFiles() + { + $config = []; + + foreach (Finder::create()->files()->name('*.php')->in(__DIR__.'/../../../../config') as $file) { + $config[basename($file->getRealPath(), '.php')] = $file->getRealPath(); + } + + return collect($config)->sortKeys()->all(); + } +} diff --git a/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php b/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php new file mode 100644 index 000000000000..ebd8b7e7b3d1 --- /dev/null +++ b/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php @@ -0,0 +1,44 @@ +phpBinary(), $composer, 'require']; + } + + $command = array_merge( + $command ?? ['composer', 'require'], + $packages, + ); + + return ! (new Process($command, $this->laravel->basePath(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) + ->setTimeout(null) + ->run(function ($type, $output) { + $this->output->write($output); + }); + } + + /** + * Get the path to the appropriate PHP binary. + * + * @return string + */ + protected function phpBinary() + { + return (new PhpExecutableFinder())->find(false) ?: 'php'; + } +} diff --git a/src/Illuminate/Foundation/Console/Kernel.php b/src/Illuminate/Foundation/Console/Kernel.php index 227f7caea513..21a82f7c927b 100644 --- a/src/Illuminate/Foundation/Console/Kernel.php +++ b/src/Illuminate/Foundation/Console/Kernel.php @@ -67,6 +67,20 @@ class Kernel implements KernelContract */ protected $commands = []; + /** + * The paths where Artisan commands should be automatically discovered. + * + * @var array + */ + protected $commandPaths = []; + + /** + * The paths where Artisan "routes" should be automatically discovered. + * + * @var array + */ + protected $commandRoutePaths = []; + /** * Indicates if the Closure commands have been loaded. * @@ -74,6 +88,13 @@ class Kernel implements KernelContract */ protected $commandsLoaded = false; + /** + * The commands paths that have been "loaded". + * + * @var array + */ + protected $loadedPaths = []; + /** * All of the registered command duration handlers. * @@ -123,8 +144,6 @@ public function __construct(Application $app, Dispatcher $events) if (! $this->app->runningUnitTests()) { $this->rerouteSymfonyCommandEvents(); } - - $this->defineConsoleSchedule(); }); } @@ -156,30 +175,6 @@ public function rerouteSymfonyCommandEvents() return $this; } - /** - * Define the application's command schedule. - * - * @return void - */ - protected function defineConsoleSchedule() - { - $this->app->singleton(Schedule::class, function ($app) { - return tap(new Schedule($this->scheduleTimezone()), function ($schedule) { - $this->schedule($schedule->useCache($this->scheduleCache())); - }); - }); - } - - /** - * Get the name of the cache store that should manage scheduling mutexes. - * - * @return string - */ - protected function scheduleCache() - { - return $this->app['config']->get('cache.schedule_store', Env::get('SCHEDULE_CACHE_DRIVER')); - } - /** * Run the console application. * @@ -280,6 +275,18 @@ protected function schedule(Schedule $schedule) // } + /** + * Resolve a console schedule instance. + * + * @return \Illuminate\Console\Scheduling\Schedule + */ + public function resolveConsoleSchedule() + { + return tap(new Schedule($this->scheduleTimezone()), function ($schedule) { + $this->schedule($schedule->useCache($this->scheduleCache())); + }); + } + /** * Get the timezone that should be used by default for scheduled events. * @@ -292,6 +299,16 @@ protected function scheduleTimezone() return $config->get('app.schedule_timezone', $config->get('app.timezone')); } + /** + * Get the name of the cache store that should manage scheduling mutexes. + * + * @return string + */ + protected function scheduleCache() + { + return $this->app['config']->get('cache.schedule_store', Env::get('SCHEDULE_CACHE_DRIVER')); + } + /** * Register the commands for the application. * @@ -338,6 +355,10 @@ protected function load($paths) return; } + $this->loadedPaths = array_values( + array_unique(array_merge($this->loadedPaths, $paths)) + ); + $namespace = $this->app->getNamespace(); foreach ((new Finder)->in($paths)->files() as $file) { @@ -452,10 +473,32 @@ public function bootstrap() if (! $this->commandsLoaded) { $this->commands(); + if ($this->shouldDiscoverCommands()) { + $this->discoverCommands(); + } + $this->commandsLoaded = true; } } + /** + * Discover the commands that should be automatically loaded. + * + * @return void + */ + protected function discoverCommands() + { + foreach ($this->commandPaths as $path) { + $this->load($path); + } + + foreach ($this->commandRoutePaths as $path) { + if (file_exists($path)) { + require $path; + } + } + } + /** * Bootstrap the application without booting service providers. * @@ -470,6 +513,16 @@ public function bootstrapWithoutBootingProviders() ); } + /** + * Determine if the kernel should discover commands. + * + * @return bool + */ + protected function shouldDiscoverCommands() + { + return get_class($this) === __CLASS__; + } + /** * Get the Artisan application instance. * @@ -502,6 +555,45 @@ public function setArtisan($artisan) $this->artisan = $artisan; } + /** + * Set the Artisan commands provided by the application. + * + * @param array $commands + * @return $this + */ + public function addCommands(array $commands) + { + $this->commands = array_values(array_unique(array_merge($this->commands, $commands))); + + return $this; + } + + /** + * Set the paths that should have their Artisan commands automatically discovered. + * + * @param array $paths + * @return $this + */ + public function addCommandPaths(array $paths) + { + $this->commandPaths = array_values(array_unique(array_merge($this->commandPaths, $paths))); + + return $this; + } + + /** + * Set the paths that should have their Artisan "routes" automatically discovered. + * + * @param array $paths + * @return $this + */ + public function addCommandRoutePaths(array $paths) + { + $this->commandRoutePaths = array_values(array_unique(array_merge($this->commandRoutePaths, $paths))); + + return $this; + } + /** * Get the bootstrap classes for the application. * diff --git a/src/Illuminate/Foundation/Console/OptimizeClearCommand.php b/src/Illuminate/Foundation/Console/OptimizeClearCommand.php index 7e932d7924d9..c75ab3a95902 100644 --- a/src/Illuminate/Foundation/Console/OptimizeClearCommand.php +++ b/src/Illuminate/Foundation/Console/OptimizeClearCommand.php @@ -32,12 +32,12 @@ public function handle() $this->components->info('Clearing cached bootstrap files.'); collect([ - 'events' => fn () => $this->callSilent('event:clear') == 0, - 'views' => fn () => $this->callSilent('view:clear') == 0, 'cache' => fn () => $this->callSilent('cache:clear') == 0, - 'route' => fn () => $this->callSilent('route:clear') == 0, - 'config' => fn () => $this->callSilent('config:clear') == 0, 'compiled' => fn () => $this->callSilent('clear-compiled') == 0, + 'config' => fn () => $this->callSilent('config:clear') == 0, + 'events' => fn () => $this->callSilent('event:clear') == 0, + 'route' => fn () => $this->callSilent('route:clear') == 0, + 'views' => fn () => $this->callSilent('view:clear') == 0, ])->each(fn ($task, $description) => $this->components->task($description, $task)); $this->newLine(); diff --git a/src/Illuminate/Foundation/Console/OptimizeCommand.php b/src/Illuminate/Foundation/Console/OptimizeCommand.php index 10f264628542..0bcf3e97a3a6 100644 --- a/src/Illuminate/Foundation/Console/OptimizeCommand.php +++ b/src/Illuminate/Foundation/Console/OptimizeCommand.php @@ -20,7 +20,7 @@ class OptimizeCommand extends Command * * @var string */ - protected $description = 'Cache the framework bootstrap files'; + protected $description = 'Cache framework bootstrap, configuration, and metadata to increase performance'; /** * Execute the console command. @@ -29,11 +29,13 @@ class OptimizeCommand extends Command */ public function handle() { - $this->components->info('Caching the framework bootstrap files'); + $this->components->info('Caching framework bootstrap, configuration, and metadata.'); collect([ 'config' => fn () => $this->callSilent('config:cache') == 0, + 'events' => fn () => $this->callSilent('event:cache') == 0, 'routes' => fn () => $this->callSilent('route:cache') == 0, + 'views' => fn () => $this->callSilent('view:cache') == 0, ])->each(fn ($task, $description) => $this->components->task($description, $task)); $this->newLine(); diff --git a/src/Illuminate/Foundation/Console/ProviderMakeCommand.php b/src/Illuminate/Foundation/Console/ProviderMakeCommand.php index 54d7e4c06486..d955c8254d7b 100644 --- a/src/Illuminate/Foundation/Console/ProviderMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ProviderMakeCommand.php @@ -3,6 +3,7 @@ namespace Illuminate\Foundation\Console; use Illuminate\Console\GeneratorCommand; +use Illuminate\Support\ServiceProvider; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputOption; @@ -30,6 +31,29 @@ class ProviderMakeCommand extends GeneratorCommand */ protected $type = 'Provider'; + /** + * Execute the console command. + * + * @return bool|null + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function handle() + { + $result = parent::handle(); + + if ($result === false) { + return $result; + } + + ServiceProvider::addProviderToBootstrapFile( + $this->qualifyClass($this->getNameInput()), + $this->laravel->getBootstrapProvidersPath(), + ); + + return $result; + } + /** * Get the stub file for the generator. * diff --git a/src/Illuminate/Foundation/Console/stubs/api-routes.stub b/src/Illuminate/Foundation/Console/stubs/api-routes.stub new file mode 100644 index 000000000000..aee5f3636395 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/api-routes.stub @@ -0,0 +1,20 @@ +user(); +})->middleware(Authenticate::using('sanctum')); diff --git a/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub b/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub new file mode 100644 index 000000000000..5d451e1fae88 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub @@ -0,0 +1,18 @@ +id === (int) $id; +}); diff --git a/src/Illuminate/Foundation/Console/stubs/echo-bootstrap-js.stub b/src/Illuminate/Foundation/Console/stubs/echo-bootstrap-js.stub new file mode 100644 index 000000000000..e932c573c3a3 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-bootstrap-js.stub @@ -0,0 +1,7 @@ +/** + * Echo exposes an expressive API for subscribing to channels and listening + * for events that are broadcast by Laravel. Echo and event broadcasting + * allow your team to quickly build robust real-time web applications. + */ + +import './echo'; diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js.stub b/src/Illuminate/Foundation/Console/stubs/echo-js.stub new file mode 100644 index 000000000000..520887d57d4a --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-js.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 ?? 'mt1', + wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, + wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, + wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, + forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', + enabledTransports: ['ws', 'wss'], +}); diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index c877e772df3c..facb54fe54c1 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -80,6 +80,20 @@ class Handler implements ExceptionHandlerContract */ protected $levels = []; + /** + * The callbacks that should be used to throttle reportable exceptions. + * + * @var array + */ + protected $throttleCallbacks = []; + + /** + * The callbacks that should be used to build exception context data. + * + * @var array + */ + protected $contextCallbacks = []; + /** * The callbacks that should be used during rendering. * @@ -232,6 +246,19 @@ public function map($from, $to = null) return $this; } + /** + * Indicate that the given exception type should not be reported. + * + * Alias of "ignore". + * + * @param string $class + * @return $this + */ + public function dontReport(string $class) + { + return $this->ignore($class); + } + /** * Indicate that the given exception type should not be reported. * @@ -245,6 +272,21 @@ public function ignore(string $class) return $this; } + /** + * Indicate that the given attributes should never be flashed to the session on validation errors. + * + * @param array|string $attributes + * @return $this + */ + public function dontFlash($attributes) + { + $this->dontFlash = array_values(array_unique( + array_merge($this->dontFlash, Arr::wrap($attributes)) + )); + + return $this; + } + /** * Set the log level for the given exception type. * @@ -373,9 +415,38 @@ protected function shouldntReport(Throwable $e) */ protected function throttle(Throwable $e) { + foreach ($this->throttleCallbacks as $throttleCallback) { + foreach ($this->firstClosureParameterTypes($throttleCallback) as $type) { + if (is_a($e, $type)) { + $response = $throttleCallback($e); + + if (! is_null($response)) { + return $response; + } + } + } + } + return Limit::none(); } + /** + * Specify the callback that should be used to throttle reportable exceptions. + * + * @param callable $throttleUsing + * @return $this + */ + public function throttleUsing(callable $throttleUsing) + { + if (! $throttleUsing instanceof Closure) { + $throttleUsing = Closure::fromCallable($throttleUsing); + } + + $this->throttleCallbacks[] = $throttleUsing; + + return $this; + } + /** * Remove the given exception class from the list of exceptions that should be ignored. * @@ -416,11 +487,17 @@ protected function buildExceptionContext(Throwable $e) */ protected function exceptionContext(Throwable $e) { + $context = []; + if (method_exists($e, 'context')) { - return $e->context(); + $context = $e->context(); } - return []; + foreach ($this->contextCallbacks as $callback) { + $context = array_merge($context, $callback($e, $context)); + } + + return $context; } /** @@ -439,6 +516,19 @@ protected function context() } } + /** + * Register a closure that should be used to build exception context data. + * + * @param \Closure $contextCallback + * @return $this + */ + public function buildContextUsing(Closure $contextCallback) + { + $this->contextCallbacks[] = $contextCallback; + + return $this; + } + /** * Render an exception into an HTTP response. * @@ -567,7 +657,7 @@ protected function unauthenticated($request, AuthenticationException $exception) { return $this->shouldReturnJson($request, $exception) ? response()->json(['message' => $exception->getMessage()], 401) - : redirect()->guest($exception->redirectTo() ?? route('login')); + : redirect()->guest($exception->redirectTo($request) ?? route('login')); } /** diff --git a/src/Illuminate/Foundation/Http/Kernel.php b/src/Illuminate/Foundation/Http/Kernel.php index 51ad7929aa55..2ddb1c58c8bf 100644 --- a/src/Illuminate/Foundation/Http/Kernel.php +++ b/src/Illuminate/Foundation/Http/Kernel.php @@ -509,6 +509,31 @@ protected function renderException($request, Throwable $e) return $this->app[ExceptionHandler::class]->render($request, $e); } + /** + * Get the application's global middleware. + * + * @return array + */ + public function getGlobalMiddleware() + { + return $this->middleware; + } + + /** + * Set the application's global middleware. + * + * @param array $middleware + * @return $this + */ + public function setGlobalMiddleware(array $middleware) + { + $this->middleware = $middleware; + + $this->syncMiddlewareToRouter(); + + return $this; + } + /** * Get the application's route middleware groups. * @@ -519,6 +544,21 @@ public function getMiddlewareGroups() return $this->middlewareGroups; } + /** + * Set the application's middleware groups. + * + * @param array $groups + * @return $this + */ + public function setMiddlewareGroups(array $groups) + { + $this->middlewareGroups = $groups; + + $this->syncMiddlewareToRouter(); + + return $this; + } + /** * Get the application's route middleware aliases. * @@ -541,6 +581,21 @@ public function getMiddlewareAliases() return array_merge($this->routeMiddleware, $this->middlewareAliases); } + /** + * Set the application's route middleware aliases. + * + * @param array $aliases + * @return $this + */ + public function setMiddlewareAliases(array $aliases) + { + $this->middlewareAliases = $aliases; + + $this->syncMiddlewareToRouter(); + + return $this; + } + /** * Get the Laravel application instance. * diff --git a/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php b/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php index 7ffcd5eaa1f7..a6e654659a9e 100644 --- a/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php +++ b/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php @@ -3,24 +3,34 @@ namespace Illuminate\Foundation\Http\Middleware; use Closure; +use Illuminate\Support\Arr; class TrimStrings extends TransformsRequest { /** - * All of the registered skip callbacks. + * The attributes that should not be trimmed. + * + * @var array + */ + protected $except = [ + 'current_password', + 'password', + 'password_confirmation', + ]; + + /** + * The globally ignored attributes that should not be trimmed. * * @var array */ - protected static $skipCallbacks = []; + protected static $neverTrim = []; /** - * The attributes that should not be trimmed. + * All of the registered skip callbacks. * - * @var array + * @var array */ - protected $except = [ - // - ]; + protected static $skipCallbacks = []; /** * Handle an incoming request. @@ -49,13 +59,28 @@ public function handle($request, Closure $next) */ protected function transform($key, $value) { - if (in_array($key, $this->except, true) || ! is_string($value)) { + $except = array_merge($this->except, static::$neverTrim); + + if (in_array($key, $except, true) || ! is_string($value)) { return $value; } return preg_replace('~^[\s\x{FEFF}\x{200B}]+|[\s\x{FEFF}\x{200B}]+$~u', '', $value) ?? trim($value); } + /** + * Indicate that the given attributes should never be trimmed. + * + * @param array|string $attributes + * @return void + */ + public static function except($attributes) + { + static::$neverTrim = array_values(array_unique( + array_merge(static::$neverTrim, Arr::wrap($attributes)) + )); + } + /** * Register a callback that instructs the middleware to be skipped. * diff --git a/src/Illuminate/Foundation/Http/Middleware/ValidateCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/ValidateCsrfToken.php new file mode 100644 index 000000000000..f49d6141f7d7 --- /dev/null +++ b/src/Illuminate/Foundation/Http/Middleware/ValidateCsrfToken.php @@ -0,0 +1,11 @@ +getPostMaxSize(); - - if ($max > 0 && $request->server('CONTENT_LENGTH') > $max) { - throw new PostTooLargeException; - } - - return $next($request); - } - - /** - * Determine the server 'post_max_size' as bytes. - * - * @return int - */ - protected function getPostMaxSize() - { - if (is_numeric($postMaxSize = ini_get('post_max_size'))) { - return (int) $postMaxSize; - } - - $metric = strtoupper(substr($postMaxSize, -1)); - $postMaxSize = (int) $postMaxSize; - - return match ($metric) { - 'K' => $postMaxSize * 1024, - 'M' => $postMaxSize * 1048576, - 'G' => $postMaxSize * 1073741824, - default => $postMaxSize, - }; - } + // } diff --git a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php index 69faa52726d2..3111be5a9ba3 100644 --- a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php +++ b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -10,6 +10,7 @@ use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Session\TokenMismatchException; +use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; use Symfony\Component\HttpFoundation\Cookie; @@ -38,6 +39,13 @@ class VerifyCsrfToken */ protected $except = []; + /** + * The globally ignored URIs that should be excluded from CSRF verification. + * + * @var array + */ + protected static $neverVerify = []; + /** * Indicates whether the XSRF-TOKEN cookie should be set on the response. * @@ -114,7 +122,7 @@ protected function runningUnitTests() */ protected function inExceptArray($request) { - foreach ($this->except as $except) { + foreach (array_merge($this->except, static::$neverVerify) as $except) { if ($except !== '/') { $except = trim($except, '/'); } @@ -215,6 +223,19 @@ protected function newCookie($request, $config) ); } + /** + * Indicate that the given URIs should be excluded from CSRF verification. + * + * @param array|string $paths + * @return void + */ + public static function except($paths) + { + static::$neverVerify = array_values(array_unique( + array_merge(static::$neverVerify, Arr::wrap($paths)) + )); + } + /** * Determine if the cookie contents should be serialized. * diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index 7269e90ede95..eab3b0d97adb 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -28,6 +28,8 @@ use Illuminate\Database\Console\TableCommand as DatabaseTableCommand; use Illuminate\Database\Console\WipeCommand; use Illuminate\Foundation\Console\AboutCommand; +use Illuminate\Foundation\Console\ApiInstallCommand; +use Illuminate\Foundation\Console\BroadcastingInstallCommand; use Illuminate\Foundation\Console\CastMakeCommand; use Illuminate\Foundation\Console\ChannelListCommand; use Illuminate\Foundation\Console\ChannelMakeCommand; @@ -35,6 +37,7 @@ use Illuminate\Foundation\Console\ComponentMakeCommand; use Illuminate\Foundation\Console\ConfigCacheCommand; use Illuminate\Foundation\Console\ConfigClearCommand; +use Illuminate\Foundation\Console\ConfigPublishCommand; use Illuminate\Foundation\Console\ConfigShowCommand; use Illuminate\Foundation\Console\ConsoleMakeCommand; use Illuminate\Foundation\Console\DocsCommand; @@ -169,11 +172,14 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid * @var array */ protected $devCommands = [ + 'ApiInstall' => ApiInstallCommand::class, + 'BroadcastingInstall' => BroadcastingInstallCommand::class, 'CacheTable' => CacheTableCommand::class, 'CastMake' => CastMakeCommand::class, 'ChannelList' => ChannelListCommand::class, 'ChannelMake' => ChannelMakeCommand::class, 'ComponentMake' => ComponentMakeCommand::class, + 'ConfigPublish' => ConfigPublishCommand::class, 'ConsoleMake' => ConsoleMakeCommand::class, 'ControllerMake' => ControllerMakeCommand::class, 'Docs' => DocsCommand::class, @@ -356,6 +362,18 @@ protected function registerConfigClearCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerConfigPublishCommand() + { + $this->app->singleton(ConfigPublishCommand::class, function ($app) { + return new ConfigPublishCommand; + }); + } + /** * Register the command. * diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php index 1d5ed5ff03c8..2134171e0ee7 100644 --- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -2,6 +2,8 @@ namespace Illuminate\Foundation\Providers; +use Illuminate\Console\Scheduling\Schedule; +use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\MaintenanceMode as MaintenanceModeContract; @@ -69,6 +71,7 @@ public function register() { parent::register(); + $this->registerConsoleSchedule(); $this->registerDumper(); $this->registerRequestValidation(); $this->registerRequestSignatureValidation(); @@ -76,6 +79,18 @@ public function register() $this->registerMaintenanceModeManager(); } + /** + * Register the console schedule implementation. + * + * @return void + */ + public function registerConsoleSchedule() + { + $this->app->singleton(Schedule::class, function ($app) { + return $app->make(ConsoleKernel::class)->resolveConsoleSchedule(); + }); + } + /** * Register a var dumper (with source) to debug variables. * diff --git a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php index d966c5fd6d23..6dd42b16ff9c 100644 --- a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php @@ -2,7 +2,10 @@ namespace Illuminate\Foundation\Support\Providers; +use Illuminate\Auth\Events\Registered; +use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Events\DiscoverEvents; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; @@ -53,6 +56,10 @@ public function register() $model::observe($observers); } }); + + $this->booted(function () { + $this->configureEmailVerification(); + }); } /** @@ -113,7 +120,7 @@ protected function discoveredEvents() */ public function shouldDiscoverEvents() { - return false; + return get_class($this) === __CLASS__; } /** @@ -156,4 +163,17 @@ protected function eventDiscoveryBasePath() { return base_path(); } + + /** + * Configure the proper event listeners for email verification. + * + * @return void + */ + protected function configureEmailVerification() + { + if (! isset($this->listen[Registered::class]) || + ! in_array(SendEmailVerificationNotification::class, Arr::wrap($this->listen[Registered::class]))) { + Event::listen(Registered::class, SendEmailVerificationNotification::class); + } + } } diff --git a/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php index c8679e51ede6..9c9ba4f90be6 100644 --- a/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php @@ -29,6 +29,13 @@ class RouteServiceProvider extends ServiceProvider */ protected $loadRoutesUsing; + /** + * The global callback that should be used to load the application's routes. + * + * @var \Closure|null + */ + protected static $alwaysLoadRoutesUsing; + /** * Register any application services. * @@ -75,6 +82,17 @@ protected function routes(Closure $routesCallback) return $this; } + /** + * Register the callback that will be used to load the application's routes. + * + * @param \Closure $routesCallback + * @return void + */ + public static function loadRoutesUsing(Closure $routesCallback) + { + static::$alwaysLoadRoutesUsing = $routesCallback; + } + /** * Set the root controller namespace for the application. * @@ -116,7 +134,9 @@ protected function loadCachedRoutes() */ protected function loadRoutes() { - if (! is_null($this->loadRoutesUsing)) { + if (! is_null(static::$alwaysLoadRoutesUsing)) { + $this->app->call(static::$alwaysLoadRoutesUsing); + } elseif (! is_null($this->loadRoutesUsing)) { $this->app->call($this->loadRoutesUsing); } elseif (method_exists($this, 'map')) { $this->app->call([$this, 'map']); diff --git a/src/Illuminate/Http/Middleware/TrustHosts.php b/src/Illuminate/Http/Middleware/TrustHosts.php index 00a5a44c2e92..c2657f02756e 100644 --- a/src/Illuminate/Http/Middleware/TrustHosts.php +++ b/src/Illuminate/Http/Middleware/TrustHosts.php @@ -5,7 +5,7 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Http\Request; -abstract class TrustHosts +class TrustHosts { /** * The application instance. @@ -30,7 +30,12 @@ public function __construct(Application $app) * * @return array */ - abstract public function hosts(); + public function hosts() + { + return [ + $this->allSubdomainsOfApplicationUrl(), + ]; + } /** * Handle the incoming request. diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 81906c1f1951..12a2f9b3692e 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -12,14 +12,18 @@ class TrustProxies * * @var array|string|null */ - protected $proxies; + protected $proxies = '*'; /** * The proxy header mappings. * * @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; + 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_AWS_ELB; /** * Handle an incoming request. diff --git a/src/Illuminate/Http/Middleware/ValidatePostSize.php b/src/Illuminate/Http/Middleware/ValidatePostSize.php new file mode 100644 index 000000000000..bfa620a85f46 --- /dev/null +++ b/src/Illuminate/Http/Middleware/ValidatePostSize.php @@ -0,0 +1,52 @@ +getPostMaxSize(); + + if ($max > 0 && $request->server('CONTENT_LENGTH') > $max) { + throw new PostTooLargeException; + } + + return $next($request); + } + + /** + * Determine the server 'post_max_size' as bytes. + * + * @return int + */ + protected function getPostMaxSize() + { + if (is_numeric($postMaxSize = ini_get('post_max_size'))) { + return (int) $postMaxSize; + } + + $metric = strtoupper(substr($postMaxSize, -1)); + + $postMaxSize = (int) $postMaxSize; + + return match ($metric) { + 'K' => $postMaxSize * 1024, + 'M' => $postMaxSize * 1048576, + 'G' => $postMaxSize * 1073741824, + default => $postMaxSize, + }; + } +} diff --git a/src/Illuminate/Notifications/Console/NotificationTableCommand.php b/src/Illuminate/Notifications/Console/NotificationTableCommand.php index 4df79e5371d8..d2329f914620 100644 --- a/src/Illuminate/Notifications/Console/NotificationTableCommand.php +++ b/src/Illuminate/Notifications/Console/NotificationTableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'notifications:table')] +#[AsCommand(name: 'make:notifications-table')] class NotificationTableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class NotificationTableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'notifications:table'; + protected $name = 'make:notifications-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['notifications:table']; /** * The console command description. diff --git a/src/Illuminate/Queue/Console/BatchesTableCommand.php b/src/Illuminate/Queue/Console/BatchesTableCommand.php index 56c1158ab903..92a83ef5ea40 100644 --- a/src/Illuminate/Queue/Console/BatchesTableCommand.php +++ b/src/Illuminate/Queue/Console/BatchesTableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'queue:batches-table')] +#[AsCommand(name: 'make:queue-batches-table')] class BatchesTableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class BatchesTableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'queue:batches-table'; + protected $name = 'make:queue-batches-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['queue:batches-table']; /** * The console command description. diff --git a/src/Illuminate/Queue/Console/FailedTableCommand.php b/src/Illuminate/Queue/Console/FailedTableCommand.php index 041196536443..3e6f00751c6f 100644 --- a/src/Illuminate/Queue/Console/FailedTableCommand.php +++ b/src/Illuminate/Queue/Console/FailedTableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'queue:failed-table')] +#[AsCommand(name: 'make:queue-failed-table')] class FailedTableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class FailedTableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'queue:failed-table'; + protected $name = 'make:queue-failed-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['queue:failed-table']; /** * The console command description. diff --git a/src/Illuminate/Queue/Console/TableCommand.php b/src/Illuminate/Queue/Console/TableCommand.php index bb1fe00a9421..24e5be54d962 100644 --- a/src/Illuminate/Queue/Console/TableCommand.php +++ b/src/Illuminate/Queue/Console/TableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'queue:table')] +#[AsCommand(name: 'make:queue-table')] class TableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class TableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'queue:table'; + protected $name = 'make:queue-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['queue:table']; /** * The console command description. diff --git a/src/Illuminate/Routing/Middleware/ValidateSignature.php b/src/Illuminate/Routing/Middleware/ValidateSignature.php index b7bf23b31fc7..2ce5ae154ccb 100644 --- a/src/Illuminate/Routing/Middleware/ValidateSignature.php +++ b/src/Illuminate/Routing/Middleware/ValidateSignature.php @@ -17,6 +17,13 @@ class ValidateSignature // ]; + /** + * The globally ignored parameters. + * + * @var array + */ + protected static $neverValidate = []; + /** * Specify that the URL signature is for a relative URL. * @@ -85,6 +92,19 @@ protected function parseArguments(array $args) $args ); - return [$relative, $ignore]; + return [$relative, array_merge($ignore, static::$neverValidate)]; + } + + /** + * Indicate that the given parameters should be ignored during signature validation. + * + * @param array|string $parameters + * @return void + */ + public static function except($parameters) + { + static::$neverValidate = array_values(array_unique( + array_merge(static::$neverValidate, Arr::wrap($parameters)) + )); } } diff --git a/src/Illuminate/Session/Console/SessionTableCommand.php b/src/Illuminate/Session/Console/SessionTableCommand.php index c020f2ac64b7..d162f3e7ce37 100644 --- a/src/Illuminate/Session/Console/SessionTableCommand.php +++ b/src/Illuminate/Session/Console/SessionTableCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\MigrationGeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand(name: 'session:table')] +#[AsCommand(name: 'make:session-table')] class SessionTableCommand extends MigrationGeneratorCommand { /** @@ -13,7 +13,14 @@ class SessionTableCommand extends MigrationGeneratorCommand * * @var string */ - protected $name = 'session:table'; + protected $name = 'make:session-table'; + + /** + * The console command name aliases. + * + * @var array + */ + protected $aliases = ['session:table']; /** * The console command description. diff --git a/src/Illuminate/Session/Middleware/AuthenticateSession.php b/src/Illuminate/Session/Middleware/AuthenticateSession.php index 43982374b456..d525e8f535e1 100644 --- a/src/Illuminate/Session/Middleware/AuthenticateSession.php +++ b/src/Illuminate/Session/Middleware/AuthenticateSession.php @@ -17,6 +17,13 @@ class AuthenticateSession implements AuthenticatesSessions */ protected $auth; + /** + * The callback that should be used to generate the authentication redirect path. + * + * @var callable + */ + protected static $redirectToCallback; + /** * Create a new middleware instance. * @@ -118,6 +125,19 @@ protected function guard() */ protected function redirectTo(Request $request) { - // + if (static::$redirectToCallback) { + return call_user_func(static::$redirectToCallback, $request); + } + } + + /** + * Specify the callback that should be used to generate the redirect path. + * + * @param callable $redirectToCallback + * @return void + */ + public static function redirectUsing(callable $redirectToCallback) + { + static::$redirectToCallback = $redirectToCallback; } } diff --git a/src/Illuminate/Support/Facades/App.php b/src/Illuminate/Support/Facades/App.php index a2b23eb49453..bd53f28ee085 100755 --- a/src/Illuminate/Support/Facades/App.php +++ b/src/Illuminate/Support/Facades/App.php @@ -3,6 +3,7 @@ namespace Illuminate\Support\Facades; /** + * @method static void configure(string|null $baseDirectory = null) * @method static string version() * @method static void bootstrapWith(string[] $bootstrappers) * @method static void afterLoadingEnvironment(\Closure $callback) @@ -14,6 +15,7 @@ * @method static \Illuminate\Foundation\Application useAppPath(string $path) * @method static string basePath(string $path = '') * @method static string bootstrapPath(string $path = '') + * @method static string getBootstrapProvidersPath() * @method static \Illuminate\Foundation\Application useBootstrapPath(string $path) * @method static string configPath(string $path = '') * @method static \Illuminate\Foundation\Application useConfigPath(string $path) @@ -41,6 +43,7 @@ * @method static bool runningConsoleCommand(string|array ...$commands) * @method static bool runningUnitTests() * @method static bool hasDebugModeEnabled() + * @method static void registered(callable $callback) * @method static void registerConfiguredProviders() * @method static \Illuminate\Support\ServiceProvider register(\Illuminate\Support\ServiceProvider|string $provider, bool $force = false) * @method static \Illuminate\Support\ServiceProvider|null getProvider(\Illuminate\Support\ServiceProvider|string $provider) @@ -56,6 +59,8 @@ * @method static void booting(callable $callback) * @method static void booted(callable $callback) * @method static \Symfony\Component\HttpFoundation\Response handle(\Symfony\Component\HttpFoundation\Request $request, int $type = 1, bool $catch = true) + * @method static void handleRequest(\Illuminate\Http\Request $request) + * @method static int handleCommand(\Symfony\Component\Console\Input\InputInterface $input) * @method static bool shouldSkipMiddleware() * @method static string getCachedServicesPath() * @method static string getCachedPackagesPath() diff --git a/src/Illuminate/Support/Facades/Artisan.php b/src/Illuminate/Support/Facades/Artisan.php index 4a0bab379582..d3a0d9d4ea81 100755 --- a/src/Illuminate/Support/Facades/Artisan.php +++ b/src/Illuminate/Support/Facades/Artisan.php @@ -9,6 +9,7 @@ * @method static void terminate(\Symfony\Component\Console\Input\InputInterface $input, int $status) * @method static void whenCommandLifecycleIsLongerThan(\DateTimeInterface|\Carbon\CarbonInterval|float|int $threshold, callable $handler) * @method static \Illuminate\Support\Carbon|null commandStartedAt() + * @method static \Illuminate\Console\Scheduling\Schedule resolveConsoleSchedule() * @method static \Illuminate\Foundation\Console\ClosureCommand command(string $signature, \Closure $callback) * @method static void registerCommand(\Symfony\Component\Console\Command\Command $command) * @method static int call(string $command, array $parameters = [], \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer = null) @@ -18,6 +19,9 @@ * @method static void bootstrap() * @method static void bootstrapWithoutBootingProviders() * @method static void setArtisan(\Illuminate\Console\Application|null $artisan) + * @method static \Illuminate\Foundation\Console\Kernel addCommands(array $commands) + * @method static \Illuminate\Foundation\Console\Kernel addCommandPaths(array $paths) + * @method static \Illuminate\Foundation\Console\Kernel addCommandRoutePaths(array $paths) * * @see \Illuminate\Foundation\Console\Kernel */ diff --git a/src/Illuminate/Support/Facades/Facade.php b/src/Illuminate/Support/Facades/Facade.php index 1dbdc321543b..caba479ee65e 100755 --- a/src/Illuminate/Support/Facades/Facade.php +++ b/src/Illuminate/Support/Facades/Facade.php @@ -303,6 +303,7 @@ public static function defaultAliases() 'Request' => Request::class, 'Response' => Response::class, 'Route' => Route::class, + 'Schedule' => Schedule::class, 'Schema' => Schema::class, 'Session' => Session::class, 'Storage' => Storage::class, diff --git a/src/Illuminate/Support/Facades/Schedule.php b/src/Illuminate/Support/Facades/Schedule.php new file mode 100644 index 000000000000..04c7e0ed516d --- /dev/null +++ b/src/Illuminate/Support/Facades/Schedule.php @@ -0,0 +1,35 @@ +getBootstrapProvidersPath(); + + if (! file_exists($path)) { + return false; + } + + $providers = collect(require $path) + ->merge([$provider]) + ->unique() + ->sort() + ->values() + ->map(fn ($p) => ' '.$p.'::class,') + ->implode(PHP_EOL); + + $content = 'shouldReceive('expectsJson')->andReturn(false); + $nextParam = null; $next = function ($param) use (&$nextParam) { diff --git a/tests/Cookie/Middleware/EncryptCookiesTest.php b/tests/Cookie/Middleware/EncryptCookiesTest.php index 06f3c6c3b05d..352f117db901 100644 --- a/tests/Cookie/Middleware/EncryptCookiesTest.php +++ b/tests/Cookie/Middleware/EncryptCookiesTest.php @@ -42,6 +42,8 @@ protected function setUp(): void }); $this->router = new Router(new Dispatcher, $this->container); + + EncryptCookiesTestMiddleware::except(['globally_unencrypted_cookie']); } public function testSetCookieEncryption() @@ -54,7 +56,7 @@ public function testSetCookieEncryption() $response = $this->router->dispatch(Request::create($this->setCookiePath, 'GET')); $cookies = $response->headers->getCookies(); - $this->assertCount(4, $cookies); + $this->assertCount(5, $cookies); $this->assertSame('encrypted_cookie', $cookies[0]->getName()); $this->assertNotSame('value', $cookies[0]->getValue()); $this->assertSame('encrypted[array_cookie]', $cookies[1]->getName()); @@ -62,6 +64,8 @@ public function testSetCookieEncryption() $this->assertSame('encrypted[nested][array_cookie]', $cookies[2]->getName()); $this->assertSame('unencrypted_cookie', $cookies[3]->getName()); $this->assertSame('value', $cookies[3]->getValue()); + $this->assertSame('globally_unencrypted_cookie', $cookies[4]->getName()); + $this->assertSame('value', $cookies[4]->getValue()); } public function testQueuedCookieEncryption() @@ -74,7 +78,7 @@ public function testQueuedCookieEncryption() $response = $this->router->dispatch(Request::create($this->queueCookiePath, 'GET')); $cookies = $response->headers->getCookies(); - $this->assertCount(4, $cookies); + $this->assertCount(5, $cookies); $this->assertSame('encrypted_cookie', $cookies[0]->getName()); $this->assertNotSame('value', $cookies[0]->getValue()); $this->assertSame('encrypted[array_cookie]', $cookies[1]->getName()); @@ -83,6 +87,8 @@ public function testQueuedCookieEncryption() $this->assertNotSame('value', $cookies[2]->getValue()); $this->assertSame('unencrypted_cookie', $cookies[3]->getName()); $this->assertSame('value', $cookies[3]->getValue()); + $this->assertSame('globally_unencrypted_cookie', $cookies[4]->getName()); + $this->assertSame('value', $cookies[4]->getValue()); } protected function getEncryptedCookieValue($key, $value) @@ -106,13 +112,14 @@ public function testCookieDecryption() ], ], 'unencrypted_cookie' => 'value', + 'globally_unencrypted_cookie' => 'value', ]; $this->container->make(EncryptCookiesTestMiddleware::class)->handle( Request::create('/cookie/read', 'GET', [], $cookies), function ($request) { $cookies = $request->cookies->all(); - $this->assertCount(3, $cookies); + $this->assertCount(4, $cookies); $this->assertArrayHasKey('encrypted_cookie', $cookies); $this->assertSame('value', $cookies['encrypted_cookie']); $this->assertArrayHasKey('encrypted', $cookies); @@ -123,6 +130,8 @@ function ($request) { $this->assertSame('value', $cookies['encrypted']['nested']['array_cookie']); $this->assertArrayHasKey('unencrypted_cookie', $cookies); $this->assertSame('value', $cookies['unencrypted_cookie']); + $this->assertArrayHasKey('globally_unencrypted_cookie', $cookies); + $this->assertSame('value', $cookies['globally_unencrypted_cookie']); return new Response; } @@ -139,6 +148,7 @@ public function setCookies() $response->headers->setCookie(new Cookie('encrypted[array_cookie]', 'value')); $response->headers->setCookie(new Cookie('encrypted[nested][array_cookie]', 'value')); $response->headers->setCookie(new Cookie('unencrypted_cookie', 'value')); + $response->headers->setCookie(new Cookie('globally_unencrypted_cookie', 'value')); return $response; } @@ -165,6 +175,7 @@ public function __construct() $cookie->queue(new Cookie('encrypted[array_cookie]', 'value')); $cookie->queue(new Cookie('encrypted[nested][array_cookie]', 'value')); $cookie->queue(new Cookie('unencrypted_cookie', 'value')); + $cookie->queue(new Cookie('globally_unencrypted_cookie', 'value')); $this->cookies = $cookie; } diff --git a/tests/Http/Middleware/TrimStringsTest.php b/tests/Http/Middleware/TrimStringsTest.php index cbc4109c5d02..98302534b6a5 100644 --- a/tests/Http/Middleware/TrimStringsTest.php +++ b/tests/Http/Middleware/TrimStringsTest.php @@ -44,6 +44,23 @@ public function test_leading_zero_width_space_character_is_trimmed() }); } + public function test_trim_strings_can_globally_ignore_certain_inputs() + { + $request = new Request; + + $request->merge([ + 'globally_ignored_title' => ' test title ', + ]); + + TrimStrings::except(['globally_ignored_title']); + + $middleware = new TrimStrings; + + $middleware->handle($request, function ($req) { + $this->assertEquals(' test title ', $req->globally_ignored_title); + }); + } + /** * Test trailing zero-width space character is trimmed [ZWSP]. */ diff --git a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php index 83c98f525d34..75ba79f9ce0a 100644 --- a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php +++ b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php @@ -6,6 +6,7 @@ use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\ScheduleListCommand; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\ProcessUtils; use Orchestra\Testbench\TestCase; @@ -129,6 +130,30 @@ public function testDisplayScheduleSubMinute() ->expectsOutput(' * * * * * 30s php artisan inspire ........... Next Due: 30 seconds from now'); } + public function testClosureCommandsMayBeScheduled() + { + $closure = function () { + }; + + Artisan::command('one', $closure)->weekly()->everySecond(); + Artisan::command('two', $closure)->everyTwoSeconds(); + Artisan::command('three', $closure)->everyFiveSeconds(); + Artisan::command('four', $closure)->everyTenSeconds(); + Artisan::command('five', $closure)->everyFifteenSeconds(); + Artisan::command('six', $closure)->everyTwentySeconds()->hourly(); + Artisan::command('six', $closure)->everyThreeHours()->everySecond(); + + $this->artisan(ScheduleListCommand::class) + ->assertSuccessful() + ->expectsOutput(' * 0 * * 0 1s php artisan one ............... Next Due: 1 second from now') + ->expectsOutput(' * * * * * 2s php artisan two .............. Next Due: 2 seconds from now') + ->expectsOutput(' * * * * * 5s php artisan three ............ Next Due: 5 seconds from now') + ->expectsOutput(' * * * * * 10s php artisan four ............ Next Due: 10 seconds from now') + ->expectsOutput(' * * * * * 15s php artisan five ............ Next Due: 15 seconds from now') + ->expectsOutput(' 0 * * * * 20s php artisan six ............. Next Due: 20 seconds from now') + ->expectsOutput(' * */3 * * * 1s php artisan six ............... Next Due: 1 second from now'); + } + protected function tearDown(): void { parent::tearDown(); diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 38f2c34d6cc8..96763dfa732f 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -132,9 +132,10 @@ public function testChangeTextColumnToTextColumn() $uppercase = strtoupper($type); - $expected = ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL"]; - - $this->assertEquals($expected, $queries); + $this->assertContains($queries, [ + ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL"], // MySQL + ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL COLLATE `utf8mb4_uca1400_ai_ci`"], // MariaDB + ]); } } diff --git a/tests/Integration/Generators/CacheTableCommandTest.php b/tests/Integration/Generators/CacheTableCommandTest.php index a02c35dc845b..403061605974 100644 --- a/tests/Integration/Generators/CacheTableCommandTest.php +++ b/tests/Integration/Generators/CacheTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Cache\Console\CacheTableCommand; + class CacheTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('cache:table')->assertExitCode(0); + $this->artisan(CacheTableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Generators/NotificationTableCommandTest.php b/tests/Integration/Generators/NotificationTableCommandTest.php index cf956e495c78..334a6612c760 100644 --- a/tests/Integration/Generators/NotificationTableCommandTest.php +++ b/tests/Integration/Generators/NotificationTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Notifications\Console\NotificationTableCommand; + class NotificationTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('notifications:table')->assertExitCode(0); + $this->artisan(NotificationTableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Generators/QueueBatchesTableCommandTest.php b/tests/Integration/Generators/QueueBatchesTableCommandTest.php index d9356fc9304c..4afb4612c03e 100644 --- a/tests/Integration/Generators/QueueBatchesTableCommandTest.php +++ b/tests/Integration/Generators/QueueBatchesTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Queue\Console\BatchesTableCommand; + class QueueBatchesTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('queue:batches-table')->assertExitCode(0); + $this->artisan(BatchesTableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Generators/QueueFailedTableCommandTest.php b/tests/Integration/Generators/QueueFailedTableCommandTest.php index dd1a1d40b7c7..c996da1d834a 100644 --- a/tests/Integration/Generators/QueueFailedTableCommandTest.php +++ b/tests/Integration/Generators/QueueFailedTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Queue\Console\FailedTableCommand; + class QueueFailedTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('queue:failed-table')->assertExitCode(0); + $this->artisan(FailedTableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Generators/QueueTableCommandTest.php b/tests/Integration/Generators/QueueTableCommandTest.php index d70b81ea48ea..ec1b953f16f4 100644 --- a/tests/Integration/Generators/QueueTableCommandTest.php +++ b/tests/Integration/Generators/QueueTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Queue\Console\TableCommand; + class QueueTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('queue:table')->assertExitCode(0); + $this->artisan(TableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Generators/SessionTableCommandTest.php b/tests/Integration/Generators/SessionTableCommandTest.php index cb491893e376..0fc60230cbcf 100644 --- a/tests/Integration/Generators/SessionTableCommandTest.php +++ b/tests/Integration/Generators/SessionTableCommandTest.php @@ -2,11 +2,13 @@ namespace Illuminate\Tests\Integration\Generators; +use Illuminate\Session\Console\SessionTableCommand; + class SessionTableCommandTest extends TestCase { public function testCreateMakesMigration() { - $this->artisan('session:table')->assertExitCode(0); + $this->artisan(SessionTableCommand::class)->assertExitCode(0); $this->assertMigrationFileContains([ 'use Illuminate\Database\Migrations\Migration;', diff --git a/tests/Integration/Http/Middleware/VerifyCsrfTokenExceptTest.php b/tests/Integration/Http/Middleware/VerifyCsrfTokenExceptTest.php index 7bd169a450f9..dfb0055892cb 100644 --- a/tests/Integration/Http/Middleware/VerifyCsrfTokenExceptTest.php +++ b/tests/Integration/Http/Middleware/VerifyCsrfTokenExceptTest.php @@ -15,6 +15,7 @@ protected function setUp(): void { parent::setUp(); + VerifyCsrfTokenExceptStub::except(['/globally/ignored']); $this->stub = new VerifyCsrfTokenExceptStub(app(), new Encrypter(Encrypter::generateKey('AES-128-CBC'))); $this->request = Request::create('http://example.com/foo/bar', 'POST'); } @@ -26,6 +27,12 @@ public function testItCanExceptPaths() $this->assertNonMatchingExcept(['/bar/foo']); } + public function testPathsCanBeGloballyIgnored() + { + $this->request = Request::create('http://example.com/globally/ignored', 'POST'); + $this->assertMatchingExcept(['globally/ignored']); + } + public function testItCanExceptWildcardPaths() { $this->assertMatchingExcept(['/foo/*']); diff --git a/tests/Integration/Routing/UrlSigningTest.php b/tests/Integration/Routing/UrlSigningTest.php index ffa3e433274e..66dd9a87cda5 100644 --- a/tests/Integration/Routing/UrlSigningTest.php +++ b/tests/Integration/Routing/UrlSigningTest.php @@ -270,7 +270,7 @@ public function testSignedMiddlewareWithRelativePath() public function testSignedMiddlewareIgnoringParameter() { - Route::get('/foo/{id}}', function (Request $request, $id) { + Route::get('/foo/{id}', function (Request $request, $id) { })->name('foo')->middleware('signed:relative'); $this->assertIsString($url = URL::signedRoute('foo', ['id' => 1]).'&ignore=me'); @@ -297,6 +297,26 @@ public function testSignedMiddlewareIgnoringParameterViaArgumentsWithRelative() $response->assertStatus(403); } + public function testSignedMiddlewareCanGloballyIgnoreParameters() + { + ValidateSignature::except(['globally_ignore']); + + Route::get('/foo/{id}', function (Request $request, $id) { + })->name('foo')->middleware('signed:relative'); + + $this->assertIsString($url = URL::signedRoute('foo', ['id' => 1]).'&globally_ignore=me'); + $request = Request::create($url); + $middleware = $this->createValidateSignatureMiddleware(['ignore']); + + try { + $middleware->handle($request, function ($request) { + $this->assertTrue($request->hasValidSignatureWhileIgnoring(['globally_ignore'])); + }); + } catch (InvalidSignatureException $exception) { + $this->fail($exception->getMessage()); + } + } + public function testSignedMiddlewareIgnoringParameterViaArgumentsWithoutRelative() { Route::get('/foo/{id}', function (Request $request, $id) { From 8725dff284b708e8988abb8d960a2348a868498a Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 29 Nov 2023 11:15:33 +0800 Subject: [PATCH 65/99] [11.x] Update Testbench to `^9.0` (#49167) * Update Testbench to `^9.0` * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- tests/Integration/Foundation/Console/AboutCommandTest.php | 2 +- tests/Integration/Queue/JobEncryptionTest.php | 1 + tests/Integration/Queue/UniqueJobTest.php | 1 + tests/Integration/Queue/WorkCommandTest.php | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ee285ac68d1a..84c53c70428d 100644 --- a/composer.json +++ b/composer.json @@ -106,7 +106,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "dev-next/slim-skeleton", + "orchestra/testbench-core": "^9.0", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.1", diff --git a/tests/Integration/Foundation/Console/AboutCommandTest.php b/tests/Integration/Foundation/Console/AboutCommandTest.php index 6795d3bafeb2..b1311d1b4d91 100644 --- a/tests/Integration/Foundation/Console/AboutCommandTest.php +++ b/tests/Integration/Foundation/Console/AboutCommandTest.php @@ -35,7 +35,7 @@ public function testItCanDisplayAboutCommandAsJson() 'database' => 'testing', 'logs' => ['single'], 'mail' => 'smtp', - 'queue' => 'sync', + 'queue' => 'database', 'session' => 'file', ], $output['drivers']); }); diff --git a/tests/Integration/Queue/JobEncryptionTest.php b/tests/Integration/Queue/JobEncryptionTest.php index 0042e24bf20c..704c08001074 100644 --- a/tests/Integration/Queue/JobEncryptionTest.php +++ b/tests/Integration/Queue/JobEncryptionTest.php @@ -15,6 +15,7 @@ use Illuminate\Tests\Integration\Database\DatabaseTestCase; use Orchestra\Testbench\Attributes\WithMigration; +#[WithMigration] #[WithMigration('queue')] class JobEncryptionTest extends DatabaseTestCase { diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index e0fa5ff9da8e..e4393986cd21 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -15,6 +15,7 @@ use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\TestCase; +#[WithMigration] #[WithMigration('queue')] class UniqueJobTest extends TestCase { diff --git a/tests/Integration/Queue/WorkCommandTest.php b/tests/Integration/Queue/WorkCommandTest.php index 968b1c5d3548..95a2a18fa179 100644 --- a/tests/Integration/Queue/WorkCommandTest.php +++ b/tests/Integration/Queue/WorkCommandTest.php @@ -11,6 +11,7 @@ use Orchestra\Testbench\TestCase; use Queue; +#[WithMigration] #[WithMigration('queue')] class WorkCommandTest extends TestCase { From 059759a963e213e3422f0c61e7cf16b0441f8e9f Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 30 Nov 2023 00:36:16 +0800 Subject: [PATCH 66/99] [11.x] Add `Illuminate\Bus\BatchRepository::rollBack()` contract. (#49176) Introduced #48961 (minor release for Laravel 10). Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Bus/BatchRepository.php | 10 +++++++--- src/Illuminate/Queue/Jobs/Job.php | 10 ++++------ .../Support/Testing/Fakes/BatchRepositoryFake.php | 10 ++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Bus/BatchRepository.php b/src/Illuminate/Bus/BatchRepository.php index 0e580ca4dcd9..f4b756008e95 100644 --- a/src/Illuminate/Bus/BatchRepository.php +++ b/src/Illuminate/Bus/BatchRepository.php @@ -4,9 +4,6 @@ use Closure; -/** - * @method void rollBack() - */ interface BatchRepository { /** @@ -92,4 +89,11 @@ public function delete(string $batchId); * @return mixed */ public function transaction(Closure $callback); + + /** + * Rollback the last database transaction for the connection. + * + * @return void + */ + public function rollBack(); } diff --git a/src/Illuminate/Queue/Jobs/Job.php b/src/Illuminate/Queue/Jobs/Job.php index 67a80e50c03e..a41b401dddb7 100755 --- a/src/Illuminate/Queue/Jobs/Job.php +++ b/src/Illuminate/Queue/Jobs/Job.php @@ -197,12 +197,10 @@ public function fail($e = null) in_array(Batchable::class, class_uses_recursive($commandName))) { $batchRepository = $this->resolve(BatchRepository::class); - if (method_exists($batchRepository, 'rollBack')) { - try { - $batchRepository->rollBack(); - } catch (Throwable $e) { - // ... - } + try { + $batchRepository->rollBack(); + } catch (Throwable $e) { + // ... } } diff --git a/src/Illuminate/Support/Testing/Fakes/BatchRepositoryFake.php b/src/Illuminate/Support/Testing/Fakes/BatchRepositoryFake.php index 021c73f27163..2afa4ab94e2c 100644 --- a/src/Illuminate/Support/Testing/Fakes/BatchRepositoryFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BatchRepositoryFake.php @@ -150,4 +150,14 @@ public function transaction(Closure $callback) { return $callback(); } + + /** + * Rollback the last database transaction for the connection. + * + * @return void + */ + public function rollBack() + { + // + } } From e447ce2fe789687c25098951d002174807b42879 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 29 Nov 2023 22:09:33 +0200 Subject: [PATCH 67/99] [11.x] Add first/last directives to data_get helper (#49175) * [11.x] Add first/last directives to data_get helper * More tests * use first and last --------- Co-authored-by: Sergey Danilchenko Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/helpers.php | 9 ++++ tests/Support/SupportHelpersTest.php | 74 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 235edceb1509..7c8a7069655a 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -74,6 +74,15 @@ function data_get($target, $key, $default = null) return in_array('*', $key) ? Arr::collapse($result) : $result; } + $segment = match ($segment) { + '\*' => '*', + '\{first}' => '{first}', + '{first}' => array_key_first($target), + '\{last}' => '{last}', + '{last}' => array_key_last($target), + default => $segment, + }; + if (Arr::accessible($target) && Arr::exists($target, $segment)) { $target = $target[$segment]; } elseif (is_object($target) && isset($target->{$segment})) { diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index c0fddec3ec76..51a1929a219c 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -252,6 +252,80 @@ public function testDataGetWithDoubleNestedArraysCollapsesResult() $this->assertEquals([], data_get($array, 'posts.*.users.*.name')); } + public function testDataGetFirstLastDirectives() + { + $array = [ + 'flights' => [ + [ + 'segments' => [ + ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'], + ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'], + ], + ], + [ + 'segments' => [ + ['from' => 'LGW', 'departure' => '8:00', 'to' => 'SAW', 'arrival' => '14:00'], + ['from' => 'SAW', 'departure' => '15:00', 'to' => 'PEK', 'arrival' => '19:00'], + ], + ], + ], + ]; + + $this->assertEquals('LHR', data_get($array, 'flights.0.segments.{first}.from')); + $this->assertEquals('PKX', data_get($array, 'flights.0.segments.{last}.to')); + + $this->assertEquals('LHR', data_get($array, 'flights.{first}.segments.{first}.from')); + $this->assertEquals('PEK', data_get($array, 'flights.{last}.segments.{last}.to')); + $this->assertEquals('PKX', data_get($array, 'flights.{first}.segments.{last}.to')); + $this->assertEquals('LGW', data_get($array, 'flights.{last}.segments.{first}.from')); + + $this->assertEquals(['LHR', 'IST'], data_get($array, 'flights.{first}.segments.*.from')); + $this->assertEquals(['SAW', 'PEK'], data_get($array, 'flights.{last}.segments.*.to')); + + $this->assertEquals(['LHR', 'LGW'], data_get($array, 'flights.*.segments.{first}.from')); + $this->assertEquals(['PKX', 'PEK'], data_get($array, 'flights.*.segments.{last}.to')); + } + + public function testDataGetFirstLastDirectivesOnKeyedArrays() + { + $array = [ + 'numericKeys' => [ + 2 => 'first', + 0 => 'second', + 1 => 'last', + ], + 'stringKeys' => [ + 'one' => 'first', + 'two' => 'second', + 'three' => 'last', + ], + ]; + + $this->assertEquals('second', data_get($array, 'numericKeys.0')); + $this->assertEquals('first', data_get($array, 'numericKeys.{first}')); + $this->assertEquals('last', data_get($array, 'numericKeys.{last}')); + $this->assertEquals('first', data_get($array, 'stringKeys.{first}')); + $this->assertEquals('last', data_get($array, 'stringKeys.{last}')); + } + + public function testDataGetEscapedSegmentKeys() + { + $array = [ + 'symbols' => [ + '{last}' => ['description' => 'dollar'], + '*' => ['description' => 'asterisk'], + '{first}' => ['description' => 'caret'], + ], + ]; + + $this->assertEquals('caret', data_get($array, 'symbols.\{first}.description')); + $this->assertEquals('dollar', data_get($array, 'symbols.{first}.description')); + $this->assertEquals('asterisk', data_get($array, 'symbols.\*.description')); + $this->assertEquals(['dollar', 'asterisk', 'caret'], data_get($array, 'symbols.*.description')); + $this->assertEquals('dollar', data_get($array, 'symbols.\{last}.description')); + $this->assertEquals('caret', data_get($array, 'symbols.{last}.description')); + } + public function testDataFill() { $data = ['foo' => 'bar']; From 5fedaf4db5b54f16920ef742e4c5655575efd34a Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 29 Nov 2023 17:39:22 -0500 Subject: [PATCH 68/99] [11.x] `Fluent::scope/collect` methods (#49180) * Add toFluent method * Add scope and toCollection methods * Add tests * Remove toFluent on collections * Remove unused import * Remove unused import * Cast attributes as array in scope instead * formatting * Use `value` for `offsetGet` and `__get` methods --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Fluent.php | 42 ++++++++++++++++++++++++++--- tests/Support/SupportFluentTest.php | 20 ++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Fluent.php b/src/Illuminate/Support/Fluent.php index 109ce17336a7..a4f482f31743 100755 --- a/src/Illuminate/Support/Fluent.php +++ b/src/Illuminate/Support/Fluent.php @@ -37,7 +37,7 @@ public function __construct($attributes = []) } /** - * Get an attribute from the fluent instance. + * Get an attribute from the fluent instance using "dot" notation. * * @template TGetDefault * @@ -46,6 +46,18 @@ public function __construct($attributes = []) * @return TValue|TGetDefault */ public function get($key, $default = null) + { + return data_get($this->attributes, $key, $default); + } + + /** + * Get an attribute from the fluent instance. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function value($key, $default = null) { if (array_key_exists($key, $this->attributes)) { return $this->attributes[$key]; @@ -54,6 +66,20 @@ public function get($key, $default = null) return value($default); } + /** + * Get the value of the given key as a new Fluent instance. + * + * @param string $key + * @param mixed $default + * @return static + */ + public function scope($key, $default = null) + { + return new static( + (array) $this->get($key, $default) + ); + } + /** * Get the attributes from the fluent instance. * @@ -74,6 +100,16 @@ public function toArray() return $this->attributes; } + /** + * Convert the fluent instance to a Collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect() + { + return new Collection($this->attributes); + } + /** * Convert the object into something JSON serializable. * @@ -114,7 +150,7 @@ public function offsetExists($offset): bool */ public function offsetGet($offset): mixed { - return $this->get($offset); + return $this->value($offset); } /** @@ -162,7 +198,7 @@ public function __call($method, $parameters) */ public function __get($key) { - return $this->get($key); + return $this->value($key); } /** diff --git a/tests/Support/SupportFluentTest.php b/tests/Support/SupportFluentTest.php index 515b4817da10..659472df335b 100755 --- a/tests/Support/SupportFluentTest.php +++ b/tests/Support/SupportFluentTest.php @@ -110,6 +110,26 @@ public function testToJsonEncodesTheToArrayResult() $this->assertJsonStringEqualsJsonString(json_encode(['foo']), $results); } + + public function testScope() + { + $fluent = new Fluent(['user' => ['name' => 'taylor']]); + $this->assertEquals(['taylor'], $fluent->scope('user.name')->toArray()); + $this->assertEquals(['dayle'], $fluent->scope('user.age', 'dayle')->toArray()); + + $fluent = new Fluent(['products' => ['forge', 'vapour', 'spark']]); + $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->scope('products')->toArray()); + $this->assertEquals(['foo', 'bar'], $fluent->scope('missing', ['foo', 'bar'])->toArray()); + + $fluent = new Fluent(['authors' => ['taylor' => ['products' => ['forge', 'vapour', 'spark']]]]); + $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->scope('authors.taylor.products')->toArray()); + } + + public function testToCollection() + { + $fluent = new Fluent(['forge', 'vapour', 'spark']); + $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->collect()->all()); + } } class FluentArrayIteratorStub implements IteratorAggregate From 0141b7521edff8e880b04105c80e31edb440b3ac Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 30 Nov 2023 16:10:06 +0200 Subject: [PATCH 69/99] [11.x] data_get helper first/last directives support for ArrayAccess&Iterable (#49193) * [11.x] data_get helper first/last directives support for ArrayAccess&Iterable * optimization --------- Co-authored-by: Sergey Danilchenko --- src/Illuminate/Collections/helpers.php | 4 +- tests/Support/SupportHelpersTest.php | 81 +++++++++++++++++++++----- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 7c8a7069655a..1854137ee6bf 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($target), + '{first}' => array_key_first(is_array($target) ? $target : collect($target)->all()), '\{last}' => '{last}', - '{last}' => array_key_last($target), + '{last}' => array_key_last(is_array($target) ? $target : collect($target)->all()), default => $segment, }; diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 51a1929a219c..3570323c252a 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -269,6 +269,7 @@ public function testDataGetFirstLastDirectives() ], ], ], + 'empty' => [], ]; $this->assertEquals('LHR', data_get($array, 'flights.0.segments.{first}.from')); @@ -284,6 +285,47 @@ public function testDataGetFirstLastDirectives() $this->assertEquals(['LHR', 'LGW'], data_get($array, 'flights.*.segments.{first}.from')); $this->assertEquals(['PKX', 'PEK'], data_get($array, 'flights.*.segments.{last}.to')); + + $this->assertEquals('Not found', data_get($array, 'empty.{first}', 'Not found')); + $this->assertEquals('Not found', data_get($array, 'empty.{last}', 'Not found')); + } + + public function testDataGetFirstLastDirectivesOnArrayAccessIterable() + { + $arrayAccessIterable = [ + 'flights' => new SupportTestArrayAccessIterable([ + [ + 'segments' => new SupportTestArrayAccessIterable([ + ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'], + ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'], + ]), + ], + [ + 'segments' => new SupportTestArrayAccessIterable([ + ['from' => 'LGW', 'departure' => '8:00', 'to' => 'SAW', 'arrival' => '14:00'], + ['from' => 'SAW', 'departure' => '15:00', 'to' => 'PEK', 'arrival' => '19:00'], + ]), + ], + ]), + 'empty' => new SupportTestArrayAccessIterable([]), + ]; + + $this->assertEquals('LHR', data_get($arrayAccessIterable, 'flights.0.segments.{first}.from')); + $this->assertEquals('PKX', data_get($arrayAccessIterable, 'flights.0.segments.{last}.to')); + + $this->assertEquals('LHR', data_get($arrayAccessIterable, 'flights.{first}.segments.{first}.from')); + $this->assertEquals('PEK', data_get($arrayAccessIterable, 'flights.{last}.segments.{last}.to')); + $this->assertEquals('PKX', data_get($arrayAccessIterable, 'flights.{first}.segments.{last}.to')); + $this->assertEquals('LGW', data_get($arrayAccessIterable, 'flights.{last}.segments.{first}.from')); + + $this->assertEquals(['LHR', 'IST'], data_get($arrayAccessIterable, 'flights.{first}.segments.*.from')); + $this->assertEquals(['SAW', 'PEK'], data_get($arrayAccessIterable, 'flights.{last}.segments.*.to')); + + $this->assertEquals(['LHR', 'LGW'], data_get($arrayAccessIterable, 'flights.*.segments.{first}.from')); + $this->assertEquals(['PKX', 'PEK'], data_get($arrayAccessIterable, 'flights.*.segments.{last}.to')); + + $this->assertEquals('Not found', data_get($arrayAccessIterable, 'empty.{first}', 'Not found')); + $this->assertEquals('Not found', data_get($arrayAccessIterable, 'empty.{last}', 'Not found')); } public function testDataGetFirstLastDirectivesOnKeyedArrays() @@ -1160,43 +1202,37 @@ class SupportTestClassThree extends SupportTestClassTwo use SupportTestTraitThree; } -class SupportTestArrayAccess implements ArrayAccess +trait SupportTestTraitArrayAccess { - protected $attributes = []; - - public function __construct($attributes = []) + public function __construct(protected array $items = []) { - $this->attributes = $attributes; } public function offsetExists($offset): bool { - return array_key_exists($offset, $this->attributes); + return array_key_exists($offset, $this->items); } public function offsetGet($offset): mixed { - return $this->attributes[$offset]; + return $this->items[$offset]; } public function offsetSet($offset, $value): void { - $this->attributes[$offset] = $value; + $this->items[$offset] = $value; } public function offsetUnset($offset): void { - unset($this->attributes[$offset]); + unset($this->items[$offset]); } } -class SupportTestArrayIterable implements IteratorAggregate +trait SupportTestTraitArrayIterable { - protected $items = []; - - public function __construct($items = []) + public function __construct(protected array $items = []) { - $this->items = $items; } public function getIterator(): Traversable @@ -1205,6 +1241,23 @@ public function getIterator(): Traversable } } +class SupportTestArrayAccess implements ArrayAccess +{ + use SupportTestTraitArrayAccess; +} + +class SupportTestArrayIterable implements IteratorAggregate +{ + use SupportTestTraitArrayIterable; +} + +class SupportTestArrayAccessIterable implements ArrayAccess, IteratorAggregate +{ + use SupportTestTraitArrayAccess, SupportTestTraitArrayIterable { + SupportTestTraitArrayAccess::__construct insteadof SupportTestTraitArrayIterable; + } +} + class SupportTestCountable implements Countable { public function count(): int From 866582b96f1c3969e3d639756e47690acdee833d Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:40:28 +0100 Subject: [PATCH 70/99] Improve stateful guard return types (#49196) --- src/Illuminate/Contracts/Auth/StatefulGuard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Contracts/Auth/StatefulGuard.php b/src/Illuminate/Contracts/Auth/StatefulGuard.php index faf1497d5f84..2448cf9b08b8 100644 --- a/src/Illuminate/Contracts/Auth/StatefulGuard.php +++ b/src/Illuminate/Contracts/Auth/StatefulGuard.php @@ -35,7 +35,7 @@ public function login(Authenticatable $user, $remember = false); * * @param mixed $id * @param bool $remember - * @return \Illuminate\Contracts\Auth\Authenticatable|bool + * @return \Illuminate\Contracts\Auth\Authenticatable|false */ public function loginUsingId($id, $remember = false); @@ -43,7 +43,7 @@ public function loginUsingId($id, $remember = false); * Log the given user ID into the application without sessions or cookies. * * @param mixed $id - * @return \Illuminate\Contracts\Auth\Authenticatable|bool + * @return \Illuminate\Contracts\Auth\Authenticatable|false */ public function onceUsingId($id); From e0fc281359e081a37bc25509c8d66e0f5e906e49 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 30 Nov 2023 14:40:59 +0000 Subject: [PATCH 71/99] Update facade docblocks --- src/Illuminate/Support/Facades/Auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Facades/Auth.php b/src/Illuminate/Support/Facades/Auth.php index d2d44fac857d..a93e3244ae44 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -32,8 +32,8 @@ * @method static bool attempt(array $credentials = [], bool $remember = false) * @method static bool once(array $credentials = []) * @method static void login(\Illuminate\Contracts\Auth\Authenticatable $user, bool $remember = false) - * @method static \Illuminate\Contracts\Auth\Authenticatable|bool loginUsingId(mixed $id, bool $remember = false) - * @method static \Illuminate\Contracts\Auth\Authenticatable|bool onceUsingId(mixed $id) + * @method static \Illuminate\Contracts\Auth\Authenticatable|false loginUsingId(mixed $id, bool $remember = false) + * @method static \Illuminate\Contracts\Auth\Authenticatable|false onceUsingId(mixed $id) * @method static bool viaRemember() * @method static void logout() * @method static \Symfony\Component\HttpFoundation\Response|null basic(string $field = 'email', array $extraConditions = []) From afcf28f15333ef05f087f5f903ebcfa8c3906bbc Mon Sep 17 00:00:00 2001 From: Saya <379924+chu121su12@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:41:17 +0800 Subject: [PATCH 72/99] [11.x] Allow to specify nested key when calling Fluent::collect() (#49191) * [11.x] Allow to specify nested key when calling Fluent::collect() * type key as string * Update src/Illuminate/Support/Fluent.php Co-authored-by: Mior Muhammad Zaki --------- Co-authored-by: Mior Muhammad Zaki --- src/Illuminate/Support/Fluent.php | 5 +++-- tests/Support/SupportFluentTest.php | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Fluent.php b/src/Illuminate/Support/Fluent.php index a4f482f31743..a4948f2848b2 100755 --- a/src/Illuminate/Support/Fluent.php +++ b/src/Illuminate/Support/Fluent.php @@ -103,11 +103,12 @@ public function toArray() /** * Convert the fluent instance to a Collection. * + * @param string|null $key * @return \Illuminate\Support\Collection */ - public function collect() + public function collect($key = null) { - return new Collection($this->attributes); + return new Collection($this->get($key)); } /** diff --git a/tests/Support/SupportFluentTest.php b/tests/Support/SupportFluentTest.php index 659472df335b..07c883c55a2a 100755 --- a/tests/Support/SupportFluentTest.php +++ b/tests/Support/SupportFluentTest.php @@ -129,6 +129,9 @@ public function testToCollection() { $fluent = new Fluent(['forge', 'vapour', 'spark']); $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->collect()->all()); + + $fluent = new Fluent(['authors' => ['taylor' => ['products' => ['forge', 'vapour', 'spark']]]]); + $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->collect('authors.taylor.products')->all()); } } From e82b7e8ca849248e8986d7fb9bfe0258b232c63c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 30 Nov 2023 16:36:36 -0600 Subject: [PATCH 73/99] add partitioned entry --- config/session.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/session.php b/config/session.php index b29b8b783815..51c77f90af42 100644 --- a/config/session.php +++ b/config/session.php @@ -199,4 +199,17 @@ 'same_site' => env('SESSION_SAME_SITE', 'lax'), + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => false, + ]; From 219daf7b689382be1527079d86abb45a611e2952 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 4 Dec 2023 21:20:06 +0000 Subject: [PATCH 74/99] [11.x] Makes `name` argument required on `config:publish` (#49238) * Makes name argument required on `config:publish` * Uses prompts * Apply fixes from StyleCI * formatting * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- .../Console/ConfigPublishCommand.php | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Foundation/Console/ConfigPublishCommand.php b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php index 65eb8c592968..bc12f35cd4d8 100644 --- a/src/Illuminate/Foundation/Console/ConfigPublishCommand.php +++ b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php @@ -6,6 +6,8 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Finder\Finder; +use function Laravel\Prompts\select; + #[AsCommand(name: 'config:publish')] class ConfigPublishCommand extends Command { @@ -16,6 +18,7 @@ class ConfigPublishCommand extends Command */ protected $signature = 'config:publish {name? : The name of the configuration file to publish} + {--all : Publish all configuration files} {--force : Overwrite any existing configuration files}'; /** @@ -34,7 +37,20 @@ public function handle() { $config = $this->getBaseConfigurationFiles(); - $name = $this->argument('name'); + if (is_null($this->argument('name')) && $this->option('all')) { + foreach ($config as $key => $file) { + $this->publish($key, $file, $this->laravel->configPath().'/'.$key.'.php'); + } + + return; + } + + $name = (string) (is_null($this->argument('name')) ? select( + label: 'Which configuration file would you like to publish?', + options: collect($config)->map(function (string $path) { + return basename($path, '.php'); + }), + ) : $this->argument('name')); if (! is_null($name) && ! isset($config[$name])) { $this->components->error('Unrecognized configuration file.'); @@ -42,11 +58,7 @@ public function handle() return 1; } - foreach ($config as $key => $file) { - if ($key === $name || is_null($name)) { - $this->publish($key, $file, $this->laravel->configPath().'/'.$key.'.php'); - } - } + $this->publish($name, $config[$name], $this->laravel->configPath().'/'.$name.'.php'); } /** From db7a87d2315634828e6efa09c60c07d04c42dc4f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 5 Dec 2023 22:48:12 +0000 Subject: [PATCH 75/99] [11.x] Adds `ServiceProvider::publishesMigrations()` method (#49246) * Adds `migrationPublishes` to register migration publishes * Adds tests * Adds `ensureUpToDateMigrationNames` * Apply fixes from StyleCI * Improves docs * Fixes test suite * Makes publishes migrations paths unique * Returns early * Increases a second before generating a date for migration * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- .../Console/VendorPublishCommand.php | 46 ++++++++++++++++++- src/Illuminate/Support/ServiceProvider.php | 31 +++++++++++++ tests/Support/SupportServiceProviderTest.php | 22 ++++++++- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Foundation/Console/VendorPublishCommand.php b/src/Illuminate/Foundation/Console/VendorPublishCommand.php index f755c3d0a3fe..037567f53445 100644 --- a/src/Illuminate/Foundation/Console/VendorPublishCommand.php +++ b/src/Illuminate/Foundation/Console/VendorPublishCommand.php @@ -42,6 +42,13 @@ class VendorPublishCommand extends Command */ protected $tags = []; + /** + * The time the command started. + * + * @var \Illuminate\Support\Carbon|null + */ + protected $publishedAt; + /** * The console command signature. * @@ -81,6 +88,8 @@ public function __construct(Filesystem $files) */ public function handle() { + $this->publishedAt = now(); + $this->determineWhatShouldBePublished(); foreach ($this->tags ?: [null] as $tag) { @@ -243,6 +252,8 @@ protected function publishFile($from, $to) { if ((! $this->option('existing') && (! $this->files->exists($to) || $this->option('force'))) || ($this->option('existing') && $this->files->exists($to))) { + $to = $this->ensureMigrationNameIsUpToDate($from, $to); + $this->createParentDirectory(dirname($to)); $this->files->copy($from, $to); @@ -274,7 +285,7 @@ protected function publishDirectory($from, $to) { $visibility = PortableVisibilityConverter::fromArray([], Visibility::PUBLIC); - $this->moveManagedFiles(new MountManager([ + $this->moveManagedFiles($from, new MountManager([ 'from' => new Flysystem(new LocalAdapter($from)), 'to' => new Flysystem(new LocalAdapter($to, $visibility)), ])); @@ -285,10 +296,11 @@ protected function publishDirectory($from, $to) /** * Move all the files in the given MountManager. * + * @param string $from * @param \League\Flysystem\MountManager $manager * @return void */ - protected function moveManagedFiles($manager) + protected function moveManagedFiles($from, $manager) { foreach ($manager->listContents('from://', true) as $file) { $path = Str::after($file['path'], 'from://'); @@ -300,6 +312,8 @@ protected function moveManagedFiles($manager) || ($this->option('existing') && $manager->fileExists('to://'.$path)) ) ) { + $path = $this->ensureMigrationNameIsUpToDate($from, $path); + $manager->write('to://'.$path, $manager->read($file['path'])); } } @@ -318,6 +332,34 @@ protected function createParentDirectory($directory) } } + /** + * Ensure the given migration name is up-to-date. + * + * @param string $from + * @param string $to + * @return string + */ + protected function ensureMigrationNameIsUpToDate($from, $to) + { + $from = realpath($from); + + foreach (ServiceProvider::publishableMigrationPaths() as $path) { + $path = realpath($path); + + if ($from === $path && preg_match('/\d{4}_(\d{2})_(\d{2})_(\d{6})_/', $to)) { + $this->publishedAt->addSecond(); + + return preg_replace( + '/\d{4}_(\d{2})_(\d{2})_(\d{6})_/', + $this->publishedAt->format('Y_m_d_His').'_', + $to, + ); + } + } + + return $to; + } + /** * Write a status message to the console. * diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index ad4a9c5837dc..fd632e8c7ed3 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -47,6 +47,13 @@ abstract class ServiceProvider */ public static $publishGroups = []; + /** + * The migration paths available for publishing. + * + * @var array + */ + protected static $publishableMigrationPaths = []; + /** * Create a new service provider instance. * @@ -267,6 +274,20 @@ protected function callAfterResolving($name, $callback) } } + /** + * Register migration paths to be published by the publish command. + * + * @param array $paths + * @param mixed $groups + * @return void + */ + protected function publishesMigrations(array $paths, $groups = null) + { + $this->publishes($paths, $groups); + + static::$publishableMigrationPaths = array_unique(array_merge(static::$publishableMigrationPaths, array_keys($paths))); + } + /** * Register paths to be published by the publish command. * @@ -380,6 +401,16 @@ public static function publishableProviders() return array_keys(static::$publishes); } + /** + * Get the migration paths available for publishing. + * + * @return array + */ + public static function publishableMigrationPaths() + { + return static::$publishableMigrationPaths; + } + /** * Get the groups available for publishing. * diff --git a/tests/Support/SupportServiceProviderTest.php b/tests/Support/SupportServiceProviderTest.php index bceb3046cfdd..4861a9e97291 100644 --- a/tests/Support/SupportServiceProviderTest.php +++ b/tests/Support/SupportServiceProviderTest.php @@ -39,7 +39,14 @@ public function testPublishableServiceProviders() public function testPublishableGroups() { $toPublish = ServiceProvider::publishableGroups(); - $this->assertEquals(['some_tag', 'tag_one', 'tag_two'], $toPublish, 'Publishable groups do not return expected set of groups.'); + $this->assertEquals([ + 'some_tag', + 'tag_one', + 'tag_two', + 'tag_three', + 'tag_four', + 'tag_five', + ], $toPublish, 'Publishable groups do not return expected set of groups.'); } public function testSimpleAssetsArePublishedCorrectly() @@ -47,7 +54,14 @@ public function testSimpleAssetsArePublishedCorrectly() $toPublish = ServiceProvider::pathsToPublish(ServiceProviderForTestingOne::class); $this->assertArrayHasKey('source/unmarked/one', $toPublish, 'Service provider does not return expected published path key.'); $this->assertArrayHasKey('source/tagged/one', $toPublish, 'Service provider does not return expected published path key.'); - $this->assertEquals(['source/unmarked/one' => 'destination/unmarked/one', 'source/tagged/one' => 'destination/tagged/one', 'source/tagged/multiple' => 'destination/tagged/multiple'], $toPublish, 'Service provider does not return expected set of published paths.'); + $this->assertEquals([ + 'source/unmarked/one' => 'destination/unmarked/one', + 'source/tagged/one' => 'destination/tagged/one', + 'source/tagged/multiple' => 'destination/tagged/multiple', + 'source/unmarked/two' => 'destination/unmarked/two', + 'source/tagged/three' => 'destination/tagged/three', + 'source/tagged/multiple_two' => 'destination/tagged/multiple_two', + ], $toPublish, 'Service provider does not return expected set of published paths.'); } public function testMultipleAssetsArePublishedCorrectly() @@ -119,6 +133,10 @@ public function boot() $this->publishes(['source/unmarked/one' => 'destination/unmarked/one']); $this->publishes(['source/tagged/one' => 'destination/tagged/one'], 'some_tag'); $this->publishes(['source/tagged/multiple' => 'destination/tagged/multiple'], ['tag_one', 'tag_two']); + + $this->publishesMigrations(['source/unmarked/two' => 'destination/unmarked/two']); + $this->publishesMigrations(['source/tagged/three' => 'destination/tagged/three'], 'tag_three'); + $this->publishesMigrations(['source/tagged/multiple_two' => 'destination/tagged/multiple_two'], ['tag_four', 'tag_five']); } } From ea3d4797d79807f9ad51e198348ad699b023c10f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 9 Dec 2023 15:15:02 +0000 Subject: [PATCH 76/99] [11.x] Improves `RedirectIfAuthenticated` middleware default redirect (#49286) * Improves `RedirectIfAuthenticated` * Apply fixes from StyleCI * Improves sorting * Improves perf * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- .../Middleware/RedirectIfAuthenticated.php | 27 ++++- .../Configuration/ApplicationBuilder.php | 3 +- .../RedirectIfAuthenticatedTest.php | 108 ++++++++++++++++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/Auth/Middleware/RedirectIfAuthenticatedTest.php diff --git a/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php b/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php index c436ef16ee0a..958229947daf 100644 --- a/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php +++ b/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Route; use Symfony\Component\HttpFoundation\Response; class RedirectIfAuthenticated @@ -12,7 +13,7 @@ class RedirectIfAuthenticated /** * The callback that should be used to generate the authentication redirect path. * - * @var callable + * @var callable|null */ protected static $redirectToCallback; @@ -41,7 +42,29 @@ protected function redirectTo(Request $request): ?string { return static::$redirectToCallback ? call_user_func(static::$redirectToCallback, $request) - : '/dashboard'; + : $this->defaultRedirectUri(); + } + + /** + * Get the default URI the user should be redirected to when they are authenticated. + */ + protected function defaultRedirectUri(): string + { + foreach (['dashboard', 'home'] as $uri) { + if (Route::has($uri)) { + return route($uri); + } + } + + $routes = Route::getRoutes()->get('GET'); + + foreach (['dashboard', 'home'] as $uri) { + if (isset($routes[$uri])) { + return '/'.$uri; + } + } + + return '/'; } /** diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index dd213b977743..bfefaeda3bbe 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -191,8 +191,7 @@ public function withMiddleware(callable $callback) { $this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) { $middleware = (new Middleware) - ->auth(redirectTo: fn () => route('login')) - ->guest(redirectTo: fn () => route('dashboard')); + ->auth(redirectTo: fn () => route('login')); $callback($middleware); diff --git a/tests/Integration/Auth/Middleware/RedirectIfAuthenticatedTest.php b/tests/Integration/Auth/Middleware/RedirectIfAuthenticatedTest.php new file mode 100644 index 000000000000..c0db2c4eb1ca --- /dev/null +++ b/tests/Integration/Auth/Middleware/RedirectIfAuthenticatedTest.php @@ -0,0 +1,108 @@ +withoutExceptionHandling(); + + /** @var \Illuminate\Contracts\Routing\Registrar $router */ + $this->router = $this->app->make(Registrar::class); + + $this->router->get('/login', function () { + return response('Login Form'); + })->middleware(RedirectIfAuthenticated::class); + + UserFactory::new()->create(); + + $user = AuthenticationTestUser::first(); + $this->router->get('/login', function () { + return response('Login Form'); + })->middleware(RedirectIfAuthenticated::class); + + UserFactory::new()->create(); + + $this->user = AuthenticationTestUser::first(); + } + + protected function defineEnvironment($app) + { + $app['config']->set('app.key', Str::random(32)); + $app['config']->set('auth.providers.users.model', AuthenticationTestUser::class); + } + + protected function defineDatabaseMigrations() + { + $this->loadLaravelMigrations(); + } + + public function testWhenDashboardNamedRouteIsAvailable() + { + $this->router->get('/named-dashboard', function () { + return response('Named Dashboard'); + })->name('dashboard'); + + $response = $this->actingAs($this->user)->get('/login'); + + $response->assertRedirect('/named-dashboard'); + } + + public function testWhenHomeNamedRouteIsAvailable() + { + $this->router->get('/named-home', function () { + return response('Named Home'); + })->name('home'); + + $response = $this->actingAs($this->user)->get('/login'); + + $response->assertRedirect('/named-home'); + } + + public function testWhenDashboardSlugIsAvailable() + { + $this->router->get('/dashboard', function () { + return response('My Dashboard'); + }); + + $response = $this->actingAs($this->user)->get('/login'); + + $response->assertRedirect('/dashboard'); + } + + public function testWhenHomeSlugIsAvailable() + { + $this->router->get('/home', function () { + return response('My Home'); + })->name('home'); + + $response = $this->actingAs($this->user)->get('/login'); + + $response->assertRedirect('/home'); + } + + public function testWhenHomeOrDashboardAreNotAvailable() + { + $response = $this->actingAs($this->user)->get('/login'); + + $response->assertRedirect('/'); + } + + public function testWhenGuest() + { + $response = $this->get('/login'); + + $response->assertOk(); + $response->assertSee('Login Form'); + } +} From 6e302e8aadf1c0779cc665176bf52443ceef17a0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sat, 9 Dec 2023 23:19:58 +0800 Subject: [PATCH 77/99] [11.x] Test Improvements (#49271) Signed-off-by: Mior Muhammad Zaki --- .github/workflows/tests.yml | 2 -- tests/Integration/Http/ThrottleRequestsTest.php | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 55ff3d93fcc5..292e06fd86d3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,7 +68,6 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: composer require guzzlehttp/psr7:^2.4 --no-interaction --no-update - if: matrix.php >= 8.2 - name: Install dependencies uses: nick-fields/retry@v2 @@ -131,7 +130,6 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: composer require guzzlehttp/psr7:~2.4 --no-interaction --no-update - if: matrix.php >= 8.2 - name: Install dependencies uses: nick-fields/retry@v2 diff --git a/tests/Integration/Http/ThrottleRequestsTest.php b/tests/Integration/Http/ThrottleRequestsTest.php index d0f43260eaa2..78f1df58ea32 100644 --- a/tests/Integration/Http/ThrottleRequestsTest.php +++ b/tests/Integration/Http/ThrottleRequestsTest.php @@ -11,6 +11,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Throwable; class ThrottleRequestsTest extends TestCase @@ -127,7 +128,7 @@ public static function perMinuteThrottlingDataSet() ]; } - /** @dataProvider perMinuteThrottlingDataSet */ + #[DataProvider('perMinuteThrottlingDataSet')] public function testItCanThrottlePerMinute(string $middleware) { $rateLimiter = Container::getInstance()->make(RateLimiter::class); From ceb8ed25e7f72f69d3df508765607b8825e046c5 Mon Sep 17 00:00:00 2001 From: Stephen Rees-Carter Date: Sun, 10 Dec 2023 23:41:06 +1000 Subject: [PATCH 78/99] [11.x] Rehash user passwords when validating credentials (#48665) * Rehash user passwords when validating credentials * Fix style violations * Remove hardcoded password when it's changable * Shift rehashing into SessionGuard The Session guard's attempt() method is a better place to apply rehashing than the validateCredentials() method on the provider. The latter shouldn't have side-effects, as per it's name. * Fix style violation * Add config option to disable rehashing on login * Clean up rehash flag injection * Fix contract in DatabaseUserProvider * Fixing return type in the docblocks * Use hash_equals() for a secure string comparison * formatting * formatting, leverage method on logoutOtherDevices * Fix spelling of passwords Co-authored-by: Chrysanthos <48060191+chrysanthos@users.noreply.github.com> --------- Co-authored-by: Taylor Otwell Co-authored-by: Chrysanthos <48060191+chrysanthos@users.noreply.github.com> --- src/Illuminate/Auth/AuthManager.php | 1 + src/Illuminate/Auth/Authenticatable.php | 19 +++++- src/Illuminate/Auth/DatabaseUserProvider.php | 19 ++++++ src/Illuminate/Auth/EloquentUserProvider.php | 19 ++++++ src/Illuminate/Auth/GenericUser.php | 10 ++++ src/Illuminate/Auth/SessionGuard.php | 50 ++++++++++++---- .../Contracts/Auth/Authenticatable.php | 7 +++ .../Contracts/Auth/UserProvider.php | 10 ++++ .../Middleware/AuthenticateSession.php | 4 +- tests/Auth/AuthDatabaseUserProviderTest.php | 58 +++++++++++++++++++ tests/Auth/AuthEloquentUserProviderTest.php | 44 ++++++++++++++ tests/Auth/AuthGuardTest.php | 43 ++++++++++++++ 12 files changed, 270 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Auth/AuthManager.php b/src/Illuminate/Auth/AuthManager.php index e95da5ec4ae4..4bba02800ad7 100755 --- a/src/Illuminate/Auth/AuthManager.php +++ b/src/Illuminate/Auth/AuthManager.php @@ -128,6 +128,7 @@ public function createSessionDriver($name, $config) $name, $provider, $this->app['session.store'], + rehashOnLogin: $this->app['config']->get('hashing.rehash_on_login', true), ); // When using the remember me functionality of the authentication services we diff --git a/src/Illuminate/Auth/Authenticatable.php b/src/Illuminate/Auth/Authenticatable.php index f1c0115916c8..0145c1cc8901 100644 --- a/src/Illuminate/Auth/Authenticatable.php +++ b/src/Illuminate/Auth/Authenticatable.php @@ -4,6 +4,13 @@ trait Authenticatable { + /** + * The column name of the password field using during authentication. + * + * @var string + */ + protected $authPasswordName = 'password'; + /** * The column name of the "remember me" token. * @@ -41,6 +48,16 @@ public function getAuthIdentifierForBroadcasting() return $this->getAuthIdentifier(); } + /** + * Get the name of the password attribute for the user. + * + * @return string + */ + public function getAuthPasswordName() + { + return $this->authPasswordName; + } + /** * Get the password for the user. * @@ -48,7 +65,7 @@ public function getAuthIdentifierForBroadcasting() */ public function getAuthPassword() { - return $this->password; + return $this->{$this->getAuthPasswordName()}; } /** diff --git a/src/Illuminate/Auth/DatabaseUserProvider.php b/src/Illuminate/Auth/DatabaseUserProvider.php index 16b70ee9c76a..1be060cb831a 100755 --- a/src/Illuminate/Auth/DatabaseUserProvider.php +++ b/src/Illuminate/Auth/DatabaseUserProvider.php @@ -158,4 +158,23 @@ public function validateCredentials(UserContract $user, array $credentials) $credentials['password'], $user->getAuthPassword() ); } + + /** + * Rehash the user's password if required and supported. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @param bool $force + * @return void + */ + public function rehashPasswordIfRequired(UserContract $user, array $credentials, bool $force = false) + { + if (! $this->hasher->needsRehash($user->getAuthPassword()) && ! $force) { + return; + } + + $this->connection->table($this->table) + ->where($user->getAuthIdentifierName(), $user->getAuthIdentifier()) + ->update([$user->getAuthPasswordName() => $this->hasher->make($credentials['password'])]); + } } diff --git a/src/Illuminate/Auth/EloquentUserProvider.php b/src/Illuminate/Auth/EloquentUserProvider.php index 39a744e0c098..646c2187f595 100755 --- a/src/Illuminate/Auth/EloquentUserProvider.php +++ b/src/Illuminate/Auth/EloquentUserProvider.php @@ -155,6 +155,25 @@ public function validateCredentials(UserContract $user, array $credentials) return $this->hasher->check($plain, $user->getAuthPassword()); } + /** + * Rehash the user's password if required and supported. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @param bool $force + * @return void + */ + public function rehashPasswordIfRequired(UserContract $user, array $credentials, bool $force = false) + { + if (! $this->hasher->needsRehash($user->getAuthPassword()) && ! $force) { + return; + } + + $user->forceFill([ + $user->getAuthPasswordName() => $this->hasher->make($credentials['password']), + ])->save(); + } + /** * Get a new query builder for the model instance. * diff --git a/src/Illuminate/Auth/GenericUser.php b/src/Illuminate/Auth/GenericUser.php index c87bc2382cb2..57bab7c8435d 100755 --- a/src/Illuminate/Auth/GenericUser.php +++ b/src/Illuminate/Auth/GenericUser.php @@ -44,6 +44,16 @@ public function getAuthIdentifier() return $this->attributes[$this->getAuthIdentifierName()]; } + /** + * Get the name of the password attribute for the user. + * + * @return string + */ + public function getAuthPasswordName() + { + return 'password'; + } + /** * Get the password for the user. * diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index b475dbc6ca2e..7966eaa7da12 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -96,6 +96,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth */ protected $timebox; + /** + * Indicates if passwords should be rehashed on login if needed. + * + * @var bool + */ + protected $rehashOnLogin; + /** * Indicates if the logout method has been called. * @@ -118,19 +125,22 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth * @param \Illuminate\Contracts\Session\Session $session * @param \Symfony\Component\HttpFoundation\Request|null $request * @param \Illuminate\Support\Timebox|null $timebox + * @param bool $rehashOnLogin * @return void */ public function __construct($name, UserProvider $provider, Session $session, Request $request = null, - Timebox $timebox = null) + Timebox $timebox = null, + bool $rehashOnLogin = true) { $this->name = $name; $this->session = $session; $this->request = $request; $this->provider = $provider; $this->timebox = $timebox ?: new Timebox; + $this->rehashOnLogin = $rehashOnLogin; } /** @@ -384,6 +394,8 @@ public function attempt(array $credentials = [], $remember = false) // 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); return true; @@ -415,6 +427,8 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe // 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); return true; @@ -465,6 +479,20 @@ protected function shouldLogin($callbacks, AuthenticatableContract $user) return true; } + /** + * Rehash the user's password if enabled and required. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @return void + */ + protected function rehashPasswordIfRequired(AuthenticatableContract $user, array $credentials) + { + if ($this->rehashOnLogin) { + $this->provider->rehashPasswordIfRequired($user, $credentials); + } + } + /** * Log the given user ID into the application. * @@ -656,18 +684,17 @@ protected function cycleRememberToken(AuthenticatableContract $user) * The application must be using the AuthenticateSession middleware. * * @param string $password - * @param string $attribute * @return \Illuminate\Contracts\Auth\Authenticatable|null * * @throws \Illuminate\Auth\AuthenticationException */ - public function logoutOtherDevices($password, $attribute = 'password') + public function logoutOtherDevices($password) { if (! $this->user()) { return; } - $result = $this->rehashUserPassword($password, $attribute); + $result = $this->rehashUserPasswordForDeviceLogout($password); if ($this->recaller() || $this->getCookieJar()->hasQueued($this->getRecallerName())) { @@ -680,23 +707,24 @@ public function logoutOtherDevices($password, $attribute = 'password') } /** - * Rehash the current user's password. + * Rehash the current user's password for logging out other devices via AuthenticateSession. * * @param string $password - * @param string $attribute * @return \Illuminate\Contracts\Auth\Authenticatable|null * * @throws \InvalidArgumentException */ - protected function rehashUserPassword($password, $attribute) + protected function rehashUserPasswordForDeviceLogout($password) { - if (! Hash::check($password, $this->user()->{$attribute})) { + $user = $this->user(); + + if (! Hash::check($password, $user->getAuthPassword())) { throw new InvalidArgumentException('The given password does not match the current password.'); } - return tap($this->user()->forceFill([ - $attribute => Hash::make($password), - ]))->save(); + $this->provider->rehashPasswordIfRequired( + $user, ['password' => $password], force: true + ); } /** diff --git a/src/Illuminate/Contracts/Auth/Authenticatable.php b/src/Illuminate/Contracts/Auth/Authenticatable.php index ac4ed886d1b7..f5ed4987b44b 100644 --- a/src/Illuminate/Contracts/Auth/Authenticatable.php +++ b/src/Illuminate/Contracts/Auth/Authenticatable.php @@ -18,6 +18,13 @@ public function getAuthIdentifierName(); */ public function getAuthIdentifier(); + /** + * Get the name of the password attribute for the user. + * + * @return string + */ + public function getAuthPasswordName(); + /** * Get the password for the user. * diff --git a/src/Illuminate/Contracts/Auth/UserProvider.php b/src/Illuminate/Contracts/Auth/UserProvider.php index a2ab122718c6..4ed51bf00e9c 100644 --- a/src/Illuminate/Contracts/Auth/UserProvider.php +++ b/src/Illuminate/Contracts/Auth/UserProvider.php @@ -46,4 +46,14 @@ public function retrieveByCredentials(array $credentials); * @return bool */ public function validateCredentials(Authenticatable $user, array $credentials); + + /** + * Rehash the user's password if required and supported. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @param bool $force + * @return void + */ + public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false); } diff --git a/src/Illuminate/Session/Middleware/AuthenticateSession.php b/src/Illuminate/Session/Middleware/AuthenticateSession.php index d525e8f535e1..b32e3ba50283 100644 --- a/src/Illuminate/Session/Middleware/AuthenticateSession.php +++ b/src/Illuminate/Session/Middleware/AuthenticateSession.php @@ -51,7 +51,7 @@ public function handle($request, Closure $next) if ($this->guard()->viaRemember()) { $passwordHash = explode('|', $request->cookies->get($this->guard()->getRecallerName()))[2] ?? null; - if (! $passwordHash || $passwordHash != $request->user()->getAuthPassword()) { + if (! $passwordHash || ! hash_equals($request->user()->getAuthPassword(), $passwordHash)) { $this->logout($request); } } @@ -60,7 +60,7 @@ public function handle($request, Closure $next) $this->storePasswordHashInSession($request); } - if ($request->session()->get('password_hash_'.$this->auth->getDefaultDriver()) !== $request->user()->getAuthPassword()) { + if (! hash_equals($request->session()->get('password_hash_'.$this->auth->getDefaultDriver()), $request->user()->getAuthPassword())) { $this->logout($request); } diff --git a/tests/Auth/AuthDatabaseUserProviderTest.php b/tests/Auth/AuthDatabaseUserProviderTest.php index 382d3532a976..ad317be84253 100755 --- a/tests/Auth/AuthDatabaseUserProviderTest.php +++ b/tests/Auth/AuthDatabaseUserProviderTest.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Hashing\Hasher; use Illuminate\Database\Connection; +use Illuminate\Database\ConnectionInterface; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; @@ -160,4 +161,61 @@ public function testCredentialValidation() $this->assertTrue($result); } + + public function testCredentialValidationFailed() + { + $conn = m::mock(Connection::class); + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false); + $provider = new DatabaseUserProvider($conn, $hasher, 'foo'); + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $result = $provider->validateCredentials($user, ['password' => 'plain']); + + $this->assertFalse($result); + } + + public function testRehashPasswordIfRequired() + { + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(true); + $hasher->shouldReceive('make')->once()->with('plain')->andReturn('rehashed'); + + $conn = m::mock(Connection::class); + $table = m::mock(ConnectionInterface::class); + $conn->shouldReceive('table')->once()->with('foo')->andReturn($table); + $table->shouldReceive('where')->once()->with('id', 1)->andReturnSelf(); + $table->shouldReceive('update')->once()->with(['password_attribute' => 'rehashed']); + + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthIdentifierName')->once()->andReturn('id'); + $user->shouldReceive('getAuthIdentifier')->once()->andReturn(1); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $user->shouldReceive('getAuthPasswordName')->once()->andReturn('password_attribute'); + + $provider = new DatabaseUserProvider($conn, $hasher, 'foo'); + $provider->rehashPasswordIfRequired($user, ['password' => 'plain']); + } + + public function testDontRehashPasswordIfNotRequired() + { + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false); + $hasher->shouldNotReceive('make'); + + $conn = m::mock(Connection::class); + $table = m::mock(ConnectionInterface::class); + $conn->shouldNotReceive('table'); + $table->shouldNotReceive('where'); + $table->shouldNotReceive('update'); + + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $user->shouldNotReceive('getAuthIdentifierName'); + $user->shouldNotReceive('getAuthIdentifier'); + $user->shouldNotReceive('getAuthPasswordName'); + + $provider = new DatabaseUserProvider($conn, $hasher, 'foo'); + $provider->rehashPasswordIfRequired($user, ['password' => 'plain']); + } } diff --git a/tests/Auth/AuthEloquentUserProviderTest.php b/tests/Auth/AuthEloquentUserProviderTest.php index 9ef0e29636b0..21d181b97ad0 100755 --- a/tests/Auth/AuthEloquentUserProviderTest.php +++ b/tests/Auth/AuthEloquentUserProviderTest.php @@ -140,6 +140,50 @@ public function testCredentialValidation() $this->assertTrue($result); } + public function testCredentialValidationFailed() + { + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('check')->once()->with('plain', 'hash')->andReturn(false); + $provider = new EloquentUserProvider($hasher, 'foo'); + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $result = $provider->validateCredentials($user, ['password' => 'plain']); + + $this->assertFalse($result); + } + + public function testRehashPasswordIfRequired() + { + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(true); + $hasher->shouldReceive('make')->once()->with('plain')->andReturn('rehashed'); + + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $user->shouldReceive('getAuthPasswordName')->once()->andReturn('password_attribute'); + $user->shouldReceive('forceFill')->once()->with(['password_attribute' => 'rehashed'])->andReturnSelf(); + $user->shouldReceive('save')->once(); + + $provider = new EloquentUserProvider($hasher, 'foo'); + $provider->rehashPasswordIfRequired($user, ['password' => 'plain']); + } + + public function testDontRehashPasswordIfNotRequired() + { + $hasher = m::mock(Hasher::class); + $hasher->shouldReceive('needsRehash')->once()->with('hash')->andReturn(false); + $hasher->shouldNotReceive('make'); + + $user = m::mock(Authenticatable::class); + $user->shouldReceive('getAuthPassword')->once()->andReturn('hash'); + $user->shouldNotReceive('getAuthPasswordName'); + $user->shouldNotReceive('forceFill'); + $user->shouldNotReceive('save'); + + $provider = new EloquentUserProvider($hasher, 'foo'); + $provider->rehashPasswordIfRequired($user, ['password' => 'plain']); + } + public function testModelsCanBeCreated() { $hasher = m::mock(Hasher::class); diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index 52c4cfe7d1c8..e75efd642c37 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -103,6 +103,7 @@ public function testAttemptCallsRetrieveByCredentials() $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo']); + $guard->getProvider()->shouldNotReceive('rehashPasswordIfRequired'); $guard->attempt(['foo']); } @@ -119,6 +120,7 @@ public function testAttemptReturnsUserInterface() $user = $this->createMock(Authenticatable::class); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn($user); $guard->getProvider()->shouldReceive('validateCredentials')->with($user, ['foo'])->andReturn(true); + $guard->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once(); $guard->expects($this->once())->method('login')->with($this->equalTo($user)); $this->assertTrue($guard->attempt(['foo'])); } @@ -135,6 +137,7 @@ public function testAttemptReturnsFalseIfUserNotGiven() $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); $mock->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn(null); + $mock->getProvider()->shouldNotReceive('rehashPasswordIfRequired'); $this->assertFalse($mock->attempt(['foo'])); } @@ -159,6 +162,7 @@ public function testAttemptAndWithCallbacks() $mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user); $mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue(); $mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse(); + $mock->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once(); $this->assertTrue($mock->attemptWhen(['foo'], function ($user, $guard) { static::assertInstanceOf(Authenticatable::class, $user); @@ -183,6 +187,44 @@ public function testAttemptAndWithCallbacks() $this->assertFalse($executed); } + public function testAttemptRehashesPasswordWhenRequired() + { + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login'])->setConstructorArgs(['default', $provider, $session, $request, $timebox])->getMock(); + $guard->setDispatcher($events = m::mock(Dispatcher::class)); + $timebox->shouldReceive('call')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) { + return $callback($timebox->shouldReceive('returnEarly')->once()->getMock()); + }); + $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); + $events->shouldReceive('dispatch')->once()->with(m::type(Validated::class)); + $user = $this->createMock(Authenticatable::class); + $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn($user); + $guard->getProvider()->shouldReceive('validateCredentials')->with($user, ['foo'])->andReturn(true); + $guard->getProvider()->shouldReceive('rehashPasswordIfRequired')->with($user, ['foo'])->once(); + $guard->expects($this->once())->method('login')->with($this->equalTo($user)); + $this->assertTrue($guard->attempt(['foo'])); + } + + public function testAttemptDoesntRehashPasswordWhenDisabled() + { + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login']) + ->setConstructorArgs(['default', $provider, $session, $request, $timebox, $rehashOnLogin = false]) + ->getMock(); + $guard->setDispatcher($events = m::mock(Dispatcher::class)); + $timebox->shouldReceive('call')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) { + return $callback($timebox->shouldReceive('returnEarly')->once()->getMock()); + }); + $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); + $events->shouldReceive('dispatch')->once()->with(m::type(Validated::class)); + $user = $this->createMock(Authenticatable::class); + $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->andReturn($user); + $guard->getProvider()->shouldReceive('validateCredentials')->with($user, ['foo'])->andReturn(true); + $guard->getProvider()->shouldNotReceive('rehashPasswordIfRequired'); + $guard->expects($this->once())->method('login')->with($this->equalTo($user)); + $this->assertTrue($guard->attempt(['foo'])); + } + public function testLoginStoresIdentifierInSession() { [$session, $provider, $request, $cookie] = $this->getMocks(); @@ -235,6 +277,7 @@ public function testFailedAttemptFiresFailedEvent() $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn(null); + $guard->getProvider()->shouldNotReceive('rehashPasswordIfRequired'); $guard->attempt(['foo']); } From ae9e287bf7e4387c7e1dd0e5a34eaf03a5ddb351 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Sun, 10 Dec 2023 13:41:37 +0000 Subject: [PATCH 79/99] Update facade docblocks --- src/Illuminate/Support/Facades/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Auth.php b/src/Illuminate/Support/Facades/Auth.php index a93e3244ae44..32b2eb64aa95 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -40,7 +40,7 @@ * @method static \Symfony\Component\HttpFoundation\Response|null onceBasic(string $field = 'email', array $extraConditions = []) * @method static bool attemptWhen(array $credentials = [], array|callable|null $callbacks = null, bool $remember = false) * @method static void logoutCurrentDevice() - * @method static \Illuminate\Contracts\Auth\Authenticatable|null logoutOtherDevices(string $password, string $attribute = 'password') + * @method static \Illuminate\Contracts\Auth\Authenticatable|null logoutOtherDevices(string $password) * @method static void attempting(mixed $callback) * @method static \Illuminate\Contracts\Auth\Authenticatable getLastAttempted() * @method static string getName() From 21a83730d24b2fba1ba7f5e04cea301efc33f848 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 10 Dec 2023 07:45:34 -0600 Subject: [PATCH 80/99] add on rehash on login --- config/hashing.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/hashing.php b/config/hashing.php index 2925f725b487..f34179a8f19a 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -51,4 +51,17 @@ 'verify' => true, ], + /* + |-------------------------------------------------------------------------- + | Rehash On Login + |-------------------------------------------------------------------------- + | + | Setting this option to true will tell Laravel to automatically rehash + | the user's password during login if the configured work factor for + | the algorithm has changed, allowing graceful upgrades of hashes. + | + */ + + 'rehash_on_login' => true, + ]; From cc5d95cf0458767558d694c960f8dc84650e0384 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Mon, 11 Dec 2023 19:43:49 +0330 Subject: [PATCH 81/99] [10.x] Include partitioned tables on PostgreSQL when retrieving tables (#49326) * wip * include partitioned tables on getTables query * fix tests * fix tests * fix getTables on legacy sqlite version * fix tests --- .../Schema/Grammars/PostgresGrammar.php | 4 ++-- .../Database/Schema/SQLiteBuilder.php | 2 +- .../Postgres/PostgresSchemaBuilderTest.php | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 2995e339d0d5..9e18f2d9a773 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -87,7 +87,7 @@ public function compileTables() { return 'select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, ' ."obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " - ."where c.relkind = 'r' and n.oid = c.relnamespace " + ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema')" .'order by c.relname'; } @@ -98,7 +98,7 @@ public function compileTables() */ public function compileViews() { - return 'select viewname as name, schemaname as schema, definition from pg_views order by viewname'; + return "select viewname as name, schemaname as schema, definition from pg_views where schemaname not in ('pg_catalog', 'information_schema') order by viewname"; } /** diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 9e7960ba3a65..e2cae2c07c9d 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -37,7 +37,7 @@ public function dropDatabaseIfExists($name) */ public function getTables() { - $withSize = $this->connection->scalar($this->grammar->compileDbstatExists()); + $withSize = rescue(fn () => $this->connection->scalar($this->grammar->compileDbstatExists()), false, false); return $this->connection->getPostProcessor()->processTables( $this->connection->selectFromWriteConnection($this->grammar->compileTables($withSize)) diff --git a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php index 99240f81b665..6f7f042fa405 100644 --- a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php +++ b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php @@ -165,6 +165,29 @@ public function testGetViews() })); } + public function testDropPartitionedTables() + { + DB::statement('create table groups (id bigserial, tenant_id bigint, name varchar, primary key (id, tenant_id)) partition by hash (tenant_id)'); + DB::statement('create table groups_1 partition of groups for values with (modulus 2, remainder 0)'); + DB::statement('create table groups_2 partition of groups for values with (modulus 2, remainder 1)'); + + $tables = array_column(Schema::getTables(), 'name'); + + $this->assertContains('groups', $tables); + $this->assertContains('groups_1', $tables); + $this->assertContains('groups_2', $tables); + + Schema::dropAllTables(); + + $this->artisan('migrate:install'); + + $tables = array_column(Schema::getTables(), 'name'); + + $this->assertNotContains('groups', $tables); + $this->assertNotContains('groups_1', $tables); + $this->assertNotContains('groups_2', $tables); + } + protected function hasView($schema, $table) { return DB::table('information_schema.views') From aeb284959f15f8a5eb79eef5b29734bfd7c1ccbc Mon Sep 17 00:00:00 2001 From: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:48:56 +0200 Subject: [PATCH 82/99] [10.x] Allow to pass `Arrayable` or `Stringble` in rules `In` and `NotIn` (#49055) * Allow to pass `Arrayable` or `Stringble` in rules `In` and `NotIn` * Add tests --- src/Illuminate/Validation/Rules/In.php | 11 ++++++--- src/Illuminate/Validation/Rules/NotIn.php | 11 ++++++--- tests/Validation/ValidationInRuleTest.php | 16 +++++++++++++ tests/Validation/ValidationNotInRuleTest.php | 25 ++++++++++++++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Validation/Rules/In.php b/src/Illuminate/Validation/Rules/In.php index 18c70bdf59be..a140fd399455 100644 --- a/src/Illuminate/Validation/Rules/In.php +++ b/src/Illuminate/Validation/Rules/In.php @@ -3,6 +3,7 @@ namespace Illuminate\Validation\Rules; use BackedEnum; +use Illuminate\Contracts\Support\Arrayable; use UnitEnum; class In @@ -24,12 +25,16 @@ class In /** * Create a new in rule instance. * - * @param array $values + * @param \Illuminate\Contracts\Support\Arrayable|array|string $values * @return void */ - public function __construct(array $values) + public function __construct($values) { - $this->values = $values; + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + $this->values = is_array($values) ? $values : func_get_args(); } /** diff --git a/src/Illuminate/Validation/Rules/NotIn.php b/src/Illuminate/Validation/Rules/NotIn.php index 5768bfa0007d..f73648a3073d 100644 --- a/src/Illuminate/Validation/Rules/NotIn.php +++ b/src/Illuminate/Validation/Rules/NotIn.php @@ -3,6 +3,7 @@ namespace Illuminate\Validation\Rules; use BackedEnum; +use Illuminate\Contracts\Support\Arrayable; use UnitEnum; class NotIn @@ -24,12 +25,16 @@ class NotIn /** * Create a new "not in" rule instance. * - * @param array $values + * @param \Illuminate\Contracts\Support\Arrayable|array|string $values * @return void */ - public function __construct(array $values) + public function __construct($values) { - $this->values = $values; + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + $this->values = is_array($values) ? $values : func_get_args(); } /** diff --git a/tests/Validation/ValidationInRuleTest.php b/tests/Validation/ValidationInRuleTest.php index 1bf213dbe367..25814076ea4f 100644 --- a/tests/Validation/ValidationInRuleTest.php +++ b/tests/Validation/ValidationInRuleTest.php @@ -17,10 +17,22 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('in:"Laravel","Framework","PHP"', (string) $rule); + $rule = new In(collect(['Taylor', 'Michael', 'Tim'])); + + $this->assertSame('in:"Taylor","Michael","Tim"', (string) $rule); + $rule = new In(['Life, the Universe and Everything', 'this is a "quote"']); $this->assertSame('in:"Life, the Universe and Everything","this is a ""quote"""', (string) $rule); + $rule = Rule::in(collect([1, 2, 3, 4])); + + $this->assertSame('in:"1","2","3","4"', (string) $rule); + + $rule = Rule::in(collect([1, 2, 3, 4])); + + $this->assertSame('in:"1","2","3","4"', (string) $rule); + $rule = new In(["a,b\nc,d"]); $this->assertSame("in:\"a,b\nc,d\"", (string) $rule); @@ -41,6 +53,10 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('in:"1","2","3","4"', (string) $rule); + $rule = new In('1', '2', '3', '4'); + + $this->assertSame('in:"1","2","3","4"', (string) $rule); + $rule = Rule::in([StringStatus::done]); $this->assertSame('in:"done"', (string) $rule); diff --git a/tests/Validation/ValidationNotInRuleTest.php b/tests/Validation/ValidationNotInRuleTest.php index c33778ff3214..ba00d81a3a51 100644 --- a/tests/Validation/ValidationNotInRuleTest.php +++ b/tests/Validation/ValidationNotInRuleTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Validation; +use Illuminate\Tests\Validation\fixtures\Values; use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\NotIn; use PHPUnit\Framework\TestCase; @@ -16,6 +17,18 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('not_in:"Laravel","Framework","PHP"', (string) $rule); + $rule = new NotIn(collect(['Taylor', 'Michael', 'Tim'])); + + $this->assertSame('not_in:"Taylor","Michael","Tim"', (string) $rule); + + $rule = Rule::notIn(collect([1, 2, 3, 4])); + + $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + + $rule = Rule::notIn(collect([1, 2, 3, 4])); + + $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + $rule = Rule::notIn([1, 2, 3, 4]); $this->assertSame('not_in:"1","2","3","4"', (string) $rule); @@ -24,10 +37,22 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + $rule = Rule::notIn(new Values); + + $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + + $rule = new NotIn(new Values); + + $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + $rule = Rule::notIn('1', '2', '3', '4'); $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + $rule = new NotIn('1', '2', '3', '4'); + + $this->assertSame('not_in:"1","2","3","4"', (string) $rule); + $rule = Rule::notIn([StringStatus::done]); $this->assertSame('not_in:"done"', (string) $rule); From a111adaff3d76376f3ff9573554bbc0944416478 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 11 Dec 2023 16:50:20 +0000 Subject: [PATCH 83/99] Adds `update_date_on_publish` to configuration file (#49330) --- config/database.php | 5 ++- .../Database/Console/DumpCommand.php | 6 ++- .../Database/MigrationServiceProvider.php | 4 +- .../Foundation/Testing/DatabaseTruncation.php | 8 ++-- src/Illuminate/Support/ServiceProvider.php | 4 +- tests/Support/SupportServiceProviderTest.php | 43 ++++++++++++++++++- 6 files changed, 62 insertions(+), 8 deletions(-) diff --git a/config/database.php b/config/database.php index ff5880bd2e8e..f030206fc946 100644 --- a/config/database.php +++ b/config/database.php @@ -121,7 +121,10 @@ | */ - 'migrations' => 'migrations', + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], /* |-------------------------------------------------------------------------- diff --git a/src/Illuminate/Database/Console/DumpCommand.php b/src/Illuminate/Database/Console/DumpCommand.php index 27121281a3fa..49d80257aa10 100644 --- a/src/Illuminate/Database/Console/DumpCommand.php +++ b/src/Illuminate/Database/Console/DumpCommand.php @@ -69,8 +69,12 @@ public function handle(ConnectionResolverInterface $connections, Dispatcher $dis */ protected function schemaState(Connection $connection) { + $migrations = Config::get('database.migrations', 'migrations'); + + $migrationTable = is_array($migrations) ? ($migrations['table'] ?? 'migrations') : $migrations; + return $connection->getSchemaState() - ->withMigrationTable($connection->getTablePrefix().Config::get('database.migrations', 'migrations')) + ->withMigrationTable($connection->getTablePrefix().$migrationTable) ->handleOutputUsing(function ($type, $buffer) { $this->output->write($buffer); }); diff --git a/src/Illuminate/Database/MigrationServiceProvider.php b/src/Illuminate/Database/MigrationServiceProvider.php index d61f4423240c..4dad13838cb3 100755 --- a/src/Illuminate/Database/MigrationServiceProvider.php +++ b/src/Illuminate/Database/MigrationServiceProvider.php @@ -59,7 +59,9 @@ public function register() protected function registerRepository() { $this->app->singleton('migration.repository', function ($app) { - $table = $app['config']['database.migrations']; + $migrations = $app['config']['database.migrations']; + + $table = is_array($migrations) ? ($migrations['table'] ?? null) : $migrations; return new DatabaseMigrationRepository($app['db'], $table); }); diff --git a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index cea2d8d09250..6eb86d9b47a7 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -130,9 +130,11 @@ protected function connectionsToTruncate(): array */ protected function exceptTables(?string $connectionName): array { - if (property_exists($this, 'exceptTables')) { - $migrationsTable = $this->app['config']->get('database.migrations'); + $migrations = $this->app['config']->get('database.migrations'); + + $migrationsTable = is_array($migrations) ? ($migrations['table'] ?? null) : $migrations; + if (property_exists($this, 'exceptTables')) { if (array_is_list($this->exceptTables ?? [])) { return array_merge( $this->exceptTables ?? [], @@ -146,7 +148,7 @@ protected function exceptTables(?string $connectionName): array ); } - return [$this->app['config']->get('database.migrations')]; + return [$migrationsTable]; } /** diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index fd632e8c7ed3..561eb740902e 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -285,7 +285,9 @@ protected function publishesMigrations(array $paths, $groups = null) { $this->publishes($paths, $groups); - static::$publishableMigrationPaths = array_unique(array_merge(static::$publishableMigrationPaths, array_keys($paths))); + if ($this->app->config->get('database.migrations.update_date_on_publish', false)) { + static::$publishableMigrationPaths = array_unique(array_merge(static::$publishableMigrationPaths, array_keys($paths))); + } } /** diff --git a/tests/Support/SupportServiceProviderTest.php b/tests/Support/SupportServiceProviderTest.php index 4861a9e97291..2cf1cf17b63f 100644 --- a/tests/Support/SupportServiceProviderTest.php +++ b/tests/Support/SupportServiceProviderTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Support; +use Illuminate\Config\Repository as Config; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; use Mockery as m; @@ -9,12 +10,21 @@ class SupportServiceProviderTest extends TestCase { + protected $app; + protected function setUp(): void { ServiceProvider::$publishes = []; ServiceProvider::$publishGroups = []; - $app = m::mock(Application::class)->makePartial(); + $this->app = $app = m::mock(Application::class)->makePartial(); + $config = m::mock(Config::class)->makePartial(); + + $config = new Config(); + + $app->instance('config', $config); + $config->set('database.migrations.update_date_on_publish', true); + $one = new ServiceProviderForTestingOne($app); $one->boot(); $two = new ServiceProviderForTestingTwo($app); @@ -119,6 +129,37 @@ public function testMultipleTaggedAssetsAreMergedCorrectly() ]; $this->assertEquals($expected, $toPublish, 'Service provider does not return expected set of published tagged paths.'); } + + public function testPublishesMigrations() + { + $serviceProvider = new ServiceProviderForTestingOne($this->app); + + (fn () => $this->publishesMigrations(['source/tagged/four' => 'destination/tagged/four'], 'tag_four')) + ->call($serviceProvider); + + $this->assertContains('source/tagged/four', ServiceProvider::publishableMigrationPaths()); + + $this->app->config->set('database.migrations.update_date_on_publish', false); + + (fn () => $this->publishesMigrations(['source/tagged/five' => 'destination/tagged/five'], 'tag_four')) + ->call($serviceProvider); + + $this->assertNotContains('source/tagged/five', ServiceProvider::publishableMigrationPaths()); + + $this->app->config->set('database.migrations', 'migrations'); + + (fn () => $this->publishesMigrations(['source/tagged/five' => 'destination/tagged/five'], 'tag_four')) + ->call($serviceProvider); + + $this->assertNotContains('source/tagged/five', ServiceProvider::publishableMigrationPaths()); + + $this->app->config->set('database.migrations', null); + + (fn () => $this->publishesMigrations(['source/tagged/five' => 'destination/tagged/five'], 'tag_four')) + ->call($serviceProvider); + + $this->assertNotContains('source/tagged/five', ServiceProvider::publishableMigrationPaths()); + } } class ServiceProviderForTestingOne extends ServiceProvider From 062450da9f7821f900fad8204a73cb5f0857f7ea Mon Sep 17 00:00:00 2001 From: Aimeos Date: Mon, 11 Dec 2023 17:51:38 +0100 Subject: [PATCH 84/99] [10.x] Display error message if json_encode() fails (#48856) * Display error message if json_encode() fails Adding the error code to the exception isn't useful. Instead, the error message can give details about the problem. * Added error code as suggested * Fixed coding style issue --- src/Illuminate/Queue/Queue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index 0ce7ad1ac1ce..4f36b80db75b 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -107,7 +107,7 @@ protected function createPayload($job, $queue, $data = '') if (json_last_error() !== JSON_ERROR_NONE) { throw new InvalidPayloadException( - 'Unable to JSON encode payload. Error code: '.json_last_error(), $value + 'Unable to JSON encode payload. Error ('.json_last_error().'): '.json_last_error_msg(), $value ); } From 2039e83fc432e2f92642ce55547681015129d0c8 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Tue, 12 Dec 2023 06:22:43 +1100 Subject: [PATCH 85/99] [10.x] Allow error list per field (#49309) * Allow error list per field * Fix bug with existing assertion --- src/Illuminate/Testing/TestResponse.php | 28 ++++++++------- tests/Testing/TestResponseTest.php | 48 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 47e4cab56e47..c78cecb64c38 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -1205,26 +1205,28 @@ public function assertInvalid($errors = null, foreach (Arr::wrap($errors) as $key => $value) { PHPUnit::assertArrayHasKey( - (is_int($key)) ? $value : $key, + $resolvedKey = (is_int($key)) ? $value : $key, $sessionErrors, - "Failed to find a validation error in session for key: '{$value}'".PHP_EOL.PHP_EOL.$errorMessage + "Failed to find a validation error in session for key: '{$resolvedKey}'".PHP_EOL.PHP_EOL.$errorMessage ); - if (! is_int($key)) { - $hasError = false; + foreach (Arr::wrap($value) as $message) { + if (! is_int($key)) { + $hasError = false; - foreach (Arr::wrap($sessionErrors[$key]) as $sessionErrorMessage) { - if (Str::contains($sessionErrorMessage, $value)) { - $hasError = true; + foreach (Arr::wrap($sessionErrors[$key]) as $sessionErrorMessage) { + if (Str::contains($sessionErrorMessage, $message)) { + $hasError = true; - break; + break; + } } - } - if (! $hasError) { - PHPUnit::fail( - "Failed to find a validation error for key and message: '$key' => '$value'".PHP_EOL.PHP_EOL.$errorMessage - ); + if (! $hasError) { + PHPUnit::fail( + "Failed to find a validation error for key and message: '$key' => '$message'".PHP_EOL.PHP_EOL.$errorMessage + ); + } } } } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 46d05ce970bf..6f565e981f8a 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -1500,6 +1500,54 @@ public function testAssertSessionValidationErrorsUsingAssertValid() $testResponse->assertValid(); } + public function testAssertingKeyIsInvalidErrorMessage() + { + app()->instance('session.store', $store = new Store('test-session', new ArraySessionHandler(1))); + $store->put('errors', $errorBag = new ViewErrorBag); + $testResponse = TestResponse::fromBaseResponse(new Response); + + try { + $testResponse->assertInvalid(['input_name']); + $this->fail(); + } catch (AssertionFailedError $e) { + $this->assertStringStartsWith("Failed to find a validation error in session for key: 'input_name'", $e->getMessage()); + } + + try { + $testResponse->assertInvalid(['input_name' => 'Expected error message.']); + $this->fail(); + } catch (AssertionFailedError $e) { + $this->assertStringStartsWith("Failed to find a validation error in session for key: 'input_name'", $e->getMessage()); + } + } + + public function testInvalidWithListOfErrors() + { + app()->instance('session.store', $store = new Store('test-session', new ArraySessionHandler(1))); + + $store->put('errors', $errorBag = new ViewErrorBag); + + $errorBag->put('default', new MessageBag([ + 'first_name' => [ + 'Your first name is required', + 'Your first name must be at least 1 character', + ], + ])); + + $testResponse = TestResponse::fromBaseResponse(new Response); + + $testResponse->assertInvalid(['first_name' => 'Your first name is required']); + $testResponse->assertInvalid(['first_name' => 'Your first name must be at least 1 character']); + $testResponse->assertInvalid(['first_name' => ['Your first name is required', 'Your first name must be at least 1 character']]); + + try { + $testResponse->assertInvalid(['first_name' => ['Your first name is required', 'FOO']]); + $this->fail(); + } catch (AssertionFailedError $e) { + $this->assertStringStartsWith("Failed to find a validation error for key and message: 'first_name' => 'FOO'", $e->getMessage()); + } + } + public function testAssertJsonValidationErrorsCustomErrorsName() { $data = [ From 7907bfb50165b3951403e2d9882b4f8e0ee26b9e Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Mon, 11 Dec 2023 23:01:00 +0330 Subject: [PATCH 86/99] [10.x] Get foreign keys of a table (#49264) * get foreign keys * fix tests * fix tests * fix tests * fix tests --- .../Query/Processors/MySqlProcessor.php | 23 +++++++++ .../Query/Processors/PostgresProcessor.php | 37 +++++++++++++++ .../Database/Query/Processors/Processor.php | 11 +++++ .../Query/Processors/SQLiteProcessor.php | 23 +++++++++ .../Query/Processors/SqlServerProcessor.php | 23 +++++++++ src/Illuminate/Database/Schema/Builder.php | 15 ++++++ .../Database/Schema/Grammars/MySqlGrammar.php | 26 ++++++++++ .../Schema/Grammars/PostgresGrammar.php | 30 ++++++++++++ .../Schema/Grammars/SQLiteGrammar.php | 17 +++++++ .../Schema/Grammars/SqlServerGrammar.php | 29 ++++++++++++ .../Database/Schema/MySqlBuilder.php | 17 +++++++ .../Database/Schema/PostgresBuilder.php | 17 +++++++ src/Illuminate/Support/Facades/Schema.php | 1 + .../Database/SchemaBuilderTest.php | 47 +++++++++++++++++++ 14 files changed, 316 insertions(+) diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 7e6c66face3a..091fc80b52b0 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -63,4 +63,27 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => $result->foreign_schema, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => strtolower($result->on_update), + 'on_delete' => strtolower($result->on_delete), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index c45ae4ae57ee..ddfbfe722da2 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -91,4 +91,41 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => $result->foreign_schema, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => match (strtolower($result->on_update)) { + 'a' => 'no action', + 'r' => 'restrict', + 'c' => 'cascade', + 'n' => 'set null', + 'd' => 'set default', + default => null, + }, + 'on_delete' => match (strtolower($result->on_delete)) { + 'a' => 'no action', + 'r' => 'restrict', + 'c' => 'cascade', + 'n' => 'set null', + 'd' => 'set default', + default => null, + }, + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 7abf156ad662..87a15c6d7a1d 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -99,6 +99,17 @@ public function processIndexes($results) return $results; } + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return $results; + } + /** * Process the results of a column listing query. * diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index 6c6da5567dba..8f5fb98206e0 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -79,4 +79,27 @@ public function processIndexes($results) return $indexes; } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => null, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => null, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => strtolower($result->on_update), + 'on_delete' => strtolower($result->on_delete), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index 15fa4d740745..c089593ed86a 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -121,4 +121,27 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => $result->foreign_schema, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => strtolower(str_replace('_', ' ', $result->on_update)), + 'on_delete' => strtolower(str_replace('_', ' ', $result->on_delete)), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 07698e7fc206..efcad17fced3 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -357,6 +357,21 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection($this->grammar->compileForeignKeys($table)) + ); + } + /** * Modify a table on the schema. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 43875591e07f..11ec0df9079b 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -185,6 +185,32 @@ public function compileIndexes($database, $table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $database + * @param string $table + * @return string + */ + public function compileForeignKeys($database, $table) + { + return sprintf( + 'select kc.constraint_name as `name`, ' + .'group_concat(kc.column_name order by kc.ordinal_position) as `columns`, ' + .'kc.referenced_table_schema as `foreign_schema`, ' + .'kc.referenced_table_name as `foreign_table`, ' + .'group_concat(kc.referenced_column_name order by kc.ordinal_position) as `foreign_columns`, ' + .'rc.update_rule as `on_update`, ' + .'rc.delete_rule as `on_delete` ' + .'from information_schema.key_column_usage kc join information_schema.referential_constraints rc ' + .'on kc.constraint_schema = rc.constraint_schema and kc.constraint_name = rc.constraint_name ' + .'where kc.table_schema = %s and kc.table_name = %s and kc.referenced_table_name is not null ' + .'group by kc.constraint_name, kc.referenced_table_schema, kc.referenced_table_name, rc.update_rule, rc.delete_rule', + $this->quoteString($database), + $this->quoteString($table) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 9e18f2d9a773..094d49605d19 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -189,6 +189,36 @@ public function compileIndexes($schema, $table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $schema + * @param string $table + * @return string + */ + public function compileForeignKeys($schema, $table) + { + return sprintf( + 'select c.conname as name, ' + ."string_agg(la.attname, ',' order by conseq.ord) as columns, " + .'fn.nspname as foreign_schema, fc.relname as foreign_table, ' + ."string_agg(fa.attname, ',' order by conseq.ord) as foreign_columns, " + .'c.confupdtype as on_update, c.confdeltype as on_delete ' + .'from pg_constraint c ' + .'join pg_class tc on c.conrelid = tc.oid ' + .'join pg_namespace tn on tn.oid = tc.relnamespace ' + .'join pg_class fc on c.confrelid = fc.oid ' + .'join pg_namespace fn on fn.oid = fc.relnamespace ' + .'join lateral unnest(c.conkey) with ordinality as conseq(num, ord) on true ' + .'join pg_attribute la on la.attrelid = c.conrelid and la.attnum = conseq.num ' + .'join pg_attribute fa on fa.attrelid = c.confrelid and fa.attnum = c.confkey[conseq.ord] ' + ."where c.contype = 'f' and tc.relname = %s and tn.nspname = %s " + .'group by c.conname, fn.nspname, fc.relname, c.confupdtype, c.confdeltype', + $this->quoteString($table), + $this->quoteString($schema) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index a008fb73b0c6..e9c7d8c80490 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -146,6 +146,23 @@ public function compileIndexes($table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $table + * @return string + */ + public function compileForeignKeys($table) + { + return sprintf( + 'select group_concat("from") as columns, "table" as foreign_table, ' + .'group_concat("to") as foreign_columns, on_update, on_delete ' + .'from (select * from pragma_foreign_key_list(%s) order by id desc, seq) ' + .'group by id, "table", on_update, on_delete', + $this->wrap(str_replace('.', '__', $table)) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index b4927d5f3432..4b9091a1d6ef 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -189,6 +189,35 @@ public function compileIndexes($table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $table + * @return string + */ + public function compileForeignKeys($table) + { + return sprintf( + 'select fk.name as name, ' + ."string_agg(lc.name, ',') within group (order by fkc.constraint_column_id) as columns, " + .'fs.name as foreign_schema, ft.name as foreign_table, ' + ."string_agg(fc.name, ',') within group (order by fkc.constraint_column_id) as foreign_columns, " + .'fk.update_referential_action_desc as on_update, ' + .'fk.delete_referential_action_desc as on_delete ' + .'from sys.foreign_keys as fk ' + .'join sys.foreign_key_columns as fkc on fkc.constraint_object_id = fk.object_id ' + .'join sys.tables as lt on lt.object_id = fk.parent_object_id ' + .'join sys.schemas as ls on lt.schema_id = ls.schema_id ' + .'join sys.columns as lc on fkc.parent_object_id = lc.object_id and fkc.parent_column_id = lc.column_id ' + .'join sys.tables as ft on ft.object_id = fk.referenced_object_id ' + .'join sys.schemas as fs on ft.schema_id = fs.schema_id ' + .'join sys.columns as fc on fkc.referenced_object_id = fc.object_id and fkc.referenced_column_id = fc.column_id ' + .'where lt.name = %s and ls.name = SCHEMA_NAME() ' + .'group by fk.name, fs.name, ft.name, fk.update_referential_action_desc, fk.delete_referential_action_desc', + $this->quoteString($table) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/MySqlBuilder.php b/src/Illuminate/Database/Schema/MySqlBuilder.php index e51305002480..0c537ba980cd 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -120,6 +120,23 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection( + $this->grammar->compileForeignKeys($this->connection->getDatabaseName(), $table) + ) + ); + } + /** * Drop all tables from the database. * diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 0efe5dc62161..4990cb445d98 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -220,6 +220,23 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + [, $schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection($this->grammar->compileForeignKeys($schema, $table)) + ); + } + /** * Get the schemas for the connection. * diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 1bf87ba9a151..934d27e6f508 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -22,6 +22,7 @@ * @method static array getColumnListing(string $table) * @method static array getColumns(string $table) * @method static array getIndexes(string $table) + * @method static array getForeignKeys(string $table) * @method static void table(string $table, \Closure $callback) * @method static void create(string $table, \Closure $callback) * @method static void drop(string $table) diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2e79303b0574..ea9a6d905031 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -261,4 +261,51 @@ public function testGetFullTextIndexes() $this->assertTrue(collect($indexes)->contains(fn ($index) => $index['columns'] === ['id'] && $index['primary'])); $this->assertTrue(collect($indexes)->contains('name', 'articles_body_title_fulltext')); } + + public function testGetForeignKeys() + { + Schema::create('users', function (Blueprint $table) { + $table->id(); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnUpdate()->nullOnDelete(); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['user_id'] + && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['id'] + && $foreign['on_update'] === 'cascade' && $foreign['on_delete'] === 'set null' + )); + } + + public function testGetCompoundForeignKeys() + { + Schema::create('parent', function (Blueprint $table) { + $table->id(); + $table->integer('a'); + $table->integer('b'); + + $table->unique(['b', 'a']); + }); + + Schema::create('child', function (Blueprint $table) { + $table->integer('c'); + $table->integer('d'); + + $table->foreign(['d', 'c'], 'test_fk')->references(['b', 'a'])->on('parent'); + }); + + $foreignKeys = Schema::getForeignKeys('child'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['d', 'c'] + && $foreign['foreign_table'] === 'parent' + && $foreign['foreign_columns'] === ['b', 'a'] + )); + } } From f30906eff7bbe90bd5f412cfb37901bb0bf0f1ba Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Dec 2023 21:49:25 +0800 Subject: [PATCH 87/99] [10.x] PHPStan Improvements (#49343) Compatible with PHPStan 1.10.49 Signed-off-by: Mior Muhammad Zaki --- types/Support/Collection.php | 32 ++++++++++++++++---------------- types/Support/LazyCollection.php | 12 ++++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/types/Support/Collection.php b/types/Support/Collection.php index f2f5311c8c85..77861041deaf 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -264,7 +264,7 @@ public function __invoke(): string return true; })); -assertType('Illuminate\Support\Collection|void', $collection->when(true, function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->when(true, function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->when(true, function ($collection) { @@ -272,12 +272,12 @@ public function __invoke(): string return 'string'; })); -assertType('Illuminate\Support\Collection|void', $collection->when('Taylor', function ($collection, $name) { +assertType('Illuminate\Support\Collection|null', $collection->when('Taylor', function ($collection, $name) { assertType('Illuminate\Support\Collection', $collection); assertType('string', $name); })); assertType( - 'Illuminate\Support\Collection|void', + 'Illuminate\Support\Collection|null', $collection->when( 'Taylor', function ($collection, $name) { @@ -290,12 +290,12 @@ function ($collection, $name) { } ) ); -assertType('Illuminate\Support\Collection|void', $collection->when(fn () => 'Taylor', function ($collection, $name) { +assertType('Illuminate\Support\Collection|null', $collection->when(fn () => 'Taylor', function ($collection, $name) { assertType('Illuminate\Support\Collection', $collection); assertType('string', $name); })); assertType( - 'Illuminate\Support\Collection|void', + 'Illuminate\Support\Collection|null', $collection->when( function ($collection) { assertType('Illuminate\Support\Collection', $collection); @@ -313,7 +313,7 @@ function ($collection, $count) { ) ); -assertType('Illuminate\Support\Collection|void', $collection->when($invokable, function ($collection, $param) { +assertType('Illuminate\Support\Collection|null', $collection->when($invokable, function ($collection, $param) { assertType('Illuminate\Support\Collection', $collection); assertType('Invokable', $param); })); @@ -323,7 +323,7 @@ function ($collection, $count) { return true; })); -assertType('Illuminate\Support\Collection|void', $collection->whenEmpty(function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->whenEmpty(function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->whenEmpty(function ($collection) { @@ -337,7 +337,7 @@ function ($collection, $count) { return true; })); -assertType('Illuminate\Support\Collection|void', $collection->whenNotEmpty(function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->whenNotEmpty(function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->whenNotEmpty(function ($collection) { @@ -351,7 +351,7 @@ function ($collection, $count) { return true; })); -assertType('Illuminate\Support\Collection|void', $collection->unless(true, function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->unless(true, function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->unless(true, function ($collection) { @@ -359,12 +359,12 @@ function ($collection, $count) { return 'string'; })); -assertType('Illuminate\Support\Collection|void', $collection->unless('Taylor', function ($collection, $name) { +assertType('Illuminate\Support\Collection|null', $collection->unless('Taylor', function ($collection, $name) { assertType('Illuminate\Support\Collection', $collection); assertType('string', $name); })); assertType( - 'Illuminate\Support\Collection|void', + 'Illuminate\Support\Collection|null', $collection->unless( 'Taylor', function ($collection, $name) { @@ -377,12 +377,12 @@ function ($collection, $name) { } ) ); -assertType('Illuminate\Support\Collection|void', $collection->unless(fn () => 'Taylor', function ($collection, $name) { +assertType('Illuminate\Support\Collection|null', $collection->unless(fn () => 'Taylor', function ($collection, $name) { assertType('Illuminate\Support\Collection', $collection); assertType('string', $name); })); assertType( - 'Illuminate\Support\Collection|void', + 'Illuminate\Support\Collection|null', $collection->unless( function ($collection) { assertType('Illuminate\Support\Collection', $collection); @@ -400,7 +400,7 @@ function ($collection, $count) { ) ); -assertType('Illuminate\Support\Collection|void', $collection->unless($invokable, function ($collection, $param) { +assertType('Illuminate\Support\Collection|null', $collection->unless($invokable, function ($collection, $param) { assertType('Illuminate\Support\Collection', $collection); assertType('Invokable', $param); })); @@ -410,7 +410,7 @@ function ($collection, $count) { return true; })); -assertType('Illuminate\Support\Collection|void', $collection->unlessEmpty(function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->unlessEmpty(function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->unlessEmpty(function ($collection) { @@ -424,7 +424,7 @@ function ($collection, $count) { return true; })); -assertType('Illuminate\Support\Collection|void', $collection->unlessNotEmpty(function ($collection) { +assertType('Illuminate\Support\Collection|null', $collection->unlessNotEmpty(function ($collection) { assertType('Illuminate\Support\Collection', $collection); })); assertType('Illuminate\Support\Collection|string', $collection->unlessNotEmpty(function ($collection) { diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index 104c2c8f3b5c..c9ad444ec154 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -250,7 +250,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->when(true, function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->when(true, function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->when(true, function ($collection) { @@ -264,7 +264,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->whenEmpty(function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->whenEmpty(function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->whenEmpty(function ($collection) { @@ -278,7 +278,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->whenNotEmpty(function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->whenNotEmpty(function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->whenNotEmpty(function ($collection) { @@ -292,7 +292,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->unless(true, function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->unless(true, function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->unless(true, function ($collection) { @@ -306,7 +306,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->unlessEmpty(function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->unlessEmpty(function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->unlessEmpty(function ($collection) { @@ -320,7 +320,7 @@ return true; })); -assertType('Illuminate\Support\LazyCollection|void', $collection->unlessNotEmpty(function ($collection) { +assertType('Illuminate\Support\LazyCollection|null', $collection->unlessNotEmpty(function ($collection) { assertType('Illuminate\Support\LazyCollection', $collection); })); assertType('Illuminate\Support\LazyCollection|string', $collection->unlessNotEmpty(function ($collection) { From 265e932e81fff663164444e4c8c950af90fb79ad Mon Sep 17 00:00:00 2001 From: DeanWunder <30644242+DeanWunder@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:56:20 +1100 Subject: [PATCH 88/99] [10.x] Handle missing translations: more robust handling of callback return value (#49341) * handle-missing-translations-fix Ensure that when the user doesn't return a value from their missing translation handler, the translator returns the provided key as per normal behaviour. Avoids the translator returning null which is unexpected and could break things. * handle-missing-translations-fix Add Test for missing translation handler closure without a return statement. --------- Co-authored-by: Dean Wunder --- src/Illuminate/Translation/Translator.php | 2 +- tests/Integration/Translation/TranslatorTest.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php index 41fb2c94922b..f9f8b49cb11c 100755 --- a/src/Illuminate/Translation/Translator.php +++ b/src/Illuminate/Translation/Translator.php @@ -348,7 +348,7 @@ protected function handleMissingTranslationKey($key, $replace, $locale, $fallbac $key = call_user_func( $this->missingTranslationKeyCallback, $key, $replace, $locale, $fallback - ); + ) ?? $key; $this->handleMissingTranslationKeys = true; diff --git a/tests/Integration/Translation/TranslatorTest.php b/tests/Integration/Translation/TranslatorTest.php index 11f449dd487d..5e374e82d1cb 100644 --- a/tests/Integration/Translation/TranslatorTest.php +++ b/tests/Integration/Translation/TranslatorTest.php @@ -56,4 +56,18 @@ public function testItCanHandleMissingKeysUsingCallback() $this->app['translator']->handleMissingKeysUsing(null); } + + public function testItCanHandleMissingKeysNoReturn() + { + $this->app['translator']->handleMissingKeysUsing(function ($key) { + $_SERVER['__missing_translation_key'] = $key; + }); + + $key = $this->app['translator']->get('some missing key'); + + $this->assertSame('some missing key', $key); + $this->assertSame('some missing key', $_SERVER['__missing_translation_key']); + + $this->app['translator']->handleMissingKeysUsing(null); + } } From f286153e137b197520845daf600b26801fa2271b Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Dec 2023 10:17:05 -0600 Subject: [PATCH 89/99] formatting --- 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 a24cdef721d1..5298092b8b1b 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 = '10.35.0'; + const VERSION = '10.36.0'; /** * The base path for the Laravel installation. From 9693e3287ff4d8b40ba54e915a05530d1cdfa4ee Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Dec 2023 16:33:26 +0000 Subject: [PATCH 90/99] Update CHANGELOG --- CHANGELOG.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f5534e4b0f..6feb564990dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.35.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.37.0...10.x) + +## [v10.37.0](https://github.com/laravel/framework/compare/v10.35.0...v10.37.0) - 2023-12-12 + +* [10.x] Add `engine` method to `Blueprint` by [@jbrooksuk](https://github.com/jbrooksuk) in https://github.com/laravel/framework/pull/49250 +* [10.x] Use translator from validator in `Can` and `Enum` rules by [@fancyweb](https://github.com/fancyweb) in https://github.com/laravel/framework/pull/49251 +* [10.x] Get indexes of a table by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49204 +* [10.x] Filesystem : can lock file on append of content by [@StephaneBour](https://github.com/StephaneBour) in https://github.com/laravel/framework/pull/49262 +* [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49266 +* [10.x] Fixes generating facades documentation shouldn't be affected by `php-psr` extension by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49268 +* [10.x] Fixes `AboutCommand::format()` docblock by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49274 +* [10.x] `Route::getController()` should return `null` when the accessing closure based route by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49269 +* [10.x] Add "noActionOnUpdate" method in Illuminate/Database/Schema/ForeignKeyDefinition by [@hrsa](https://github.com/hrsa) in https://github.com/laravel/framework/pull/49297 +* [10.x] Fixing number helper for floating 0.0 by [@mr-punyapal](https://github.com/mr-punyapal) in https://github.com/laravel/framework/pull/49277 +* [10.x] Allow checking if lock succesfully restored by [@Joostb](https://github.com/Joostb) in https://github.com/laravel/framework/pull/49272 +* [10.x] Enable DynamoDB as a backend for Job Batches by [@khepin](https://github.com/khepin) in https://github.com/laravel/framework/pull/49169 +* [10.x] Removed deprecated and not used argument by [@Muetze42](https://github.com/Muetze42) in https://github.com/laravel/framework/pull/49304 +* [10.x] Add Conditionable to Batched and Chained jobs by [@bretto36](https://github.com/bretto36) in https://github.com/laravel/framework/pull/49310 +* [10.x] Include partitioned tables on PostgreSQL when retrieving tables by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49326 +* [10.x] Allow to pass `Arrayable` or `Stringble` in rules `In` and `NotIn` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/49055 +* [10.x] Display error message if json_encode() fails by [@aimeos](https://github.com/aimeos) in https://github.com/laravel/framework/pull/48856 +* [10.x] Allow error list per field by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/49309 +* [10.x] Get foreign keys of a table by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49264 +* [10.x] PHPStan Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49343 +* [10.x] Handle missing translations: more robust handling of callback return value by [@DeanWunder](https://github.com/DeanWunder) in https://github.com/laravel/framework/pull/49341 ## [v10.35.0](https://github.com/laravel/framework/compare/v10.34.2...v10.35.0) - 2023-12-05 From 2f65457e6fc4f17c751fadc71bd2ca93e593cf28 Mon Sep 17 00:00:00 2001 From: KentarouTakeda Date: Wed, 13 Dec 2023 01:35:08 +0900 Subject: [PATCH 91/99] Disconnecting the database connection after testing (#49327) * Disconnection after testing with traits for database testing * Revert "Disconnection after testing with traits for database testing" This reverts commit 71c6653640d56d65d88d5addc85731974430e6d0. * Disconnection at teardown * No need to disconnect for individual traits * fix: Behavior when run without database * Avoid changing the behavior before fixing as much as possible. * Update TestCase.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Foundation/Testing/DatabaseTransactions.php | 2 +- src/Illuminate/Foundation/Testing/RefreshDatabase.php | 2 +- src/Illuminate/Foundation/Testing/TestCase.php | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/DatabaseTransactions.php b/src/Illuminate/Foundation/Testing/DatabaseTransactions.php index 83a686f3558c..c6fb714ada4b 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTransactions.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTransactions.php @@ -33,7 +33,7 @@ public function beginDatabaseTransaction() $connection->unsetEventDispatcher(); $connection->rollBack(); $connection->setEventDispatcher($dispatcher); - $connection->disconnect(); + $database->purge($name); } }); } diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index 0f916ac55b51..c68b19bbd54d 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -109,7 +109,7 @@ public function beginDatabaseTransaction() $connection->unsetEventDispatcher(); $connection->rollBack(); $connection->setEventDispatcher($dispatcher); - $connection->disconnect(); + $database->purge($name); } }); } diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index a7315a2c5124..e38e65430a08 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -197,6 +197,12 @@ protected function tearDown(): void ParallelTesting::callTearDownTestCaseCallbacks($this); + $database = $this->app['db'] ?? null; + + foreach (array_keys($database?->getConnections() ?? []) as $name) { + $database->purge($name); + } + $this->app->flush(); $this->app = null; From 57ae89a8acbc316153315272a4af2e68370f5e36 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 12 Dec 2023 20:06:26 +0330 Subject: [PATCH 92/99] [10.x] Get user-defined types on PostgreSQL (#49303) * get user defined types * wip * fix tests * fix tests * wip * fix tests --- .../Query/Processors/PostgresProcessor.php | 48 +++++++++++++++++++ .../Database/Query/Processors/Processor.php | 11 +++++ src/Illuminate/Database/Schema/Builder.php | 10 ++++ .../Schema/Grammars/PostgresGrammar.php | 30 ++++++++++++ .../Database/Schema/PostgresBuilder.php | 37 ++++++++++---- src/Illuminate/Support/Facades/Schema.php | 1 + .../Database/SchemaBuilderTest.php | 27 +++++++++++ 7 files changed, 156 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index ddfbfe722da2..d35ee2dc2c6c 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -45,6 +45,54 @@ public function processColumnListing($results) }, $results); } + /** + * Process the results of a types query. + * + * @param array $results + * @return array + */ + public function processTypes($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'schema' => $result->schema, + 'implicit' => (bool) $result->implicit, + 'type' => match (strtolower($result->type)) { + 'b' => 'base', + 'c' => 'composite', + 'd' => 'domain', + 'e' => 'enum', + 'p' => 'pseudo', + 'r' => 'range', + 'm' => 'multirange', + default => null, + }, + 'category' => match (strtolower($result->category)) { + 'a' => 'array', + 'b' => 'boolean', + 'c' => 'composite', + 'd' => 'date_time', + 'e' => 'enum', + 'g' => 'geometric', + 'i' => 'network_address', + 'n' => 'numeric', + 'p' => 'pseudo', + 'r' => 'range', + 's' => 'string', + 't' => 'timespan', + 'u' => 'user_defined', + 'v' => 'bit_string', + 'x' => 'unknown', + 'z' => 'internal_use', + default => null, + }, + ]; + }, $results); + } + /** * Process the results of a columns query. * diff --git a/src/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 87a15c6d7a1d..97a994ebc221 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -77,6 +77,17 @@ public function processViews($results) }, $results); } + /** + * Process the results of a types query. + * + * @param array $results + * @return array + */ + public function processTypes($results) + { + return $results; + } + /** * Process the results of a columns query. * diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index efcad17fced3..c4b3da940265 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -211,6 +211,16 @@ public function getViews() ); } + /** + * Get the user-defined types that belong to the database. + * + * @return array + */ + public function getTypes() + { + throw new LogicException('This database driver does not support user-defined types.'); + } + /** * Get all of the table names for the database. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 094d49605d19..4d5fb7ba1312 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -101,6 +101,23 @@ public function compileViews() return "select viewname as name, schemaname as schema, definition from pg_views where schemaname not in ('pg_catalog', 'information_schema') order by viewname"; } + /** + * Compile the query to determine the user-defined types. + * + * @return string + */ + public function compileTypes() + { + return 'select t.typname as name, n.nspname as schema, t.typtype as type, t.typcategory as category, ' + ."((t.typinput = 'array_in'::regproc and t.typoutput = 'array_out'::regproc) or t.typtype = 'm') as implicit " + .'from pg_type t join pg_namespace n on n.oid = t.typnamespace ' + .'left join pg_class c on c.oid = t.typrelid ' + .'left join pg_type el on el.oid = t.typelem ' + .'left join pg_class ce on ce.oid = el.typrelid ' + ."where ((t.typrelid = 0 and (ce.relkind = 'c' or ce.relkind is null)) or c.relkind = 'c') " + ."and n.nspname not in ('pg_catalog', 'information_schema')"; + } + /** * Compile the SQL needed to retrieve all table names. * @@ -503,9 +520,22 @@ public function compileDropAllTypes($types) return 'drop type '.implode(',', $this->escapeNames($types)).' cascade'; } + /** + * Compile the SQL needed to drop all domains. + * + * @param array $domains + * @return string + */ + public function compileDropAllDomains($domains) + { + return 'drop domain '.implode(',', $this->escapeNames($domains)).' cascade'; + } + /** * Compile the SQL needed to retrieve all type names. * + * @deprecated Will be removed in a future Laravel version. + * * @return string */ public function compileGetAllTypes() diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 4990cb445d98..5b62187b45f0 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -53,6 +53,18 @@ public function hasTable($table) )) > 0; } + /** + * Get the user-defined types that belong to the database. + * + * @return array + */ + public function getTypes() + { + return $this->connection->getPostProcessor()->processTypes( + $this->connection->selectFromWriteConnection($this->grammar->compileTypes()) + ); + } + /** * Get all of the table names for the database. * @@ -151,6 +163,8 @@ public function dropAllViews() /** * Get all of the type names for the database. * + * @deprecated Will be removed in a future Laravel version. + * * @return array */ public function getAllTypes() @@ -168,20 +182,27 @@ public function getAllTypes() public function dropAllTypes() { $types = []; + $domains = []; - foreach ($this->getAllTypes() as $row) { - $row = (array) $row; + $schemas = $this->grammar->escapeNames($this->getSchemas()); - $types[] = reset($row); + foreach ($this->getTypes() as $type) { + if (! $type['implicit'] && in_array($this->grammar->escapeNames([$type['schema']])[0], $schemas)) { + if ($type['type'] === 'domain') { + $domains[] = $type['schema'].'.'.$type['name']; + } else { + $types[] = $type['schema'].'.'.$type['name']; + } + } } - if (empty($types)) { - return; + if (! empty($types)) { + $this->connection->statement($this->grammar->compileDropAllTypes($types)); } - $this->connection->statement( - $this->grammar->compileDropAllTypes($types) - ); + if (! empty($domains)) { + $this->connection->statement($this->grammar->compileDropAllDomains($domains)); + } } /** diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 934d27e6f508..8d4d9b0c0dc8 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -14,6 +14,7 @@ * @method static bool hasView(string $view) * @method static array getTables() * @method static array getViews() + * @method static array getTypes() * @method static bool hasColumn(string $table, string $column) * @method static bool hasColumns(string $table, array $columns) * @method static void whenTableHasColumn(string $table, string $column, \Closure $callback) diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index ea9a6d905031..5d38e0e2a8a2 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -182,6 +182,33 @@ public function testGetViews() $this->assertEmpty(array_diff(['foo', 'bar', 'baz'], array_column($views, 'name'))); } + public function testGetAndDropTypes() + { + if ($this->driver !== 'pgsql') { + $this->markTestSkipped('Test requires a PostgreSQL connection.'); + } + + DB::statement('create type pseudo_foo'); + DB::statement('create type comp_foo as (f1 int, f2 text)'); + DB::statement("create type enum_foo as enum ('new', 'open', 'closed')"); + DB::statement('create type range_foo as range (subtype = float8)'); + DB::statement('create domain domain_foo as text'); + + $types = Schema::getTypes(); + + $this->assertCount(11, $types); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'pseudo_foo' && $type['type'] === 'pseudo' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'comp_foo' && $type['type'] === 'composite' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'enum_foo' && $type['type'] === 'enum' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'range_foo' && $type['type'] === 'range' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'domain_foo' && $type['type'] === 'domain' && ! $type['implicit'])); + + Schema::dropAllTypes(); + $types = Schema::getTypes(); + + $this->assertEmpty($types); + } + public function testGetIndexes() { Schema::create('foo', function (Blueprint $table) { From b41612c58e358655cda1239e18d8851ff8736e8f Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Dec 2023 13:03:09 -0600 Subject: [PATCH 93/99] verion --- 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 5298092b8b1b..049585052154 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 = '10.36.0'; + const VERSION = '10.37.1'; /** * The base path for the Laravel installation. From 5654bdf6403bf1f22432ca1c14badadaaa4cfcb1 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Tue, 12 Dec 2023 14:36:09 -0500 Subject: [PATCH 94/99] Ability to test chained job via closure (#49337) --- .../Support/Testing/Fakes/BusFake.php | 12 ++++++++++++ tests/Support/SupportTestingBusFakeTest.php | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Illuminate/Support/Testing/Fakes/BusFake.php b/src/Illuminate/Support/Testing/Fakes/BusFake.php index 3d717eae6231..38a3f2c15708 100644 --- a/src/Illuminate/Support/Testing/Fakes/BusFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BusFake.php @@ -421,6 +421,18 @@ protected function assertDispatchedWithChainOfObjects($command, $expectedChain, ! $chain[$index]($chainedBatch->toPendingBatch())) { return false; } + } elseif ($chain[$index] instanceof Closure) { + [$expectedType, $callback] = [$this->firstClosureParameterType($chain[$index]), $chain[$index]]; + + $chainedJob = unserialize($serializedChainedJob); + + if (! $chainedJob instanceof $expectedType) { + throw new RuntimeException('The chained job was expected to be of type '.$expectedType.', '.$chainedJob::class.' chained.'); + } + + if (! $callback($chainedJob)) { + return false; + } } elseif (is_string($chain[$index])) { if ($chain[$index] != get_class(unserialize($serializedChainedJob))) { return false; diff --git a/tests/Support/SupportTestingBusFakeTest.php b/tests/Support/SupportTestingBusFakeTest.php index b1d6d8bfd952..384eb930831a 100644 --- a/tests/Support/SupportTestingBusFakeTest.php +++ b/tests/Support/SupportTestingBusFakeTest.php @@ -534,6 +534,16 @@ public function testAssertChained() ChainedJobStub::class, ]); + $this->fake->chain([ + new ChainedJobStub(123), + new ChainedJobStub(456), + ])->dispatch(); + + $this->fake->assertChained([ + fn (ChainedJobStub $job) => $job->id === 123, + fn (ChainedJobStub $job) => $job->id === 456, + ]); + Container::setInstance(null); } @@ -777,6 +787,13 @@ class BusJobStub class ChainedJobStub { use Queueable; + + public $id; + + public function __construct($id = null) + { + $this->id = $id; + } } class OtherBusJobStub From 4c1aa683ee1e003c731d6f9d1240b3b143e92382 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 12 Dec 2023 23:18:30 +0330 Subject: [PATCH 95/99] [11.x] Make floating-types consistent (#48861) * make floating-types consistent * fix tests * fix tests * fix tests --- src/Illuminate/Database/Schema/Blueprint.php | 59 +++---------------- .../Database/Schema/Grammars/MySqlGrammar.php | 10 ++-- .../Schema/Grammars/PostgresGrammar.php | 6 +- .../Schema/Grammars/SQLiteGrammar.php | 2 +- .../Schema/Grammars/SqlServerGrammar.php | 6 +- .../DatabaseMySqlSchemaGrammarTest.php | 16 +---- .../DatabasePostgresSchemaGrammarTest.php | 6 +- .../DatabaseSQLiteSchemaGrammarTest.php | 6 +- .../Database/DatabaseSchemaBlueprintTest.php | 12 ---- .../DatabaseSqlServerSchemaGrammarTest.php | 8 +-- .../Database/DatabaseSchemaBlueprintTest.php | 4 +- 11 files changed, 38 insertions(+), 97 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index eee247377c59..640cf0177c37 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -980,28 +980,23 @@ public function foreignIdFor($model, $column = null) * Create a new float column on the table. * * @param string $column - * @param int $total - * @param int $places - * @param bool $unsigned + * @param int $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function float($column, $total = 8, $places = 2, $unsigned = false) + public function float($column, $precision = 53) { - return $this->addColumn('float', $column, compact('total', 'places', 'unsigned')); + return $this->addColumn('float', $column, compact('precision')); } /** * Create a new double column on the table. * * @param string $column - * @param int|null $total - * @param int|null $places - * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function double($column, $total = 15, $places = 6, $unsigned = false) + public function double($column) { - return $this->addColumn('double', $column, compact('total', 'places', 'unsigned')); + return $this->addColumn('double', $column); } /** @@ -1010,51 +1005,11 @@ public function double($column, $total = 15, $places = 6, $unsigned = false) * @param string $column * @param int $total * @param int $places - * @param bool $unsigned - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function decimal($column, $total = 8, $places = 2, $unsigned = false) - { - return $this->addColumn('decimal', $column, compact('total', 'places', 'unsigned')); - } - - /** - * Create a new unsigned float column on the table. - * - * @param string $column - * @param int $total - * @param int $places - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function unsignedFloat($column, $total = 8, $places = 2) - { - return $this->float($column, $total, $places, true); - } - - /** - * Create a new unsigned double column on the table. - * - * @param string $column - * @param int $total - * @param int $places - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function unsignedDouble($column, $total = null, $places = null) - { - return $this->double($column, $total, $places, true); - } - - /** - * Create a new unsigned decimal column on the table. - * - * @param string $column - * @param int $total - * @param int $places * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function unsignedDecimal($column, $total = 8, $places = 2) + public function decimal($column, $total = 8, $places = 2) { - return $this->decimal($column, $total, $places, true); + return $this->addColumn('decimal', $column, compact('total', 'places')); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 43875591e07f..a65abbf34f97 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -774,7 +774,11 @@ protected function typeSmallInteger(Fluent $column) */ protected function typeFloat(Fluent $column) { - return $this->typeDouble($column); + if ($column->precision) { + return "float({$column->precision})"; + } + + return 'float'; } /** @@ -785,10 +789,6 @@ protected function typeFloat(Fluent $column) */ protected function typeDouble(Fluent $column) { - if ($column->total && $column->places) { - return "double({$column->total}, {$column->places})"; - } - return 'double'; } diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 2995e339d0d5..8ec0f576a0c7 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -809,7 +809,11 @@ protected function typeSmallInteger(Fluent $column) */ protected function typeFloat(Fluent $column) { - return $this->typeDouble($column); + if ($column->precision) { + return "float({$column->precision})"; + } + + return 'float'; } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 07edd4e2c6f2..2c8c55210531 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -698,7 +698,7 @@ protected function typeFloat(Fluent $column) */ protected function typeDouble(Fluent $column) { - return 'float'; + return 'double'; } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index b4927d5f3432..01f77a746759 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -708,6 +708,10 @@ protected function typeSmallInteger(Fluent $column) */ protected function typeFloat(Fluent $column) { + if ($column->precision) { + return "float({$column->precision})"; + } + return 'float'; } @@ -719,7 +723,7 @@ protected function typeFloat(Fluent $column) */ protected function typeDouble(Fluent $column) { - return 'float'; + return 'double precision'; } /** diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 5ffc3aa88d86..7ec3e87f9752 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -754,11 +754,11 @@ public function testAddingTinyInteger() public function testAddingFloat() { $blueprint = new Blueprint('users'); - $blueprint->float('foo', 5, 2); + $blueprint->float('foo', 5); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` double(5, 2) not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` float(5) not null', $statements[0]); } public function testAddingDouble() @@ -768,17 +768,7 @@ public function testAddingDouble() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` double(15, 6) not null', $statements[0]); - } - - public function testAddingDoubleSpecifyingPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->double('foo', 15, 8); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` double(15, 8) not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` double not null', $statements[0]); } public function testAddingDecimal() diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index 9229c6effc8f..20e48d1daf48 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -606,17 +606,17 @@ public function testAddingSmallInteger() public function testAddingFloat() { $blueprint = new Blueprint('users'); - $blueprint->float('foo', 5, 2); + $blueprint->float('foo', 5); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" double precision not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" float(5) not null', $statements[0]); } public function testAddingDouble() { $blueprint = new Blueprint('users'); - $blueprint->double('foo', 15, 8); + $blueprint->double('foo'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 60339647c0f9..b43c30a3d2bb 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -465,7 +465,7 @@ public function testAddingSmallInteger() public function testAddingFloat() { $blueprint = new Blueprint('users'); - $blueprint->float('foo', 5, 2); + $blueprint->float('foo', 5); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); @@ -475,11 +475,11 @@ public function testAddingFloat() public function testAddingDouble() { $blueprint = new Blueprint('users'); - $blueprint->double('foo', 15, 8); + $blueprint->double('foo'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" float not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" double not null', $statements[0]); } public function testAddingDecimal() diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index 884434ddc642..b1bf206988ee 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -146,18 +146,6 @@ public function testDefaultCurrentTimestamp() $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SqlServerGrammar)); } - public function testUnsignedDecimalTable() - { - $base = new Blueprint('users', function ($table) { - $table->unsignedDecimal('money', 10, 2)->useCurrent(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; - $this->assertEquals(['alter table `users` add `money` decimal(10, 2) unsigned not null'], $blueprint->toSql($connection, new MySqlGrammar)); - } - public function testRemoveColumn() { $base = new Blueprint('users', function ($table) { diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index f75d8d932966..72e0bc314508 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -510,21 +510,21 @@ public function testAddingSmallInteger() public function testAddingFloat() { $blueprint = new Blueprint('users'); - $blueprint->float('foo', 5, 2); + $blueprint->float('foo', 5); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add "foo" float not null', $statements[0]); + $this->assertSame('alter table "users" add "foo" float(5) not null', $statements[0]); } public function testAddingDouble() { $blueprint = new Blueprint('users'); - $blueprint->double('foo', 15, 2); + $blueprint->double('foo'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add "foo" float not null', $statements[0]); + $this->assertSame('alter table "users" add "foo" double precision not null', $statements[0]); } public function testAddingDecimal() diff --git a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 66a217a02f15..3dee06143062 100644 --- a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -123,7 +123,7 @@ public function testNativeColumnModifyingOnMySql() $schema->useNativeSchemaOperationsIfPossible(); $blueprint = new Blueprint('users', function ($table) { - $table->double('amount', 6, 2)->nullable()->invisible()->after('name')->change(); + $table->double('amount')->nullable()->invisible()->after('name')->change(); $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->useCurrentOnUpdate()->change(); $table->enum('difficulty', ['easy', 'hard'])->default('easy')->charset('utf8mb4')->collation('unicode')->change(); $table->multiPolygon('positions')->srid(1234)->storedAs('expression')->change(); @@ -133,7 +133,7 @@ public function testNativeColumnModifyingOnMySql() $this->assertEquals([ 'alter table `users` ' - .'modify `amount` double(6, 2) null invisible after `name`, ' + .'modify `amount` double null invisible after `name`, ' .'modify `added_at` timestamp(4) not null default CURRENT_TIMESTAMP(4) on update CURRENT_TIMESTAMP(4), ' ."modify `difficulty` enum('easy', 'hard') character set utf8mb4 collate 'unicode' not null default 'easy', " .'modify `positions` multipolygon as (expression) stored srid 1234, ' From 00c9c7fe596116c920ce52f7874f0bfa7d53bcca Mon Sep 17 00:00:00 2001 From: Orkhan Ahmadov Date: Tue, 12 Dec 2023 21:04:59 +0100 Subject: [PATCH 96/99] [10.x] Add `progress` option to `PendingBatch` (#49273) * Add `progress` option to pending batch * Invoke `progress` callback also on failure --- src/Illuminate/Bus/Batch.php | 26 ++++++++++++++++++++++++++ src/Illuminate/Bus/PendingBatch.php | 25 +++++++++++++++++++++++++ tests/Bus/BusBatchTest.php | 16 +++++++++++++++- tests/Bus/BusPendingBatchTest.php | 5 ++++- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Bus/Batch.php b/src/Illuminate/Bus/Batch.php index 644bfbce9dc6..e08b4a975ae4 100644 --- a/src/Illuminate/Bus/Batch.php +++ b/src/Illuminate/Bus/Batch.php @@ -241,6 +241,14 @@ public function recordSuccessfulJob(string $jobId) { $counts = $this->decrementPendingJobs($jobId); + if ($this->hasProgressCallbacks()) { + $batch = $this->fresh(); + + collect($this->options['progress'])->each(function ($handler) use ($batch) { + $this->invokeHandlerCallback($handler, $batch); + }); + } + if ($counts->pendingJobs === 0) { $this->repository->markAsFinished($this->id); } @@ -283,6 +291,16 @@ public function finished() return ! is_null($this->finishedAt); } + /** + * Determine if the batch has "progress" callbacks. + * + * @return bool + */ + public function hasProgressCallbacks() + { + return isset($this->options['progress']) && ! empty($this->options['progress']); + } + /** * Determine if the batch has "success" callbacks. * @@ -328,6 +346,14 @@ public function recordFailedJob(string $jobId, $e) $this->cancel(); } + if ($this->hasProgressCallbacks() && $this->allowsFailures()) { + $batch = $this->fresh(); + + collect($this->options['progress'])->each(function ($handler) use ($batch, $e) { + $this->invokeHandlerCallback($handler, $batch, $e); + }); + } + if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) { $batch = $this->fresh(); diff --git a/src/Illuminate/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php index 463cebd9cf6b..60ff3884c8b8 100644 --- a/src/Illuminate/Bus/PendingBatch.php +++ b/src/Illuminate/Bus/PendingBatch.php @@ -74,6 +74,31 @@ public function add($jobs) return $this; } + /** + * Add a callback to be executed after a job in the batch have executed successfully. + * + * @param callable $callback + * @return $this + */ + public function progress($callback) + { + $this->options['progress'][] = $callback instanceof Closure + ? new SerializableClosure($callback) + : $callback; + + return $this; + } + + /** + * Get the "progress" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function progressCallbacks() + { + return $this->options['progress'] ?? []; + } + /** * Add a callback to be executed after all jobs in the batch have executed successfully. * diff --git a/tests/Bus/BusBatchTest.php b/tests/Bus/BusBatchTest.php index 49c394611185..e6137c93843b 100644 --- a/tests/Bus/BusBatchTest.php +++ b/tests/Bus/BusBatchTest.php @@ -40,6 +40,7 @@ protected function setUp(): void $this->createSchema(); $_SERVER['__finally.count'] = 0; + $_SERVER['__progress.count'] = 0; $_SERVER['__then.count'] = 0; $_SERVER['__catch.count'] = 0; } @@ -72,7 +73,7 @@ public function createSchema() */ protected function tearDown(): void { - unset($_SERVER['__finally.batch'], $_SERVER['__then.batch'], $_SERVER['__catch.batch'], $_SERVER['__catch.exception']); + unset($_SERVER['__finally.batch'], $_SERVER['__progress.batch'], $_SERVER['__then.batch'], $_SERVER['__catch.batch'], $_SERVER['__catch.exception']); $this->schema()->drop('job_batches'); @@ -201,12 +202,14 @@ public function test_successful_jobs_can_be_recorded() $batch->recordSuccessfulJob('test-id'); $this->assertInstanceOf(Batch::class, $_SERVER['__finally.batch']); + $this->assertInstanceOf(Batch::class, $_SERVER['__progress.batch']); $this->assertInstanceOf(Batch::class, $_SERVER['__then.batch']); $batch = $batch->fresh(); $this->assertEquals(0, $batch->pendingJobs); $this->assertTrue($batch->finished()); $this->assertEquals(1, $_SERVER['__finally.count']); + $this->assertEquals(2, $_SERVER['__progress.count']); $this->assertEquals(1, $_SERVER['__then.count']); } @@ -247,6 +250,7 @@ public function test_failed_jobs_can_be_recorded_while_not_allowing_failures() $this->assertTrue($batch->finished()); $this->assertTrue($batch->cancelled()); $this->assertEquals(1, $_SERVER['__finally.count']); + $this->assertEquals(0, $_SERVER['__progress.count']); $this->assertEquals(1, $_SERVER['__catch.count']); $this->assertSame('Something went wrong.', $_SERVER['__catch.exception']->getMessage()); } @@ -288,6 +292,7 @@ public function test_failed_jobs_can_be_recorded_while_allowing_failures() $this->assertFalse($batch->finished()); $this->assertFalse($batch->cancelled()); $this->assertEquals(1, $_SERVER['__catch.count']); + $this->assertEquals(2, $_SERVER['__progress.count']); $this->assertSame('Something went wrong.', $_SERVER['__catch.exception']->getMessage()); } @@ -327,6 +332,11 @@ public function test_batch_state_can_be_inspected() $batch->finishedAt = now(); $this->assertTrue($batch->finished()); + $batch->options['progress'] = []; + $this->assertFalse($batch->hasProgressCallbacks()); + $batch->options['progress'] = [1]; + $this->assertTrue($batch->hasProgressCallbacks()); + $batch->options['then'] = []; $this->assertFalse($batch->hasThenCallbacks()); $batch->options['then'] = [1]; @@ -463,6 +473,10 @@ protected function createTestBatch($queue, $allowFailures = false) $repository = new DatabaseBatchRepository(new BatchFactory($queue), DB::connection(), 'job_batches'); $pendingBatch = (new PendingBatch(new Container, collect())) + ->progress(function (Batch $batch) { + $_SERVER['__progress.batch'] = $batch; + $_SERVER['__progress.count']++; + }) ->then(function (Batch $batch) { $_SERVER['__then.batch'] = $batch; $_SERVER['__then.count']++; diff --git a/tests/Bus/BusPendingBatchTest.php b/tests/Bus/BusPendingBatchTest.php index 471330eb8d48..7cd5f7e3a80e 100644 --- a/tests/Bus/BusPendingBatchTest.php +++ b/tests/Bus/BusPendingBatchTest.php @@ -37,7 +37,9 @@ public function test_pending_batch_may_be_configured_and_dispatched() $pendingBatch = new PendingBatch($container, new Collection([$job])); - $pendingBatch = $pendingBatch->then(function () { + $pendingBatch = $pendingBatch->progress(function () { + // + })->then(function () { // })->catch(function () { // @@ -45,6 +47,7 @@ public function test_pending_batch_may_be_configured_and_dispatched() $this->assertSame('test-connection', $pendingBatch->connection()); $this->assertSame('test-queue', $pendingBatch->queue()); + $this->assertCount(1, $pendingBatch->progressCallbacks()); $this->assertCount(1, $pendingBatch->thenCallbacks()); $this->assertCount(1, $pendingBatch->catchCallbacks()); $this->assertArrayHasKey('extra-option', $pendingBatch->options); From 9fe14c8c77c1c0367ace91ac96c129a42ee0d59a Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 13 Dec 2023 09:28:59 +0800 Subject: [PATCH 97/99] [10.x] Test Improvements (#49338) * 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 * 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 * 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 * 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 * 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 --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot --- .github/workflows/queues.yml | 160 ++++++++++++++++++ .../Testing/Concerns/InteractsWithRedis.php | 2 +- .../Queue/CallQueuedHandlerTest.php | 7 - tests/Integration/Queue/CustomPayloadTest.php | 5 +- tests/Integration/Queue/DynamoBatchTest.php | 38 +++-- .../Queue/DynamoBatchTestWithTTL.php | 10 +- tests/Integration/Queue/JobChainingTest.php | 117 ++++++++++--- .../Integration/Queue/JobDispatchingTest.php | 33 +++- .../Integration/Queue/QueueConnectionTest.php | 2 +- tests/Integration/Queue/QueueTestCase.php | 86 ++++++++++ tests/Integration/Queue/RateLimitedTest.php | 7 - .../Queue/RateLimitedWithRedisTest.php | 4 +- .../Queue/RedisQueueTest.php} | 95 ++++------- .../Queue/SkipIfBatchCancelledTest.php | 7 - .../Queue/ThrottlesExceptionsTest.php | 7 - .../ThrottlesExceptionsWithRedisTest.php | 6 +- tests/Integration/Queue/UniqueJobTest.php | 54 +++--- .../Queue/WithoutOverlappingJobsTest.php | 10 +- tests/Integration/Queue/WorkCommandTest.php | 69 ++++---- 19 files changed, 496 insertions(+), 223 deletions(-) create mode 100644 .github/workflows/queues.yml create mode 100644 tests/Integration/Queue/QueueTestCase.php rename tests/{Queue/RedisQueueIntegrationTest.php => Integration/Queue/RedisQueueTest.php} (94%) diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml new file mode 100644 index 000000000000..68e41cfa2aca --- /dev/null +++ b/.github/workflows/queues.yml @@ -0,0 +1,160 @@ +name: queues + +on: + push: + branches: + - master + - '*.x' + pull_request: + +jobs: + sync: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: true + + name: Sync Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + QUEUE_CONNECTION: sync + + database: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: true + + name: Database Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + DB_CONNECTION: testing + QUEUE_CONNECTION: database + + redis: + runs-on: ubuntu-22.04 + + services: + redis: + image: redis:7.0 + ports: + - 6379:6379 + options: --entrypoint redis-server + + strategy: + fail-fast: true + + name: Redis Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + QUEUE_CONNECTION: redis + + beanstalkd: + runs-on: ubuntu-22.04 + + name: Beanstalkd Driver + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/checkout@v3 + - name: Download & Extract beanstalkd + run: curl -L https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz + - name: Make beanstalkd + run: make + working-directory: beanstalkd-1.13 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Daemonize beanstalkd + run: ./beanstalkd-1.13/beanstalkd & + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Queue + env: + QUEUE_CONNECTION: beanstalkd diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithRedis.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithRedis.php index 7784736b7ed9..f6263d29cd93 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithRedis.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithRedis.php @@ -19,7 +19,7 @@ trait InteractsWithRedis /** * Redis manager instance. * - * @var \Illuminate\Redis\RedisManager[] + * @var array */ private $redis; diff --git a/tests/Integration/Queue/CallQueuedHandlerTest.php b/tests/Integration/Queue/CallQueuedHandlerTest.php index 97682983bc06..bd4a179322ac 100644 --- a/tests/Integration/Queue/CallQueuedHandlerTest.php +++ b/tests/Integration/Queue/CallQueuedHandlerTest.php @@ -15,13 +15,6 @@ class CallQueuedHandlerTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testJobCanBeDispatched() { CallQueuedHandlerTestJob::$handled = false; diff --git a/tests/Integration/Queue/CustomPayloadTest.php b/tests/Integration/Queue/CustomPayloadTest.php index 5ae3a8f43017..e3f7cf28b9a8 100644 --- a/tests/Integration/Queue/CustomPayloadTest.php +++ b/tests/Integration/Queue/CustomPayloadTest.php @@ -8,6 +8,7 @@ use Illuminate\Queue\Queue; use Illuminate\Support\ServiceProvider; use Orchestra\Testbench\Concerns\CreatesApplication; +use PHPUnit\Framework\Attributes\DataProvider; class CustomPayloadTest extends TestCase { @@ -25,9 +26,7 @@ public static function websites() yield ['blog.laravel.com']; } - /** - * @dataProvider websites - */ + #[DataProvider('websites')] public function test_custom_payload_gets_cleared_for_each_data_provider(string $websites) { $dispatcher = $this->app->make(QueueingDispatcher::class); diff --git a/tests/Integration/Queue/DynamoBatchTest.php b/tests/Integration/Queue/DynamoBatchTest.php index 43086a39d6aa..76a861d7468f 100644 --- a/tests/Integration/Queue/DynamoBatchTest.php +++ b/tests/Integration/Queue/DynamoBatchTest.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Env; use Illuminate\Support\Facades\Bus; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; @@ -17,34 +18,35 @@ */ class DynamoBatchTest extends TestCase { - const DYNAMODB_ENDPOINT = 'http://localhost:8888'; + public function setUp(): void + { + $this->afterApplicationCreated(function () { + BatchRunRecorder::reset(); + app(DynamoBatchRepository::class)->createAwsDynamoTable(); + }); + + $this->beforeApplicationDestroyed(function () { + app(DynamoBatchRepository::class)->deleteAwsDynamoTable(); + }); - protected function getEnvironmentSetUp($app) + parent::setUp(); + } + + protected function defineEnvironment($app) { + if (is_null($endpoint = Env::get('DYNAMODB_ENDPOINT'))) { + $this->markTestSkipped('Require `dynamodb` to be configured'); + } + $app['config']->set('queue.batching', [ 'driver' => 'dynamodb', 'region' => 'us-west-2', - 'endpoint' => static::DYNAMODB_ENDPOINT, + 'endpoint' => $endpoint, 'key' => 'key', 'secret' => 'secret', ]); } - public function setUp(): void - { - parent::setUp(); - - BatchRunRecorder::reset(); - app(DynamoBatchRepository::class)->createAwsDynamoTable(); - } - - public function tearDown(): void - { - app(DynamoBatchRepository::class)->deleteAwsDynamoTable(); - - parent::tearDown(); - } - public function test_running_a_batch() { Bus::batch([ diff --git a/tests/Integration/Queue/DynamoBatchTestWithTTL.php b/tests/Integration/Queue/DynamoBatchTestWithTTL.php index 4522b7f3b2fa..9aa516737c73 100644 --- a/tests/Integration/Queue/DynamoBatchTestWithTTL.php +++ b/tests/Integration/Queue/DynamoBatchTestWithTTL.php @@ -2,14 +2,20 @@ namespace Illuminate\Tests\Integration\Queue; +use Illuminate\Support\Env; + class DynamoBatchTestWithTTL extends DynamoBatchTest { - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { + if (is_null($endpoint = Env::get('DYNAMODB_ENDPOINT'))) { + $this->markTestSkipped('Require `dynamodb` to be configured'); + } + $app['config']->set('queue.batching', [ 'driver' => 'dynamodb', 'region' => 'us-west-2', - 'endpoint' => static::DYNAMODB_ENDPOINT, + 'endpoint' => $endpoint, 'key' => 'key', 'secret' => 'secret', 'ttl' => 1, diff --git a/tests/Integration/Queue/JobChainingTest.php b/tests/Integration/Queue/JobChainingTest.php index 3fa00618baa7..7037625b62bf 100644 --- a/tests/Integration/Queue/JobChainingTest.php +++ b/tests/Integration/Queue/JobChainingTest.php @@ -14,39 +14,38 @@ use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\Attributes\WithMigration; -use Orchestra\Testbench\TestCase; #[WithMigration('queue')] -class JobChainingTest extends TestCase +class JobChainingTest extends QueueTestCase { use DatabaseMigrations; public static $catchCallbackRan = false; - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { - $app['config']->set('queue.connections.sync1', [ - 'driver' => 'sync', - ]); + parent::defineEnvironment($app); - $app['config']->set('queue.connections.sync2', [ - 'driver' => 'sync', + $app['config']->set([ + 'queue.connections.sync1' => ['driver' => 'sync'], + 'queue.connections.sync2' => ['driver' => 'sync'], ]); } protected function setUp(): void { - parent::setUp(); + $this->afterApplicationCreated(function () { + JobRunRecorder::reset(); + }); - JobRunRecorder::reset(); - } + $this->beforeApplicationDestroyed(function () { + JobChainingTestFirstJob::$ran = false; + JobChainingTestSecondJob::$ran = false; + JobChainingTestThirdJob::$ran = false; + static::$catchCallbackRan = false; + }); - protected function tearDown(): void - { - JobChainingTestFirstJob::$ran = false; - JobChainingTestSecondJob::$ran = false; - JobChainingTestThirdJob::$ran = false; - static::$catchCallbackRan = false; + parent::setUp(); } public function testJobsCanBeChainedOnSuccess() @@ -55,6 +54,8 @@ public function testJobsCanBeChainedOnSuccess() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -65,6 +66,8 @@ public function testJobsCanBeChainedOnSuccessUsingPendingChain() new JobChainingTestSecondJob, ])->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -76,6 +79,8 @@ public function testJobsCanBeChainedOnSuccessUsingBusFacade() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -87,6 +92,8 @@ public function testJobsCanBeChainedOnSuccessUsingBusFacadeAsArguments() new JobChainingTestSecondJob ); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -97,6 +104,8 @@ public function testJobsChainedOnExplicitDelete() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestDeletingJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -108,6 +117,8 @@ public function testJobsCanBeChainedOnSuccessWithSeveralJobs() new JobChainingTestThirdJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); $this->assertTrue(JobChainingTestThirdJob::$ran); @@ -119,45 +130,55 @@ public function testJobsCanBeChainedOnSuccessUsingHelper() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } public function testJobsCanBeChainedViaQueue() { - Queue::connection('sync')->push((new JobChainingTestFirstJob)->chain([ + Queue::push((new JobChainingTestFirstJob)->chain([ new JobChainingTestSecondJob, ])); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } public function testSecondJobIsNotFiredIfFirstFailed() { - Queue::connection('sync')->push((new JobChainingTestFailingJob)->chain([ + Queue::push((new JobChainingTestFailingJob)->chain([ new JobChainingTestSecondJob, ])); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(JobChainingTestSecondJob::$ran); } public function testSecondJobIsNotFiredIfFirstReleased() { - Queue::connection('sync')->push((new JobChainingTestReleasingJob)->chain([ + Queue::push((new JobChainingTestReleasingJob)->chain([ new JobChainingTestSecondJob, ])); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(JobChainingTestSecondJob::$ran); } public function testThirdJobIsNotFiredIfSecondFails() { - Queue::connection('sync')->push((new JobChainingTestFirstJob)->chain([ + Queue::push((new JobChainingTestFirstJob)->chain([ new JobChainingTestFailingJob, new JobChainingTestThirdJob, ])); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertFalse(JobChainingTestThirdJob::$ran); } @@ -172,6 +193,8 @@ public function testCatchCallbackIsCalledOnFailure() self::$catchCallbackRan = true; })->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(static::$catchCallbackRan); $this->assertFalse(JobChainingTestSecondJob::$ran); @@ -184,6 +207,8 @@ public function testChainJobsUseSameConfig() new JobChainingTestThirdJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertSame('some_queue', JobChainingTestFirstJob::$usedQueue); $this->assertSame('sync1', JobChainingTestFirstJob::$usedConnection); @@ -201,6 +226,8 @@ public function testChainJobsUseOwnConfig() new JobChainingTestThirdJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertSame('some_queue', JobChainingTestFirstJob::$usedQueue); $this->assertSame('sync1', JobChainingTestFirstJob::$usedConnection); @@ -218,6 +245,8 @@ public function testChainJobsUseDefaultConfig() new JobChainingTestThirdJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertSame('some_queue', JobChainingTestFirstJob::$usedQueue); $this->assertSame('sync1', JobChainingTestFirstJob::$usedConnection); @@ -232,6 +261,8 @@ public function testChainJobsCanBePrepended() { JobChainAddingPrependingJob::withChain([new JobChainAddingExistingJob])->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); $this->assertNotNull(JobChainAddingExistingJob::$ranAt); $this->assertTrue(JobChainAddingAddedJob::$ranAt->isBefore(JobChainAddingExistingJob::$ranAt)); @@ -241,6 +272,8 @@ public function testChainJobsCanBePrependedWithoutExistingChain() { JobChainAddingPrependingJob::dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); } @@ -248,6 +281,8 @@ public function testChainJobsCanBeAppended() { JobChainAddingAppendingJob::withChain([new JobChainAddingExistingJob])->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); $this->assertNotNull(JobChainAddingExistingJob::$ranAt); $this->assertTrue(JobChainAddingAddedJob::$ranAt->isAfter(JobChainAddingExistingJob::$ranAt)); @@ -257,6 +292,8 @@ public function testChainJobsCanBeAppendedWithoutExistingChain() { JobChainAddingAppendingJob::dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); } @@ -274,6 +311,8 @@ public function testBatchCanBeAddedToChain() new JobChainingNamedTestJob('c3'), ])->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertEquals(['c1', 'c2', 'b1', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results); } @@ -291,7 +330,15 @@ public function testDynamicBatchCanBeAddedToChain() new JobChainingNamedTestJob('c3'), ])->dispatch(); - $this->assertEquals(['c1', 'c2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + + if ($this->getQueueDriver() === 'sync') { + $this->assertEquals( + ['c1', 'c2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results + ); + } + + $this->assertCount(11, JobRunRecorder::$results); } public function testChainBatchChain() @@ -312,7 +359,15 @@ public function testChainBatchChain() new JobChainingNamedTestJob('c3'), ])->dispatch(); - $this->assertEquals(['c1', 'c2', 'bc1', 'bc2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + + if ($this->getQueueDriver() === 'sync') { + $this->assertEquals( + ['c1', 'c2', 'bc1', 'bc2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results + ); + } + + $this->assertCount(13, JobRunRecorder::$results); } public function testChainBatchChainBatch() @@ -337,7 +392,15 @@ public function testChainBatchChainBatch() new JobChainingNamedTestJob('c3'), ])->dispatch(); - $this->assertEquals(['c1', 'c2', 'bc1', 'bc2', 'bb1', 'bb2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + + if ($this->getQueueDriver() === 'sync') { + $this->assertEquals( + ['c1', 'c2', 'bc1', 'bc2', 'bb1', 'bb2', 'b1', 'b2-0', 'b2-1', 'b2-2', 'b2-3', 'b2', 'b3', 'b4', 'c3'], JobRunRecorder::$results + ); + } + + $this->assertCount(15, JobRunRecorder::$results); } public function testBatchCatchCallbacks() @@ -351,6 +414,8 @@ public function testBatchCatchCallbacks() new JobChainingNamedTestJob('c3'), ])->catch(fn () => JobRunRecorder::recordFailure('chain failed'))->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertEquals(['c1', 'c2'], JobRunRecorder::$results); $this->assertEquals(['batch failed', 'chain failed'], JobRunRecorder::$failures); } @@ -368,6 +433,8 @@ public function testChainBatchFailureAllowed() new JobChainingNamedTestJob('c3'), ])->catch(fn () => JobRunRecorder::recordFailure('chain failed'))->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertEquals(['c1', 'c2', 'b1', 'b3', 'c3'], JobRunRecorder::$results); // Only the batch failed, but the chain should keep going since the batch allows failures $this->assertEquals(['batch failed'], JobRunRecorder::$failures); @@ -386,6 +453,8 @@ public function testChainBatchFailureNotAllowed() new JobChainingNamedTestJob('c3'), ])->catch(fn () => JobRunRecorder::recordFailure('chain failed'))->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertEquals(['c1', 'c2', 'b1', 'b3'], JobRunRecorder::$results); $this->assertEquals(['batch failed', 'chain failed'], JobRunRecorder::$failures); } diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index 4ea41da42cd1..2fe21725ebb0 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -8,20 +8,27 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; -use Orchestra\Testbench\TestCase; +use Orchestra\Testbench\Attributes\WithMigration; -class JobDispatchingTest extends TestCase +#[WithMigration('queue')] +class JobDispatchingTest extends QueueTestCase { - protected function tearDown(): void + protected function setUp(): void { - Job::$ran = false; - Job::$value = null; + $this->beforeApplicationDestroyed(function () { + Job::$ran = false; + Job::$value = null; + }); + + parent::setUp(); } public function testJobCanUseCustomMethodsAfterDispatch() { Job::dispatch('test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(Job::$ran); $this->assertSame('new-test', Job::$value); } @@ -30,11 +37,15 @@ public function testDispatchesConditionallyWithBoolean() { Job::dispatchIf(false, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(Job::$ran); $this->assertNull(Job::$value); Job::dispatchIf(true, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(Job::$ran); $this->assertSame('new-test', Job::$value); } @@ -43,10 +54,14 @@ public function testDispatchesConditionallyWithClosure() { Job::dispatchIf(fn ($job) => $job instanceof Job ? 0 : 1, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(Job::$ran); Job::dispatchIf(fn ($job) => $job instanceof Job ? 1 : 0, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(Job::$ran); } @@ -54,11 +69,15 @@ public function testDoesNotDispatchConditionallyWithBoolean() { Job::dispatchUnless(true, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(Job::$ran); $this->assertNull(Job::$value); Job::dispatchUnless(false, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(Job::$ran); $this->assertSame('new-test', Job::$value); } @@ -67,10 +86,14 @@ public function testDoesNotDispatchConditionallyWithClosure() { Job::dispatchUnless(fn ($job) => $job instanceof Job ? 1 : 0, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertFalse(Job::$ran); Job::dispatchUnless(fn ($job) => $job instanceof Job ? 0 : 1, 'test')->replaceValue('new-test'); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(Job::$ran); } diff --git a/tests/Integration/Queue/QueueConnectionTest.php b/tests/Integration/Queue/QueueConnectionTest.php index 397a9e19f6c1..1ae5f1ef9083 100644 --- a/tests/Integration/Queue/QueueConnectionTest.php +++ b/tests/Integration/Queue/QueueConnectionTest.php @@ -23,7 +23,7 @@ protected function tearDown(): void { QueueConnectionTestJob::$ran = false; - m::close(); + parent::tearDown(); } public function testJobWontGetDispatchedInsideATransaction() diff --git a/tests/Integration/Queue/QueueTestCase.php b/tests/Integration/Queue/QueueTestCase.php new file mode 100644 index 000000000000..a885c6a87802 --- /dev/null +++ b/tests/Integration/Queue/QueueTestCase.php @@ -0,0 +1,86 @@ +driver = $app['config']->get('queue.default', 'sync'); + } + + /** + * Run queue worker command. + * + * @param array $options + * @param int $times + * @return void + */ + protected function runQueueWorkerCommand(array $options = [], int $times = 1): void + { + if ($this->getQueueDriver() !== 'sync' && $times > 0) { + $count = 0; + + do { + $this->artisan('queue:work', array_merge($options, [ + '--memory' => 1024, + ]))->assertSuccessful(); + + $count++; + } while ($count < $times); + } + } + + /** + * Mark test as skipped when using given queue drivers. + * + * @param array $drivers + * @return void + */ + protected function markTestSkippedWhenUsingQueueDrivers(array $drivers): void + { + foreach ($drivers as $driver) { + if ($this->getQueueDriver() === $driver) { + $this->markTestSkipped("Unable to use `{$driver}` queue driver for the test"); + } + } + } + + /** + * Mark test as skipped when using "sync" queue driver. + * + * @return void + */ + protected function markTestSkippedWhenUsingSyncQueueDriver(): void + { + $this->markTestSkippedWhenUsingQueueDrivers(['sync']); + } + + /** + * Get the queue driver. + * + * @return string + */ + protected function getQueueDriver(): string + { + return $this->driver; + } +} diff --git a/tests/Integration/Queue/RateLimitedTest.php b/tests/Integration/Queue/RateLimitedTest.php index 80fc594fcd91..f9d90f702854 100644 --- a/tests/Integration/Queue/RateLimitedTest.php +++ b/tests/Integration/Queue/RateLimitedTest.php @@ -16,13 +16,6 @@ class RateLimitedTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testUnlimitedJobsAreExecuted() { $rateLimiter = $this->app->make(RateLimiter::class); diff --git a/tests/Integration/Queue/RateLimitedWithRedisTest.php b/tests/Integration/Queue/RateLimitedWithRedisTest.php index 768b3c2db9b0..ebd497ce6368 100644 --- a/tests/Integration/Queue/RateLimitedWithRedisTest.php +++ b/tests/Integration/Queue/RateLimitedWithRedisTest.php @@ -29,11 +29,9 @@ protected function setUp(): void protected function tearDown(): void { - parent::tearDown(); - $this->tearDownRedis(); - m::close(); + parent::tearDown(); } public function testUnlimitedJobsAreExecuted() diff --git a/tests/Queue/RedisQueueIntegrationTest.php b/tests/Integration/Queue/RedisQueueTest.php similarity index 94% rename from tests/Queue/RedisQueueIntegrationTest.php rename to tests/Integration/Queue/RedisQueueTest.php index e7d26522f1e4..74f1dffcec4a 100644 --- a/tests/Queue/RedisQueueIntegrationTest.php +++ b/tests/Integration/Queue/RedisQueueTest.php @@ -8,13 +8,14 @@ use Illuminate\Queue\Events\JobQueued; use Illuminate\Queue\Jobs\RedisJob; use Illuminate\Queue\RedisQueue; -use Illuminate\Support\Carbon; use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; use Mockery as m; -use PHPUnit\Framework\TestCase; +use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -class RedisQueueIntegrationTest extends TestCase +class RedisQueueTest extends TestCase { use InteractsWithRedis, InteractsWithTime; @@ -37,18 +38,29 @@ protected function setUp(): void protected function tearDown(): void { + $this->tearDownRedis(); + parent::tearDown(); + } - Carbon::setTestNow(null); - $this->tearDownRedis(); - m::close(); + /** + * @param string $driver + * @param string $default + * @param string|null $connection + * @param int $retryAfter + * @param int|null $blockFor + */ + private function setQueue($driver, $default = 'default', $connection = null, $retryAfter = 60, $blockFor = null) + { + $this->queue = new RedisQueue($this->redis[$driver], $default, $connection, $retryAfter, $blockFor); + $this->container = m::spy(Container::class); + $this->queue->setContainer($this->container); } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testExpiredJobsArePopped($driver) { $this->setQueue($driver); @@ -65,8 +77,6 @@ public function testExpiredJobsArePopped($driver) $this->queue->later(-300, $jobs[2]); $this->queue->later(-100, $jobs[3]); - $this->container->shouldHaveReceived('bound')->with('events')->times(4); - $this->assertEquals($jobs[2], unserialize(json_decode($this->queue->pop()->getRawBody())->data->command)); $this->assertEquals($jobs[1], unserialize(json_decode($this->queue->pop()->getRawBody())->data->command)); $this->assertEquals($jobs[3], unserialize(json_decode($this->queue->pop()->getRawBody())->data->command)); @@ -77,14 +87,12 @@ public function testExpiredJobsArePopped($driver) } /** - * @dataProvider redisDriverProvider - * - * @requires extension pcntl - * * @param mixed $driver * * @throws \Exception */ + #[RequiresPhpExtension('pcntl')] + #[DataProvider('redisDriverProvider')] public function testBlockingPop($driver) { $this->tearDownRedis(); @@ -105,10 +113,9 @@ public function testBlockingPop($driver) } // /** - // * @dataProvider redisDriverProvider - // * // * @param string $driver // */ + // #[DataProvider('redisDriverProvider')] // public function testMigrateMoreThan100Jobs($driver) // { // $this->setQueue($driver); @@ -122,10 +129,9 @@ public function testBlockingPop($driver) // } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopProperlyPopsJobOffOfRedis($driver) { $this->setQueue($driver); @@ -157,10 +163,9 @@ public function testPopProperlyPopsJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopProperlyPopsDelayedJobOffOfRedis($driver) { $this->setQueue($driver); @@ -184,10 +189,9 @@ public function testPopProperlyPopsDelayedJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPopPopsDelayedJobOffOfRedisWhenExpireNull($driver) { $this->setQueue($driver, 'default', null, null); @@ -214,10 +218,9 @@ public function testPopPopsDelayedJobOffOfRedisWhenExpireNull($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBlockingPopProperlyPopsJobOffOfRedis($driver) { $this->setQueue($driver, 'default', null, 60, 5); @@ -235,10 +238,9 @@ public function testBlockingPopProperlyPopsJobOffOfRedis($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBlockingPopProperlyPopsExpiredJobs($driver) { Str::createUuidsUsing(function () { @@ -266,10 +268,9 @@ public function testBlockingPopProperlyPopsExpiredJobs($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testNotExpireJobsWhenExpireNull($driver) { $this->setQueue($driver, 'default', null, null); @@ -315,10 +316,9 @@ public function testNotExpireJobsWhenExpireNull($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testExpireJobsWhenExpireSet($driver) { $this->setQueue($driver, 'default', null, 30); @@ -344,10 +344,9 @@ public function testExpireJobsWhenExpireSet($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testRelease($driver) { $this->setQueue($driver); @@ -385,10 +384,9 @@ public function testRelease($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testReleaseInThePast($driver) { $this->setQueue($driver); @@ -403,10 +401,9 @@ public function testReleaseInThePast($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testDelete($driver) { $this->setQueue($driver); @@ -427,10 +424,9 @@ public function testDelete($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testClear($driver) { $this->setQueue($driver); @@ -447,10 +443,9 @@ public function testClear($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testSize($driver) { $this->setQueue($driver); @@ -468,10 +463,9 @@ public function testSize($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testPushJobQueuedEvent($driver) { $events = m::mock(Dispatcher::class); @@ -493,10 +487,9 @@ public function testPushJobQueuedEvent($driver) } /** - * @dataProvider redisDriverProvider - * * @param string $driver */ + #[DataProvider('redisDriverProvider')] public function testBulkJobQueuedEvent($driver) { $events = m::mock(Dispatcher::class); @@ -515,20 +508,6 @@ public function testBulkJobQueuedEvent($driver) new RedisQueueIntegrationTestJob(15), ]); } - - /** - * @param string $driver - * @param string $default - * @param string|null $connection - * @param int $retryAfter - * @param int|null $blockFor - */ - private function setQueue($driver, $default = 'default', $connection = null, $retryAfter = 60, $blockFor = null) - { - $this->queue = new RedisQueue($this->redis[$driver], $default, $connection, $retryAfter, $blockFor); - $this->container = m::spy(Container::class); - $this->queue->setContainer($this->container); - } } class RedisQueueIntegrationTestJob diff --git a/tests/Integration/Queue/SkipIfBatchCancelledTest.php b/tests/Integration/Queue/SkipIfBatchCancelledTest.php index 91694c0fd444..454d0274903f 100644 --- a/tests/Integration/Queue/SkipIfBatchCancelledTest.php +++ b/tests/Integration/Queue/SkipIfBatchCancelledTest.php @@ -14,13 +14,6 @@ class SkipIfBatchCancelledTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testJobsAreSkippedOnceBatchIsCancelled() { [$beforeCancelled] = (new SkipCancelledBatchableTestJob())->withFakeBatch(); diff --git a/tests/Integration/Queue/ThrottlesExceptionsTest.php b/tests/Integration/Queue/ThrottlesExceptionsTest.php index 6eff31a6aabd..40abab630266 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsTest.php @@ -14,13 +14,6 @@ class ThrottlesExceptionsTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testCircuitIsOpenedForJobErrors() { $this->assertJobWasReleasedImmediately(CircuitBreakerTestJob::class); diff --git a/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php b/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php index 358d0a8b2513..e47d73fb41fd 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php @@ -30,13 +30,9 @@ protected function setUp(): void protected function tearDown(): void { - parent::tearDown(); - $this->tearDownRedis(); - Carbon::setTestNow(); - - m::close(); + parent::tearDown(); } public function testCircuitIsOpenedForJobErrors() diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index eb259a73fb9b..ebcd81c0a171 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -9,22 +9,27 @@ use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Support\Facades\Bus; use Orchestra\Testbench\Attributes\WithMigration; -use Orchestra\Testbench\TestCase; +#[WithMigration('cache')] #[WithMigration('queue')] -class UniqueJobTest extends TestCase +class UniqueJobTest extends QueueTestCase { - use DatabaseMigrations; + protected function defineEnvironment($app) + { + parent::defineEnvironment($app); + + $app['config']->set('cache.default', 'database'); + } public function testUniqueJobsAreNotDispatched() { Bus::fake(); UniqueTestJob::dispatch(); + $this->runQueueWorkerCommand(['--once' => true]); Bus::assertDispatched(UniqueTestJob::class); $this->assertFalse( @@ -33,6 +38,7 @@ public function testUniqueJobsAreNotDispatched() Bus::assertDispatchedTimes(UniqueTestJob::class); UniqueTestJob::dispatch(); + $this->runQueueWorkerCommand(['--once' => true]); Bus::assertDispatchedTimes(UniqueTestJob::class); $this->assertFalse( @@ -44,6 +50,7 @@ public function testLockIsReleasedForSuccessfulJobs() { UniqueTestJob::$handled = false; dispatch($job = new UniqueTestJob); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -56,7 +63,7 @@ public function testLockIsReleasedForFailedJobs() $this->expectException(Exception::class); try { - dispatch($job = new UniqueTestFailJob); + dispatchSync($job = new UniqueTestFailJob); } finally { $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -65,25 +72,21 @@ public function testLockIsReleasedForFailedJobs() public function testLockIsNotReleasedForJobRetries() { + $this->markTestSkippedWhenUsingSyncQueueDriver(); + UniqueTestRetryJob::$handled = false; dispatch($job = new UniqueTestRetryJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); - $this->artisan('queue:work', [ - 'connection' => 'database', - '--once' => true, - ]); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertTrue($job::$handled); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); UniqueTestRetryJob::$handled = false; - $this->artisan('queue:work', [ - 'connection' => 'database', - '--once' => true, - ]); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -91,24 +94,20 @@ public function testLockIsNotReleasedForJobRetries() public function testLockIsNotReleasedForJobReleases() { + $this->markTestSkippedWhenUsingSyncQueueDriver(); + UniqueTestReleasedJob::$handled = false; dispatch($job = new UniqueTestReleasedJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); - $this->artisan('queue:work', [ - 'connection' => 'database', - '--once' => true, - ]); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertTrue($job::$handled); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); UniqueTestReleasedJob::$handled = false; - $this->artisan('queue:work', [ - 'connection' => 'database', - '--once' => true, - ]); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertFalse($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -116,16 +115,15 @@ public function testLockIsNotReleasedForJobReleases() public function testLockCanBeReleasedBeforeProcessing() { + $this->markTestSkippedWhenUsingSyncQueueDriver(); + UniqueUntilStartTestJob::$handled = false; dispatch($job = new UniqueUntilStartTestJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); - $this->artisan('queue:work', [ - 'connection' => 'database', - '--once' => true, - ]); + $this->runQueueWorkerCommand(['--once' => true]); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -169,8 +167,6 @@ class UniqueTestReleasedJob extends UniqueTestFailJob { public $tries = 1; - public $connection = 'database'; - public function handle() { static::$handled = true; @@ -182,13 +178,9 @@ public function handle() class UniqueTestRetryJob extends UniqueTestFailJob { public $tries = 2; - - public $connection = 'database'; } class UniqueUntilStartTestJob extends UniqueTestJob implements ShouldBeUniqueUntilProcessing { public $tries = 2; - - public $connection = 'database'; } diff --git a/tests/Integration/Queue/WithoutOverlappingJobsTest.php b/tests/Integration/Queue/WithoutOverlappingJobsTest.php index 049792f750df..98eea03acce6 100644 --- a/tests/Integration/Queue/WithoutOverlappingJobsTest.php +++ b/tests/Integration/Queue/WithoutOverlappingJobsTest.php @@ -11,17 +11,9 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Mockery as m; -use Orchestra\Testbench\TestCase; -class WithoutOverlappingJobsTest extends TestCase +class WithoutOverlappingJobsTest extends QueueTestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testNonOverlappingJobsAreExecuted() { OverlappingTestJob::$handled = false; diff --git a/tests/Integration/Queue/WorkCommandTest.php b/tests/Integration/Queue/WorkCommandTest.php index 968b1c5d3548..f9681df5da08 100644 --- a/tests/Integration/Queue/WorkCommandTest.php +++ b/tests/Integration/Queue/WorkCommandTest.php @@ -7,36 +7,38 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\Attributes\WithMigration; -use Orchestra\Testbench\TestCase; -use Queue; #[WithMigration('queue')] -class WorkCommandTest extends TestCase +class WorkCommandTest extends QueueTestCase { use DatabaseMigrations; - protected function tearDown(): void + protected function setUp(): void { - parent::tearDown(); + $this->beforeApplicationDestroyed(function () { + FirstJob::$ran = false; + SecondJob::$ran = false; + ThirdJob::$ran = false; + }); - FirstJob::$ran = false; - SecondJob::$ran = false; - ThirdJob::$ran = false; + parent::setUp(); + + $this->markTestSkippedWhenUsingSyncQueueDriver(); } public function testRunningOneJob() { - Queue::connection('database')->push(new FirstJob); - Queue::connection('database')->push(new SecondJob); + Queue::push(new FirstJob); + Queue::push(new SecondJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--once' => true, '--memory' => 1024, ])->assertExitCode(0); - $this->assertSame(1, Queue::connection('database')->size()); + $this->assertSame(1, Queue::size()); $this->assertTrue(FirstJob::$ran); $this->assertFalse(SecondJob::$ran); } @@ -45,10 +47,9 @@ public function testRunTimestampOutputWithDefaultAppTimezone() { // queue.output_timezone not set at all $this->travelTo(Carbon::create(2023, 1, 18, 10, 10, 11)); - Queue::connection('database')->push(new FirstJob); + Queue::push(new FirstJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--once' => true, '--memory' => 1024, ])->expectsOutputToContain('2023-01-18 10:10:11') @@ -60,10 +61,9 @@ public function testRunTimestampOutputWithDifferentLogTimezone() $this->app['config']->set('queue.output_timezone', 'Europe/Helsinki'); $this->travelTo(Carbon::create(2023, 1, 18, 10, 10, 11)); - Queue::connection('database')->push(new FirstJob); + Queue::push(new FirstJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--once' => true, '--memory' => 1024, ])->expectsOutputToContain('2023-01-18 12:10:11') @@ -75,10 +75,9 @@ public function testRunTimestampOutputWithSameAppDefaultAndQueueLogDefault() $this->app['config']->set('queue.output_timezone', 'UTC'); $this->travelTo(Carbon::create(2023, 1, 18, 10, 10, 11)); - Queue::connection('database')->push(new FirstJob); + Queue::push(new FirstJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--once' => true, '--memory' => 1024, ])->expectsOutputToContain('2023-01-18 10:10:11') @@ -87,72 +86,72 @@ public function testRunTimestampOutputWithSameAppDefaultAndQueueLogDefault() public function testDaemon() { - Queue::connection('database')->push(new FirstJob); - Queue::connection('database')->push(new SecondJob); + Queue::push(new FirstJob); + Queue::push(new SecondJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--daemon' => true, '--stop-when-empty' => true, '--memory' => 1024, ])->assertExitCode(0); - $this->assertSame(0, Queue::connection('database')->size()); + $this->assertSame(0, Queue::size()); $this->assertTrue(FirstJob::$ran); $this->assertTrue(SecondJob::$ran); } public function testMemoryExceeded() { - Queue::connection('database')->push(new FirstJob); - Queue::connection('database')->push(new SecondJob); + Queue::push(new FirstJob); + Queue::push(new SecondJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--daemon' => true, '--stop-when-empty' => true, '--memory' => 0.1, ])->assertExitCode(12); // Memory limit isn't checked until after the first job is attempted. - $this->assertSame(1, Queue::connection('database')->size()); + $this->assertSame(1, Queue::size()); $this->assertTrue(FirstJob::$ran); $this->assertFalse(SecondJob::$ran); } public function testMaxJobsExceeded() { - Queue::connection('database')->push(new FirstJob); - Queue::connection('database')->push(new SecondJob); + $this->markTestSkippedWhenUsingQueueDrivers(['redis', 'beanstalkd']); + + Queue::push(new FirstJob); + Queue::push(new SecondJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--daemon' => true, '--stop-when-empty' => true, '--max-jobs' => 1, ]); // Memory limit isn't checked until after the first job is attempted. - $this->assertSame(1, Queue::connection('database')->size()); + $this->assertSame(1, Queue::size()); $this->assertTrue(FirstJob::$ran); $this->assertFalse(SecondJob::$ran); } public function testMaxTimeExceeded() { - Queue::connection('database')->push(new ThirdJob); - Queue::connection('database')->push(new FirstJob); - Queue::connection('database')->push(new SecondJob); + $this->markTestSkippedWhenUsingQueueDrivers(['redis', 'beanstalkd']); + + Queue::push(new ThirdJob); + Queue::push(new FirstJob); + Queue::push(new SecondJob); $this->artisan('queue:work', [ - 'connection' => 'database', '--daemon' => true, '--stop-when-empty' => true, '--max-time' => 1, ]); // Memory limit isn't checked until after the first job is attempted. - $this->assertSame(2, Queue::connection('database')->size()); + $this->assertSame(2, Queue::size()); $this->assertTrue(ThirdJob::$ran); $this->assertFalse(FirstJob::$ran); $this->assertFalse(SecondJob::$ran); From 02f3144b807b2dcc758f033223042c7d8b8f11ab Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 13 Dec 2023 02:32:58 +0000 Subject: [PATCH 98/99] Apply fixes from StyleCI --- src/Illuminate/Validation/Rules/In.php | 2 +- src/Illuminate/Validation/Rules/NotIn.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Validation/Rules/In.php b/src/Illuminate/Validation/Rules/In.php index 25639b4471f1..9d33842b8071 100644 --- a/src/Illuminate/Validation/Rules/In.php +++ b/src/Illuminate/Validation/Rules/In.php @@ -3,8 +3,8 @@ namespace Illuminate\Validation\Rules; use BackedEnum; -use Stringable; use Illuminate\Contracts\Support\Arrayable; +use Stringable; use UnitEnum; class In implements Stringable diff --git a/src/Illuminate/Validation/Rules/NotIn.php b/src/Illuminate/Validation/Rules/NotIn.php index da0ccf7e1a57..e6b392c60b1b 100644 --- a/src/Illuminate/Validation/Rules/NotIn.php +++ b/src/Illuminate/Validation/Rules/NotIn.php @@ -3,8 +3,8 @@ namespace Illuminate\Validation\Rules; use BackedEnum; -use Stringable; use Illuminate\Contracts\Support\Arrayable; +use Stringable; use UnitEnum; class NotIn implements Stringable From b4206275818747ad4a7289b2b0dd398f510ae5c9 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 13 Dec 2023 16:36:10 +0800 Subject: [PATCH 99/99] [11.x] Test Improvements (#49352) * [11.x] Test Improvements Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .github/workflows/queues.yml | 8 ++++---- tests/Integration/Queue/JobChainingTest.php | 1 + tests/Integration/Queue/JobDispatchingTest.php | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index 68e41cfa2aca..dbe5c2bcc1b4 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -25,7 +25,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr tools: composer:v2 coverage: none @@ -59,7 +59,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr tools: composer:v2 coverage: none @@ -101,7 +101,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr tools: composer:v2 coverage: none @@ -139,7 +139,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr tools: composer:v2 coverage: none diff --git a/tests/Integration/Queue/JobChainingTest.php b/tests/Integration/Queue/JobChainingTest.php index 7037625b62bf..27674d1e4709 100644 --- a/tests/Integration/Queue/JobChainingTest.php +++ b/tests/Integration/Queue/JobChainingTest.php @@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\Attributes\WithMigration; +#[WithMigration] #[WithMigration('queue')] class JobChainingTest extends QueueTestCase { diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index 6867086392a3..6b5f4928538f 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -10,6 +10,7 @@ use Illuminate\Queue\InteractsWithQueue; use Orchestra\Testbench\Attributes\WithMigration; +#[WithMigration] #[WithMigration('queue')] class JobDispatchingTest extends QueueTestCase {