diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 2bb38f0f1ab2..f4457fd85c52 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -35,7 +35,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 @@ -52,6 +52,7 @@ jobs: env: DB_CONNECTION: mysql DB_USERNAME: root + MYSQL_COLLATION: utf8mb4_unicode_ci mysql_8: runs-on: ubuntu-22.04 @@ -80,7 +81,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 @@ -125,7 +126,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 @@ -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: @@ -171,7 +172,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, :php-psr tools: composer:v2 coverage: none @@ -215,7 +216,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, :php-psr tools: composer:v2 coverage: none diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index fe4b95f2f4b3..589c600a5306 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -25,8 +25,8 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - extensions: :php-psr - php-version: 8.1 + php-version: 8.2 + extensions: :php-psr tools: composer:v2 coverage: none diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml new file mode 100644 index 000000000000..dbe5c2bcc1b4 --- /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.2 + 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.2 + 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.2 + 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.2 + 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/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d7f42b4641d8..a96597ca96f9 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -27,7 +27,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 5cda208db8ec..292e06fd86d3 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, 8.3] + php: [8.2, 8.3] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} @@ -62,20 +62,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 symfony/css-selector:^6.0 --no-interaction --no-update - - name: Set Minimum PHP 8.2 Versions uses: nick-fields/retry@v2 with: 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 @@ -108,7 +100,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3] + php: [8.2, 8.3] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows @@ -132,20 +124,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 symfony/css-selector:~6.0 --no-interaction --no-update - - name: Set Minimum PHP 8.2 Versions uses: nick-fields/retry@v2 with: 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/CHANGELOG.md b/CHANGELOG.md index f9f5534e4b0f..e69de29bb2d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1052 +0,0 @@ -# Release Notes for 10.x - -## [Unreleased](https://github.com/laravel/framework/compare/v10.35.0...10.x) - -## [v10.35.0](https://github.com/laravel/framework/compare/v10.34.2...v10.35.0) - 2023-12-05 - -* [10.x] Add `Conditionable` trait to `AssertableJson` by [@khalilst](https://github.com/khalilst) in https://github.com/laravel/framework/pull/49172 -* [10.x] Add `--with-secret` option to Artisan `down` command. by [@jj15asmr](https://github.com/jj15asmr) in https://github.com/laravel/framework/pull/49171 -* [10.x] Add support for `Number::summarize` by [@jcsoriano](https://github.com/jcsoriano) in https://github.com/laravel/framework/pull/49197 -* [10.x] Add Blade [@use](https://github.com/use) directive by [@simonhamp](https://github.com/simonhamp) in https://github.com/laravel/framework/pull/49179 -* [10.x] Fixes retrying failed jobs causes PHP memory exhaustion errors when dealing with thousands of failed jobs by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49186 -* [10.x] Add "substituteImplicitBindingsUsing" method to router by [@calebporzio](https://github.com/calebporzio) in https://github.com/laravel/framework/pull/49200 -* [10.x] Cookies Having Independent Partitioned State (CHIPS) by [@fabricecw](https://github.com/fabricecw) in https://github.com/laravel/framework/pull/48745 -* [10.x] Update InteractsWithDictionary.php to use base InvalidArgumentException by [@Grldk](https://github.com/Grldk) in https://github.com/laravel/framework/pull/49209 -* [10.x] Fix docblock for wasRecentlyCreated by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/49208 -* [10.x] Fix loss of attributes after calling child component by [@rojtjo](https://github.com/rojtjo) in https://github.com/laravel/framework/pull/49216 -* [10.x] Fix typo in PHPDoc comment by [@caendesilva](https://github.com/caendesilva) in https://github.com/laravel/framework/pull/49234 -* [10.x] Determine if the given view exists. by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49231 - -## [v10.34.2](https://github.com/laravel/framework/compare/v10.34.1...v10.34.2) - 2023-11-28 - -* [v10.x] Add missing methods to newly extended fake `Vite` instance by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/49165 - -## [v10.34.1](https://github.com/laravel/framework/compare/v10.34.0...v10.34.1) - 2023-11-28 - -* [10.x] Streamline `DatabaseMigrations` and `RefreshDatabase` events by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49153 -* [10.x] Use HtmlString in Vite fake by [@jasonvarga](https://github.com/jasonvarga) in https://github.com/laravel/framework/pull/49163 - -## [v10.34.0](https://github.com/laravel/framework/compare/v10.33.0...v10.34.0) - 2023-11-28 - -* [10.x] Fix `hex_color` validation rule by [@apih](https://github.com/apih) in https://github.com/laravel/framework/pull/49070 -* [10.x] Prevent passing null to base64_decode in Encrypter by [@robtesch](https://github.com/robtesch) in https://github.com/laravel/framework/pull/49071 -* [10.x] Alias Number class by [@ziadoz](https://github.com/ziadoz) in https://github.com/laravel/framework/pull/49073 -* [10.x] Added File Validation `extensions` by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/49082 -* [10.x] Add [@throws](https://github.com/throws) in doc-blocks by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/49091 -* [10.x] Update docblocks for consistency by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/49092 -* [10.x] Throw exception when trying to initiate `Collection` using `WeakMap` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49095 -* [10.x] Only stage committed transactions by [@hansnn](https://github.com/hansnn) in https://github.com/laravel/framework/pull/49093 -* Better transaction manager object design by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/49103 -* [10.x] use php 8.3 `mb_str_pad()` for `Str::pad*` by [@amacado](https://github.com/amacado) in https://github.com/laravel/framework/pull/49108 -* [10.x] Add Conditionable to TestResponse by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/49112 -* [10.x] Allow multiple types in Collection's `ensure` method by [@ash-jc-allen](https://github.com/ash-jc-allen) in https://github.com/laravel/framework/pull/49127 -* [10.x] Fix middleware "SetCacheHeaders" with download responses by [@clementbirkle](https://github.com/clementbirkle) in https://github.com/laravel/framework/pull/49138 -* [10.x][Cache] Fix handling of `false` values in apc by [@simivar](https://github.com/simivar) in https://github.com/laravel/framework/pull/49145 -* [10.x] Reset numeric rules after each attribute's validation by [@apih](https://github.com/apih) in https://github.com/laravel/framework/pull/49142 -* [10.x] Extract dirty getter for `performUpdate` by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/49141 -* [10.x] `ensure`: Resolve `$itemType` outside the closure by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/49137 -* Allow "missing" method to be used on route groups by [@redelschaap](https://github.com/redelschaap) in https://github.com/laravel/framework/pull/49144 -* [10.x] Get tables and views info by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49020 -* [10.x] Fix `MorphTo::associate()` PHPDoc parameter by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/49162 -* [10.x] Make test error messages more multi-byte readable by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/49160 -* [10.x] Generate a unique hash for anonymous components by [@billyonecan](https://github.com/billyonecan) in https://github.com/laravel/framework/pull/49156 -* [10.x] Improves output when using `php artisan about --json` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49154 -* [10.x] Make fake instance inherit from `Vite` when using `withoutVite()` by [@orkhanahmadov](https://github.com/orkhanahmadov) in https://github.com/laravel/framework/pull/49150 - -## [v10.33.0](https://github.com/laravel/framework/compare/v10.32.1...v10.33.0) - 2023-11-21 - -- [10.x] Fix wrong parameter passing and add these rules to dependent rules by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/49008 -- [10.x] Make Validator::getValue() public by [@shinsenter](https://github.com/shinsenter) in https://github.com/laravel/framework/pull/49007 -- [10.x] Custom messages for `Password` validation rule by [@rcknr](https://github.com/rcknr) in https://github.com/laravel/framework/pull/48928 -- [10.x] Round milliseconds in database seeder console output runtime by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/49014 -- [10.x] Add a `Number` utility class by [@caendesilva](https://github.com/caendesilva) in https://github.com/laravel/framework/pull/48845 -- [10.x] Fix the replace() method in DefaultService class by [@jonagoldman](https://github.com/jonagoldman) in https://github.com/laravel/framework/pull/49022 -- [10.x] Pass the property $validator as a parameter to the $callback Closure by [@shinsenter](https://github.com/shinsenter) in https://github.com/laravel/framework/pull/49015 -- [10.x] Fix Cache DatabaseStore::add() error occur on Postgres within transaction by [@xdevor](https://github.com/xdevor) in https://github.com/laravel/framework/pull/49025 -- [10.x] Support asserting against chained batches by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/49003 -- [10.x] Prevent DB `Cache::get()` occur race condition by [@xdevor](https://github.com/xdevor) in https://github.com/laravel/framework/pull/49031 -- [10.x] Fix notifications being counted as sent without a "shouldSend" method by [@joelwmale](https://github.com/joelwmale) in https://github.com/laravel/framework/pull/49030 -- [10.x] Fix tests failure on Windows by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49037 -- [10.x] Add unless conditional on validation rules by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/49048 -- [10.x] Handle string based payloads that are not JSON or form data when creating PSR request instances by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/49047 -- [10.x] Fix directory separator CMD display on windows by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/49045 -- [10.x] Fix mapSpread doc by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48941 -- [10.x] Tiny `Support\Collection` test fix - Unused data provider parameter by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/49053 -- [10.x] Feat: Add color_hex validation rule by [@nikopeikrishvili](https://github.com/nikopeikrishvili) in https://github.com/laravel/framework/pull/49056 -- [10.x] Handle missing translation strings using callback by [@DeanWunder](https://github.com/DeanWunder) in https://github.com/laravel/framework/pull/49040 -- [10.x] Add Str::transliterate to Stringable by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/49065 -- Add Alpha Channel support to Hex validation rule by [@ahinkle](https://github.com/ahinkle) in https://github.com/laravel/framework/pull/49069 - -## [v10.32.1](https://github.com/laravel/framework/compare/v10.32.0...v10.32.1) - 2023-11-14 - -- [10.x] Add `[@pushElseIf](https://github.com/pushElseIf)` and `[@pushElse](https://github.com/pushElse)` by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/48990 - -## [v10.32.0](https://github.com/laravel/framework/compare/v10.31.0...v10.32.0) - 2023-11-14 - -- Update PendingRequest.php by [@mattkingshott](https://github.com/mattkingshott) in https://github.com/laravel/framework/pull/48939 -- [10.x] Change array_key_exists with null coalescing assignment operator in FilesystemAdapter by [@miladev95](https://github.com/miladev95) in https://github.com/laravel/framework/pull/48943 -- [10.x] Use container to resolve email validator class by [@orkhanahmadov](https://github.com/orkhanahmadov) in https://github.com/laravel/framework/pull/48942 -- [10.x] Added `getGlobalMiddleware` method to HTTP Client Factory by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/48950 -- [10.x] Detect MySQL read-only mode error as a lost connection by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/48937 -- [10.x] Adds more implicit validation rules for `present` based on other fields by [@diamondobama](https://github.com/diamondobama) in https://github.com/laravel/framework/pull/48908 -- [10.x] Refactor set_error_handler callback to use arrow function in `InteractsWithDeprecationHandling` by [@miladev95](https://github.com/miladev95) in https://github.com/laravel/framework/pull/48954 -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48962 -- Fix issue that prevents BladeCompiler to raise an exception when temporal compiled blade template is not found. by [@juanparati](https://github.com/juanparati) in https://github.com/laravel/framework/pull/48957 -- [10.x] Fix how nested transaction callbacks are handled by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/framework/pull/48859 -- [10.x] Fixes Batch Callbacks not triggering if job timeout while in transaction by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48961 -- [10.x] expressions in migration computations fail by [@tpetry](https://github.com/tpetry) in https://github.com/laravel/framework/pull/48976 -- [10.x] Fixes Exception: Cannot traverse an already closed generator when running Arr::first with an empty generator and no callback by [@moshe-autoleadstar](https://github.com/moshe-autoleadstar) in https://github.com/laravel/framework/pull/48979 -- fixes issue with stderr when there was "]" character. by [@nikopeikrishvili](https://github.com/nikopeikrishvili) in https://github.com/laravel/framework/pull/48975 -- [10.x] Fix Postgres cache store failed to put exist cache in transaction by [@xdevor](https://github.com/xdevor) in https://github.com/laravel/framework/pull/48968 - -## [v10.31.0](https://github.com/laravel/framework/compare/v10.30.1...v10.31.0) - 2023-11-07 - -- [10.x] Allow `Sleep::until()` to be passed a timestamp as a string by [@jameshulse](https://github.com/jameshulse) in https://github.com/laravel/framework/pull/48883 -- [10.x] Fix whereHasMorph() with nullable morphs by [@MarkKremer](https://github.com/MarkKremer) in https://github.com/laravel/framework/pull/48903 -- [10.x] Handle `class_parents` returning false in `class_uses_recursive` by [@RoflCopter24](https://github.com/RoflCopter24) in https://github.com/laravel/framework/pull/48902 -- [10.x] Enable default retrieval of all fragments in `fragments()` and `fragmentsIf()` methods by [@tabuna](https://github.com/tabuna) in https://github.com/laravel/framework/pull/48894 -- [10.x] Allow placing a batch on a chain by [@khepin](https://github.com/khepin) in https://github.com/laravel/framework/pull/48633 -- [10.x] Dispatch 'connection failed' event in async http client request by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/48900 -- authenticate method refactored to use null coalescing operator by [@miladev95](https://github.com/miladev95) in https://github.com/laravel/framework/pull/48917 -- [10.x] Add support for Sec-Purpose header by [@nanos](https://github.com/nanos) in https://github.com/laravel/framework/pull/48925 -- [10.x] Allow setting retain_visibility config option on Flysystem filesystems by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/48935 -- [10.x] Escape forward slashes when exploding wildcard rules by [@matt-farrugia](https://github.com/matt-farrugia) in https://github.com/laravel/framework/pull/48936 - -## [v10.30.1](https://github.com/laravel/framework/compare/v10.30.0...v10.30.1) - 2023-11-01 - -- [10.x] Fix postgreSQL reserved word column names w/ guarded attributes broken in native column attributes implementation by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/48877 - -## [v10.30.0](https://github.com/laravel/framework/compare/v10.29.0...v10.30.0) - 2023-10-31 - -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48815 -- [10.x] Verify hash config by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48814 -- [10.x] Fix the issue of using the now function within the ArrayCache in Lumen by [@cxlblm](https://github.com/cxlblm) in https://github.com/laravel/framework/pull/48826 -- [10.x] Match service provider after resolved by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48824 -- [10.x] Fix type error registering PSR Request by [@kpicaza](https://github.com/kpicaza) in https://github.com/laravel/framework/pull/48823 -- [10.x] Ability to configure default session block timeouts by [@bytestream](https://github.com/bytestream) in https://github.com/laravel/framework/pull/48795 -- [10.x] Improvements for `artisan migrate --pretend` command 🚀 by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/48768 -- [10.x] Add support for getting native columns' attributes by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/48357 -- fix(Eloquent/Builder): calling the methods on passthru base object should be case-insensitive by [@luka-papez](https://github.com/luka-papez) in https://github.com/laravel/framework/pull/48852 -- [10.x] Fix `QueriesRelationships[@getRelationHashedColumn](https://github.com/getRelationHashedColumn)()` typehint by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/48847 -- [10.x] Remember the job on the exception by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48830 -- fix bug for always throwing exception when we pass a callable to throwUnlessStatus method [test included] by [@mhfereydouni](https://github.com/mhfereydouni) in https://github.com/laravel/framework/pull/48844 -- [10.x] Dispatch events based on a DB transaction result by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/framework/pull/48705 -- [10.x] Reset ShouldDispatchAfterCommitEventTest objects properties by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/framework/pull/48858 -- [10.x] Throw exception when trying to escape array for database connection by [@sidneyprins](https://github.com/sidneyprins) in https://github.com/laravel/framework/pull/48836 -- [10.x] Fix Stringable objects not converted to string in HTTP facade Query parameters and Body by [@LasseRafn](https://github.com/LasseRafn) in https://github.com/laravel/framework/pull/48849 - -## [v10.29.0](https://github.com/laravel/framework/compare/v10.28.0...v10.29.0) - 2023-10-24 - -- [10.x] Fixes `Str::password()` does not always generate password with numbers by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48681 -- [10.x] Fixes cache:prune-stale-tags preg_match delimiter no escaped by [@ame1973](https://github.com/ame1973) in https://github.com/laravel/framework/pull/48702 -- [10.x] Allow route:list to expand middleware groups in 'VeryVerbose' mode by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/48703 -- [10.x] Fix model:prune command error with non-class php files by [@zlodes](https://github.com/zlodes) in https://github.com/laravel/framework/pull/48708 -- [10.x] Show CliDumper source content on last line by [@CalebDW](https://github.com/CalebDW) in https://github.com/laravel/framework/pull/48707 -- [10.x] Revival of the reverted changes in 10.25.0: `firstOrCreate` `updateOrCreate` improvement through `createOrFirst` + additional query tests by [@mpyw](https://github.com/mpyw) in https://github.com/laravel/framework/pull/48637 -- [10.x] allow resolving view from closure by [@PH7-Jack](https://github.com/PH7-Jack) in https://github.com/laravel/framework/pull/48719 -- [10.x] Allow creation of PSR request with merged data by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48696 -- [10.x] Update DocBlock for `convertCase` Method to Reflect Optional $encoding Parameter by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48729 -- [10.x] Use ValidationException class from Validator Property by [@a-h-abid](https://github.com/a-h-abid) in https://github.com/laravel/framework/pull/48736 -- [10.x] Implement Test Coverage for `Str::convertCase` Method by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48730 -- [10.x] Extend Test Coverage for `Str::take` Function by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48728 -- [10.x] Add `replaceMatches` to Str class by [@hosmelq](https://github.com/hosmelq) in https://github.com/laravel/framework/pull/48727 -- [10.x] Fix duplicate conditions on retrying `SELECT` calls under `createOrFirst()` by [@KentarouTakeda](https://github.com/KentarouTakeda) in https://github.com/laravel/framework/pull/48725 -- [10.x] Uses `stefanzweifel/git-auto-commit-action[@v5](https://github.com/v5)` by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/48763 -- [10.x] fix typo in comment by [@vintagesucks](https://github.com/vintagesucks) in https://github.com/laravel/framework/pull/48770 -- [10.x] Require DBAL 3 when installing by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/48769 -- [10.x] Escape the delimiter when extracting an excerpt from text by [@standaniels](https://github.com/standaniels) in https://github.com/laravel/framework/pull/48765 -- [10.x] Fix `replaceMatches` in Str class by [@hosmelq](https://github.com/hosmelq) in https://github.com/laravel/framework/pull/48760 -- [10.x] Moves logger instance creation to a protected method by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/48759 -- [10.x] Add runningConsoleCommand(...$commands) method by [@trevorgehman](https://github.com/trevorgehman) in https://github.com/laravel/framework/pull/48751 -- [10.x] Update annotations in wrap method to accommodate Collection instances by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48746 -- [10.x] Add Tests for Str::replaceMatches Method by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48771 -- [10.x] Do not bubble exceptions thrown rendering error view when debug is false (prevent infinite loops) by [@simensen](https://github.com/simensen) in https://github.com/laravel/framework/pull/48732 -- [10.x] Correct phpdoc for Grammar::setConnection by [@Neol3108](https://github.com/Neol3108) in https://github.com/laravel/framework/pull/48779 -- [10.x] Add `displayName` for queued Artisan commands by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/48778 -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48797 -- [10.x] Make inherited relations and virtual attributes appear in model:show command by [@sebj54](https://github.com/sebj54) in https://github.com/laravel/framework/pull/48800 - -## [v10.28.0](https://github.com/laravel/framework/compare/v10.27.0...v10.28.0) - 2023-10-10 - -- [10.x] Fixed issue: Added a call to the `getValue` method by [@lozobojan](https://github.com/lozobojan) in https://github.com/laravel/framework/pull/48652 -- [10.x] Add an example for queue retry range option by [@pionl](https://github.com/pionl) in https://github.com/laravel/framework/pull/48691 -- [10.x] Add percentage to be used as High Order Messages by [@WendellAdriel](https://github.com/WendellAdriel) in https://github.com/laravel/framework/pull/48689 -- [10.x] Optimize `exists` validation for empty array input by [@mtawil](https://github.com/mtawil) in https://github.com/laravel/framework/pull/48684 - -## [v10.27.0](https://github.com/laravel/framework/compare/v10.26.2...v10.27.0) - 2023-10-09 - -- [10.x] Store blocks after prepare strings by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/48641 -- [10.x] throw TransportException instead of Exception in SES mail drivers by [@bchalier](https://github.com/bchalier) in https://github.com/laravel/framework/pull/48645 -- [10.x] Fix `Model::replicate()` when using unique keys by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/48636 -- [10.x] Don't crash if replacement cannot be represented as a string by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/48530 -- [10.x] Extended `pluck()` testcases by [@bert-w](https://github.com/bert-w) in https://github.com/laravel/framework/pull/48657 -- [10.x] Fixes `GeneratorCommand` not able to prevent uppercase reserved name such as `__CLASS__` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48667 -- [10.x] Fix timing sensitive flaky test by [@KentarouTakeda](https://github.com/KentarouTakeda) in https://github.com/laravel/framework/pull/48664 -- [10.x] Fixed implementation related to `afterCommit` on Postgres and MSSQL database drivers by [@SakiTakamachi](https://github.com/SakiTakamachi) in https://github.com/laravel/framework/pull/48662 -- [10.x] Implement chunkById in descending order by [@cristiancalara](https://github.com/cristiancalara) in https://github.com/laravel/framework/pull/48666 - -## [v10.26.2](https://github.com/laravel/framework/compare/v10.26.1...v10.26.2) - 2023-10-03 - -- Revert "Hint query builder closures (#48562)" by @taylorotwell in https://github.com/laravel/framework/pull/48620 - -## [v10.26.1](https://github.com/laravel/framework/compare/v10.26.0...v10.26.1) - 2023-10-03 - -- [10.x] Fix selection of vendor files after searching by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/48619 - -## [v10.26.0](https://github.com/laravel/framework/compare/v10.25.2...v10.26.0) - 2023-10-03 - -- [10.x] Convert Expression to string for from in having subqueries by @ikari7789 in https://github.com/laravel/framework/pull/48525 -- [10.x] Allow searching on `vendor:publish` prompt by @jessarcher in https://github.com/laravel/framework/pull/48586 -- [10.x] Enhance Test Coverage for Macroable Trait by @salehhashemi1992 in https://github.com/laravel/framework/pull/48583 -- [10.x] Add new SQL error messages by @magnusvin in https://github.com/laravel/framework/pull/48601 -- [10.x] Ensure array cache considers milliseconds by @timacdonald in https://github.com/laravel/framework/pull/48573 -- [10.x] Prevent `session:table` command from creating duplicates by @jessarcher in https://github.com/laravel/framework/pull/48602 -- [10.x] Handle expiration in seconds by @timacdonald in https://github.com/laravel/framework/pull/48600 -- [10.x] Avoid duplicate code for create table commands by extending new `Illuminate\Console\MigrationGeneratorCommand` by @crynobone in https://github.com/laravel/framework/pull/48603 -- [10.x] Add Closure Type Hinting for Query Builders by @AJenbo in https://github.com/laravel/framework/pull/48562 - -## [v10.25.2](https://github.com/laravel/framework/compare/v10.25.1...v10.25.2) - 2023-09-28 - -- [10.x] Account for new MariaDB platform by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48563 -- [10.x] Add Windows fallback for `multisearch` prompt by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/48565 -- Revert "[10.x] Fix blade failing to compile when mixing inline/block [@php](https://github.com/php) directives" by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48575 -- [10.x] Added Validation Macro Functionality Tests by [@salehhashemi1992](https://github.com/salehhashemi1992) in https://github.com/laravel/framework/pull/48570 -- Revert expiry time changes by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48576 - -## [v10.25.1](https://github.com/laravel/framework/compare/v10.25.0...v10.25.1) - 2023-09-27 - -- [10.x] Correct parameter type on MakesHttpRequests:followRedirects() by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/48557 -- [10.x] Fix `firstOrNew` on `HasManyThrough` relations by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48542 -- [10.x] Fix "after commit" callbacks not running on nested transactions using `RefreshDatabase` or `DatabaseMigrations` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48523 -- [10.x] Use the dedicated key getters in BelongsTo by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/48509 -- [10.x] Fix undefined constant `STDIN` error with `Artisan::call` during a request by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/48559 - -## [v10.25.0](https://github.com/laravel/framework/compare/v10.24.0...v10.25.0) - 2023-09-26 - -- [10.x] Fix key type in [@return](https://github.com/return) tag of EnumeratesValues::ensure() docblock by [@wimski](https://github.com/wimski) in https://github.com/laravel/framework/pull/48456 -- [10.x] Add str()->take($limit) and Str::take($string, $limit) by [@moshe-autoleadstar](https://github.com/moshe-autoleadstar) in https://github.com/laravel/framework/pull/48467 -- [10.x] Throttle exceptions by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48391 -- [10.x] Fix blade failing to compile when mixing inline/block [@php](https://github.com/php) directives by [@CalebDW](https://github.com/CalebDW) in https://github.com/laravel/framework/pull/48420 -- [10.x] Fix test name for stringable position by [@shawnlindstrom](https://github.com/shawnlindstrom) in https://github.com/laravel/framework/pull/48480 -- [10.x] Create fluent method convertCase by [@rmunate](https://github.com/rmunate) in https://github.com/laravel/framework/pull/48492 -- [10.x] Fix `CanBeOneOfMany` giving erroneous results by [@Guilhem-DELAITRE](https://github.com/Guilhem-DELAITRE) in https://github.com/laravel/framework/pull/47427 -- [10.x] Disable autoincrement for unsupported column type by [@ikari7789](https://github.com/ikari7789) in https://github.com/laravel/framework/pull/48501 -- [10.x] Increase bcrypt rounds to 12 by [@valorin](https://github.com/valorin) in https://github.com/laravel/framework/pull/48494 -- [10.x] Ensure array driver expires values at the expiry time by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48497 -- [10.x] Fix typos by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/48513 -- [10.x] Improve tests for `Arr::first` and `Arr::last` by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/48511 -- [10.x] Set morph type for MorphToMany pivot model by [@gazben](https://github.com/gazben) in https://github.com/laravel/framework/pull/48432 -- [10.x] Revert from using `createOrFirst` in other `*OrCreate` methods by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48531 -- [10.x] Fix typos in tests by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/48534 -- [10.x] Adds `updateOrCreate` on HasManyThrough relations regression test by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48533 -- [10.x] Convert exception rate limit to seconds by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48543 -- [10.x] Adds the `firstOrCreate` and `createOrFirst` methods to the `HasManyThrough` relation by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48541 -- [10.x] Handle custom extensions when caching views by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48524 -- [10.x] Set prompt interactivity mode by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/48468 - -## [v10.24.0](https://github.com/laravel/framework/compare/v10.23.1...v10.24.0) - 2023-09-19 - -- Make types of parameter of join method consistent in the Query Builder by [@melicerte](https://github.com/melicerte) in https://github.com/laravel/framework/pull/48386 -- [10.x] Fix file race condition after view:cache and artisan up by [@roxik](https://github.com/roxik) in https://github.com/laravel/framework/pull/48368 -- [10.x] Re-enable SQL Server CI by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/48393 -- Update request.stub by [@olivsinz](https://github.com/olivsinz) in https://github.com/laravel/framework/pull/48402 -- [10.x] phpdoc: Auth\Access\Response constructor allows null message by [@snmatsui](https://github.com/snmatsui) in https://github.com/laravel/framework/pull/48394 -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48390 -- Turn off autocomplete for csrf_field by [@maxheckel](https://github.com/maxheckel) in https://github.com/laravel/framework/pull/48371 -- [10.x] Remove PHP 8.1 Check for including Enums in Tests by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/48415 -- [10.x] Improve naming by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48413 -- [10.x] Fix "Text file busy" error when call deleteDirectory by [@ycs77](https://github.com/ycs77) in https://github.com/laravel/framework/pull/48422 -- Fix Cache::many() with small numeric keys by [@AlexKarpan](https://github.com/AlexKarpan) in https://github.com/laravel/framework/pull/48423 -- [10.x] Update actions/checkout from v3 to v4 by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/48439 -- `lazyById` doesn't check availability of id (alias) column in database response and silently ends up with endless loop. `chunkById` does. by [@decadence](https://github.com/decadence) in https://github.com/laravel/framework/pull/48436 -- [10.x] Allow older jobs to be faked by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48434 -- [10.x] introduce `Str::substrPos` by [@amacado](https://github.com/amacado) in https://github.com/laravel/framework/pull/48421 -- [10.x] Guess table name correctly in migrations if column's name have ('to', 'from' and/or 'in') terms by [@i350](https://github.com/i350) in https://github.com/laravel/framework/pull/48437 -- [10.x] Refactored LazyCollection::take() to save memory by [@fuwasegu](https://github.com/fuwasegu) in https://github.com/laravel/framework/pull/48382 -- [10.x] Get value attribute when default value is an enum by [@squiaios](https://github.com/squiaios) in https://github.com/laravel/framework/pull/48452 -- [10.x] Composer helper improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48448 -- [10.x] Test Symfony v6.4 by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48400 - -## [v10.23.1](https://github.com/laravel/framework/compare/v10.23.0...v10.23.1) - 2023-09-13 - -- Use PHP native json_validate in isJson function if available by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/48367 -- [10.x] Remove and update a few tearDown methods. by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/48381 -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48378 -- add "resolve" to `Component::ignoredMethods()` method by [@PH7-Jack](https://github.com/PH7-Jack) in https://github.com/laravel/framework/pull/48373 -- [10.x] Add `notModified` method to HTTP client by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/48379 -- [10.x] Update the visibility of setUp and tearDown by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/48383 -- Revert "[10.x] Validate version and variant in `Str::isUuid()`" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/48385 - -## [v10.23.0](https://github.com/laravel/framework/compare/v10.22.0...v10.23.0) - 2023-09-12 - -- [10.x] Do not add token to AWS credentials without validating it first by [@mmehmet](https://github.com/mmehmet) in https://github.com/laravel/framework/pull/48297 -- [10.x] Add array to docs of `ResponseFactory::redirectToAction` by [@NiclasvanEyk](https://github.com/NiclasvanEyk) in https://github.com/laravel/framework/pull/48309 -- [10.x] Deduplicate exceptions by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48288 -- [10.x] Change Arr::sortRecursiveDesc() method to static. by [@gkisiel](https://github.com/gkisiel) in https://github.com/laravel/framework/pull/48327 -- [10.x] Validate version and variant in `Str::isUuid()` by [@inxilpro](https://github.com/inxilpro) in https://github.com/laravel/framework/pull/48321 -- [10.x] Adds `make:view` Artisan command by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/48330 -- [10.x] Make ComponentAttributeBag JsonSerializable by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/48338 -- [10.x] add missing method to message bag class by [@PH7-Jack](https://github.com/PH7-Jack) in https://github.com/laravel/framework/pull/48348 -- [10.x] Add newResponse method to PendingRequest by [@denniseilander](https://github.com/denniseilander) in https://github.com/laravel/framework/pull/48344 -- [10.x] Add before/after database truncation methods to DatabaseTruncation trait by [@cwilby](https://github.com/cwilby) in https://github.com/laravel/framework/pull/48345 -- [10.x] Passthru test options by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/48335 -- [10.x] Support for phpredis 6.0.0 by [@stemis](https://github.com/stemis) in https://github.com/laravel/framework/pull/48362 -- [10.x] Improve test cases and achieve 100% code coverage by [@sohelrana820](https://github.com/sohelrana820) in https://github.com/laravel/framework/pull/48360 -- [10.x] Support for phpredis 6.0.0 by [@stemis](https://github.com/stemis) in https://github.com/laravel/framework/pull/48364 -- [10.x] Render mailable inline images by [@pniaps](https://github.com/pniaps) in https://github.com/laravel/framework/pull/48292 - -## [v10.22.0](https://github.com/laravel/framework/compare/v10.21.1...v10.22.0) - 2023-09-05 - -- [10.x] Add ulid testing helpers by [@Jasonej](https://github.com/Jasonej) in https://github.com/laravel/framework/pull/48276 -- [10.x] Fix issue with table prefix duplication in DatabaseTruncation trait by [@mobidev86](https://github.com/mobidev86) in https://github.com/laravel/framework/pull/48291 -- [10.x] Fixed a typo in phpdoc block by [@back2Lobby](https://github.com/back2Lobby) in https://github.com/laravel/framework/pull/48296 - -## [v10.21.1](https://github.com/laravel/framework/compare/v10.21.0...v10.21.1) - 2023-09-04 - -- [10.x] HotFix: throw captured `UniqueConstraintViolationException` if there are no matching records on `SELECT` retry by [@mpyw](https://github.com/mpyw) in https://github.com/laravel/framework/pull/48234 -- [10.x] Adds testing helpers for Precognition by [@peterfox](https://github.com/peterfox) in https://github.com/laravel/framework/pull/48151 -- [10.x] GeneratorCommand - Sorting possible models and events by [@TWithers](https://github.com/TWithers) in https://github.com/laravel/framework/pull/48249 -- [10.x] Add Enum Support to the In and NotIn Validation Rules by [@geisi](https://github.com/geisi) in https://github.com/laravel/framework/pull/48247 -- PHP 8.3 Support by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48265 -- [10.x] Call `renderForAssertions` in all Mailable assertions by [@jamsch](https://github.com/jamsch) in https://github.com/laravel/framework/pull/48254 -- [10.x] Introduce `requireEnv` helper by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/48261 -- [10.x] Combine prefix with table for `compileDropPrimary` PostgreSQL by [@dyriavin](https://github.com/dyriavin) in https://github.com/laravel/framework/pull/48268 -- [10.x] BelongsToMany Docblock Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48282 - -## [v10.21.0](https://github.com/laravel/framework/compare/v10.20.0...v10.21.0) - 2023-08-29 - -- [10.x] Add broadcastAs function at BroadcastNotificationCreated by [@raphaelcangucu](https://github.com/raphaelcangucu) in https://github.com/laravel/framework/pull/48136 -- [10.x] Fix `createOrFirst` on transactions by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48144 -- [10.x] Improve `PendingRequest::pool()` return type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/48150 -- [10.x] Adds start and end string replacement helpers by [@joedixon](https://github.com/joedixon) in https://github.com/laravel/framework/pull/48025 -- [10.x] Fix flaky test using microtime by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48156 -- [10.x] Allow failed job providers to be countable by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48177 -- [10.x] Change the return type of getPublicToken function by [@fahamjv](https://github.com/fahamjv) in https://github.com/laravel/framework/pull/48173 -- [10.x] Fix flakey `HttpClientTest` test by [@joshbonnick](https://github.com/joshbonnick) in https://github.com/laravel/framework/pull/48166 -- [10.x] Give access to job UUID in the job queued event by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48179 -- [10.x] Add `serializeAndRestore()` to `QueueFake` and`BusFake` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/48131 -- Add visibility Support for Scoped Disk Configurations by [@okaufmann](https://github.com/okaufmann) in https://github.com/laravel/framework/pull/48186 -- [10.x] Ensuring Primary Reference on Retry in `createOrFirst()` by [@mpyw](https://github.com/mpyw) in https://github.com/laravel/framework/pull/48161 -- [10.x] Make the `firstOrCreate` methods in relations use `createOrFirst` behind the scenes by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/48192 -- [10.x] Enhancing `updateOrCreate()` to Use `firstOrCreate()` by [@mpyw](https://github.com/mpyw) in https://github.com/laravel/framework/pull/48160 -- [10.x] Introduce short-hand "false" syntax for Blade component props by [@ryangjchandler](https://github.com/ryangjchandler) in https://github.com/laravel/framework/pull/48084 -- [10.x] Fix validation of attributes that depend on previous excluded attribute by [@hans-thomas](https://github.com/hans-thomas) in https://github.com/laravel/framework/pull/48122 -- [10.x] Remove unused `catch` exception variables by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/48209 -- Revert "feature: introduce short hand false syntax for component prop… by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48220 -- [10.x] Return from maintenance middleware early if URL is excluded by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/48218 -- [10.x] Array to string conversion error exception by [@hans-thomas](https://github.com/hans-thomas) in https://github.com/laravel/framework/pull/48219 -- [10.x] Migrate to `laravel/facade-documenter` repository by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48223 -- Remove unneeded Return type in Docblock of Illuminate\Database\Eloquent\Builder.php by [@FrazerFlanagan](https://github.com/FrazerFlanagan) in https://github.com/laravel/framework/pull/48228 -- [10.x] Fix issues with updated_at by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/48230 -- [10.x] Use Symfony Response in exception handler by [@thomasschiet](https://github.com/thomasschiet) in https://github.com/laravel/framework/pull/48226 -- [10.x] Allow failed jobs to be counted by "connection" and "queue" by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48216 -- [10.x] Add method `Str::convertCase` by [@rmunate](https://github.com/rmunate) in https://github.com/laravel/framework/pull/48224 -- [10.x] Make the `updateOrCreate` methods in relations use `firstOrCreate` behind the scenes by [@mpyw](https://github.com/mpyw) in https://github.com/laravel/framework/pull/48213 - -## [v10.20.0](https://github.com/laravel/framework/compare/v10.19.0...v10.20.0) - 2023-08-22 - -- [10.x] Allow default values when merging values into a resource by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/48073 -- [10.x] Adds a `createOrFirst` method to Eloquent by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/47973 -- [10.x] Allow utilising `withTrashed()`, `withoutTrashed()` and `onlyTrashed()` on `MorphTo` relationship even without `SoftDeletes` Model by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47880 -- [10.x] Mark Request JSON data to be InputBag in docblocks by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/48085 -- [10.x] Markdown Mailables: Allow omitting Footer and Header when customising components by [@jorisnoo](https://github.com/jorisnoo) in https://github.com/laravel/framework/pull/48080 -- [10.x] Update EmailVerificationRequest return docblock by [@ahmedash95](https://github.com/ahmedash95) in https://github.com/laravel/framework/pull/48087 -- [10.x] Add commonly reusable Composer related commands from 1st party packages by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48096 -- [10.x] Add ability to measure a single callable and get result by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48077 -- [10.x] Fixes incorrect method visibility and add unit tests for `Illuminate\Support\Composer` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/48104 -- [10.x] Skip convert empty string to null test by [@hungthai1401](https://github.com/hungthai1401) in https://github.com/laravel/framework/pull/48105 -- [10.x] Using complete insert for mysqldump when appending migration dump to schema file by [@emulgeator](https://github.com/emulgeator) in https://github.com/laravel/framework/pull/48126 -- [10.x] Add `hasPackage` method to Composer class by [@emargareten](https://github.com/emargareten) in https://github.com/laravel/framework/pull/48124 -- [10.x] Add `assertJsonPathCanonicalizing` method by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/48117 -- [10.x] Configurable storage path via environment variable by [@sl0wik](https://github.com/sl0wik) in https://github.com/laravel/framework/pull/48115 -- [10.x] Support providing subquery as value to `where` builder method by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/48116 -- [10.x] Minor Tweaks by [@utsavsomaiya](https://github.com/utsavsomaiya) in https://github.com/laravel/framework/pull/48138 - -## [v10.19.0](https://github.com/laravel/framework/compare/v10.18.0...v10.19.0) - 2023-08-15 - -- [10.x] Fix typo in update `HasUniqueIds` by [@iamcarlos94](https://github.com/iamcarlos94) in https://github.com/laravel/framework/pull/47994 -- [10.x] Gracefully handle scientific notation by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/48002 -- [10.x] Fix docblocks for throw_if and throw_unless by [@AbdelElrafa](https://github.com/AbdelElrafa) in https://github.com/laravel/framework/pull/48003 -- [10.x] Add `wordWrap` to `Str` by [@joshbonnick](https://github.com/joshbonnick) in https://github.com/laravel/framework/pull/48012 -- [10.x] Fix RetryBatchCommand overlapping of failed jobs when run concurrently with the same Batch ID using isolatableId by [@rybakihor](https://github.com/rybakihor) in https://github.com/laravel/framework/pull/48000 -- [10.x] Fix `assertRedirectToRoute` when route uri is empty by [@khernik93](https://github.com/khernik93) in https://github.com/laravel/framework/pull/48023 -- [10.x] Fix empty table displayed when using the --pending option but there are no pending migrations by [@TheBlckbird](https://github.com/TheBlckbird) in https://github.com/laravel/framework/pull/48019 -- [10.x] Fix forced use of write DB connection by [@oleksiikhr](https://github.com/oleksiikhr) in https://github.com/laravel/framework/pull/48015 -- [10.x] Use model cast when builder created updated at value by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47942 -- [10.x] Fix Collection::search and LazyCollection::search return type by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/48030 -- [10.x] Add ability to customize class resolution in event discovery by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/48031 -- [10.x] Add `percentage` method to Collections by [@WendellAdriel](https://github.com/WendellAdriel) in https://github.com/laravel/framework/pull/48034 -- [10.x] Fix parsing error in console when parameter description contains `--` by [@rxrw](https://github.com/rxrw) in https://github.com/laravel/framework/pull/48021 -- [10.x] Allow Listeners to dynamically specify delay using `withDelay` by [@CalebDW](https://github.com/CalebDW) in https://github.com/laravel/framework/pull/48026 -- [10.x] Add dynamic return types to rescue helper by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/48062 -- [10.x] createMany & createManyQuietly add count argument by [@JHWelch](https://github.com/JHWelch) in https://github.com/laravel/framework/pull/48048 -- [10.x] Attributes support on default component slot by [@royduin](https://github.com/royduin) in https://github.com/laravel/framework/pull/48039 -- [10.x] Add WithoutRelations attribute for model serialization by [@Neol3108](https://github.com/Neol3108) in https://github.com/laravel/framework/pull/47989 -- [10.x] Can apply WithoutRelations to entire class by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/48068 -- [10.x] createMany & createManyQuietly make argument optional by [@JHWelch](https://github.com/JHWelch) in https://github.com/laravel/framework/pull/48070 - -## [v10.18.0](https://github.com/laravel/framework/compare/v17.1...v10.18.0) - 2023-08-08 - -- [10.x] Allow DatabaseRefreshed event to include given `database` and `seed` options by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47923 -- [10.x] Use generics in `throw_if` and `throw_unless` to indicate dynamic exception type by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/47938 -- [10.x] Fixes artisan about --only should be case insensitive by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47955 -- [10.x] Improve decimal shape validation by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47954 -- docs: update phpdoc in Str helper for remove function by [@squiaios](https://github.com/squiaios) in https://github.com/laravel/framework/pull/47967 -- [10.x] Remove return on void callback by [@gonzunigad](https://github.com/gonzunigad) in https://github.com/laravel/framework/pull/47969 -- [9.x] Improve decimal shape validation by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47957 -- [10.x] Add `content` method to Vite by [@michael-rubel](https://github.com/michael-rubel) in https://github.com/laravel/framework/pull/47968 -- [10.x] Allow empty port in psql schema dump by [@Arzaroth](https://github.com/Arzaroth) in https://github.com/laravel/framework/pull/47988 -- [10.x] Show config when the value is false or zero by [@saeedhosseiinii](https://github.com/saeedhosseiinii) in https://github.com/laravel/framework/pull/47987 -- [10.x] Add getter for components on IO interaction by [@chris-ware](https://github.com/chris-ware) in https://github.com/laravel/framework/pull/47982 - -## [v10.17.1](https://github.com/laravel/framework/compare/v10.17.0...v10.17.1) - 2023-08-02 - -- [9.x] Back porting #47838 by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47840 -- [9.x] Normalise predis command argument where it maybe an object. by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47902 -- [9.x] Migrate JSON data to shared InputBag by [@ImJustToNy](https://github.com/ImJustToNy) in https://github.com/laravel/framework/pull/47919 -- [10.x] Fix docblocks of the dispatchable trait by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/47921 -- [9.x] Circumvent PHP 8.2.9 date format bug that makes artisan serve crash by [@levu42](https://github.com/levu42) in https://github.com/laravel/framework/pull/47931 -- [10.x] Fix prompt and console component spacing when calling another command by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/47928 -- [10.x] Fix prompt rendering after `callSilent` by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/47929 -- [10.x] Update ensure() collection method to correctly work with Interfaces and object inheritance by [@karpilin](https://github.com/karpilin) in https://github.com/laravel/framework/pull/47934 - -## [v10.17.0](https://github.com/laravel/framework/compare/v10.16.1...v10.17.0) - 2023-08-01 - -- [10.x] Update `TrustProxies` to rely on `$headers` if properly set by [@inxilpro](https://github.com/inxilpro) in https://github.com/laravel/framework/pull/47844 -- [10.x] Accept protocols as argument for URL validation by [@MrMicky-FR](https://github.com/MrMicky-FR) in https://github.com/laravel/framework/pull/47843 -- [10.x] Support human-friendly text for file size by [@jxxe](https://github.com/jxxe) in https://github.com/laravel/framework/pull/47846 -- [10.x] Added UploadedFile as return type by [@khrigo](https://github.com/khrigo) in https://github.com/laravel/framework/pull/47847 -- [10.x] Add option to adjust database default lock timeout by [@joelharkes](https://github.com/joelharkes) in https://github.com/laravel/framework/pull/47854 -- [10.x] PHP 8.3 builds by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/47788 -- [10.x] Add Collection::enforce() method by [@inxilpro](https://github.com/inxilpro) in https://github.com/laravel/framework/pull/47785 -- [10.x] Allow custom mutex names for isolated commands by [@rybakihor](https://github.com/rybakihor) in https://github.com/laravel/framework/pull/47814 -- Fix for issues with closure-based scheduled commands in schedule:test by [@mobidev86](https://github.com/mobidev86) in https://github.com/laravel/framework/pull/47862 -- [10.x] Extract customised deleted_at column name from Model FQN by [@edvordo](https://github.com/edvordo) in https://github.com/laravel/framework/pull/47873 -- [10.x] Adding Minutes Option in Some Frequencies by [@joaopalopes24](https://github.com/joaopalopes24) in https://github.com/laravel/framework/pull/47789 -- [10.x] Add `config:show` command by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/framework/pull/47858 -- [10.x] Test Improvements for `hashed` password by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47904 -- [10.x] Use shared facade script by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47901 -- [10.x] Add --test and --pest options to make:component by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/47894 -- [10.x] Prompts by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/46772 -- [10.x] Migrate JSON data to shared InputBag by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47914 -- [10.x] Fix `Factory::configure()` return type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/47920 -- [10.x] Fix Http global middleware for queue, octane, and dependency injection by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47915 - -## [v10.16.1](https://github.com/laravel/framework/compare/v10.17.1...v10.16.1) - 2023-07-26 - -- [10.x] Fix BusFake::assertChained() for a single job by [@gehrisandro](https://github.com/gehrisandro) in https://github.com/laravel/framework/pull/47832 -- [10.x] Retain `$request->request` `InputBag` type by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/47838 - -## [v10.16.0](https://github.com/laravel/framework/compare/v10.15.0...v10.16.0) - 2023-07-25 - -- [10.x] Improve display of sub-minute tasks in `schedule:list` command. by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/47720 -- [10.x] Add new SQL error message "No connection could be made because the target machine actively refused it" by [@magnusvin](https://github.com/magnusvin) in https://github.com/laravel/framework/pull/47718 -- [10.x] Ignore second in HttpRequestTest date comparison by [@kylekatarnls](https://github.com/kylekatarnls) in https://github.com/laravel/framework/pull/47719 -- [10.x] Call `renderForAssertions` in `assertHasSubject` by [@ttrig](https://github.com/ttrig) in https://github.com/laravel/framework/pull/47728 -- [10.x] We dont want Symfony to catch pcntl signal by [@ChristopheBorcard](https://github.com/ChristopheBorcard) in https://github.com/laravel/framework/pull/47725 -- [10.x] Use atomic locks for command mutex by [@Gaitholabi](https://github.com/Gaitholabi) in https://github.com/laravel/framework/pull/47624 -- [10.x] Improve typehint for Model::getConnectionResolver() by [@LukeTowers](https://github.com/LukeTowers) in https://github.com/laravel/framework/pull/47749 -- [10.x] add getRedisConnection to ThrottleRequestsWithRedis by [@snmatsui](https://github.com/snmatsui) in https://github.com/laravel/framework/pull/47742 -- [10.x] Adjusts for Volt by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/47757 -- [10.x] Fix sql server paging problems by [@joelharkes](https://github.com/joelharkes) in https://github.com/laravel/framework/pull/47763 -- [10.x] Typo type of data by [@hungthai1401](https://github.com/hungthai1401) in https://github.com/laravel/framework/pull/47775 -- [10.x] Add missing tests for the `schedule:list` command. by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/framework/pull/47787 -- [10.x] Fix `Str::replace` return type by [@datlechin](https://github.com/datlechin) in https://github.com/laravel/framework/pull/47779 -- [10.x] Collection::except() with null returns all by [@pniaps](https://github.com/pniaps) in https://github.com/laravel/framework/pull/47821 -- [10.x] fix issue #47727 with wrong return type by [@renky](https://github.com/renky) in https://github.com/laravel/framework/pull/47820 -- [10.x] Remove unused variable in `VendorPublishCommand` by [@hungthai1401](https://github.com/hungthai1401) in https://github.com/laravel/framework/pull/47817 -- [10.x] Remove unused variable in `MigrateCommand` by [@sangnguyenplus](https://github.com/sangnguyenplus) in https://github.com/laravel/framework/pull/47816 -- [10.x] Revert 47763 fix sql server by [@dunhamjared](https://github.com/dunhamjared) in https://github.com/laravel/framework/pull/47792 -- [10.x] Add test for Message ID, References and Custom Headers for Mailables by [@alexbowers](https://github.com/alexbowers) in https://github.com/laravel/framework/pull/47791 -- [10.x] Add support for `BackedEnum` in Collection `groupBy` method by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/47823 -- [10.x] Support inline disk for scoped driver by [@alexbowers](https://github.com/alexbowers) in https://github.com/laravel/framework/pull/47776 -- [10.x] Allowing bind of IPv6 addresses in development server by [@MuriloChianfa](https://github.com/MuriloChianfa) in https://github.com/laravel/framework/pull/47804 -- [10.x] Add more info to issue template by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/47828 - -## [v10.15.0](https://github.com/laravel/framework/compare/v10.14.1...v10.15.0) - 2023-07-11 - -- [10.x] Change return type of `getPrivateToken` in AblyBroadcaster by [@milwad](https://github.com/milwad)-dev in https://github.com/laravel/framework/pull/47602 -- [10.x] Add toRawSql, dumpRawSql() and ddRawSql() to Query Builders by [@tpetry](https://github.com/tpetry) in https://github.com/laravel/framework/pull/47507 -- [10.x] Fix recorderHandler not recording changes made by middleware by [@j3j5](https://github.com/j3j5) in https://github.com/laravel/framework/pull/47614 -- Pass queue from Mailable to SendQueuedMailable job by [@Tarpsvo](https://github.com/Tarpsvo) in https://github.com/laravel/framework/pull/47612 -- [10.x] Sub-minute Scheduling by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/47279 -- [10.x] Fixes failing tests running on DynamoDB Local 2.0.0 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47653 -- [10.x] Allow password reset callback to modify the result by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/47641 -- Forget with collections by [@joelbutcher](https://github.com/joelbutcher) in https://github.com/laravel/framework/pull/47637 -- [10.x] Do not apply global scopes when incrementing/decrementing an existing model by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/47629 -- [10.x] Adds inline attachments support for "notifications" markdown mailables by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/47643 -- Assertions for counting outgoing mailables by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/47655 -- [10.x] Add getRawQueryLog() method by [@fuwasegu](https://github.com/fuwasegu) in https://github.com/laravel/framework/pull/47623 -- [10.x] Fix Storage::cloud() return type by [@tattali](https://github.com/tattali) in https://github.com/laravel/framework/pull/47664 -- [10.x] Add `isUrl` to the `Str` class and use it from the validator by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/47688 -- [10.x] Remove unwanted call to include stack traces by [@HazzazBinFaiz](https://github.com/HazzazBinFaiz) in https://github.com/laravel/framework/pull/47687 -- [10.x] Make Vite throw a new `ManifestNotFoundException` by [@innocenzi](https://github.com/innocenzi) in https://github.com/laravel/framework/pull/47681 -- [10.x] Move class from file logic in Console Kernel to dedicated method by [@CalebDW](https://github.com/CalebDW) in https://github.com/laravel/framework/pull/47665 -- [10.x] Dispatch model pruning started and ended events by [@ziadoz](https://github.com/ziadoz) in https://github.com/laravel/framework/pull/47669 -- [10.x] Update DatabaseRule to handle Enums for simple where clause by [@CalebDW](https://github.com/CalebDW) in https://github.com/laravel/framework/pull/47679 -- [10.x] Add data_remove helper by [@PhiloNL](https://github.com/PhiloNL) in https://github.com/laravel/framework/pull/47618 -- [10.x] Added tests for `isUrl` to Str. by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/47690 -- [10.x] Added `isUrl` to Stringable. by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/47689 -- [10.x] Tweak return type for missing config by [@sfreytag](https://github.com/sfreytag) in https://github.com/laravel/framework/pull/47702 -- [10.x] Fix parallel testing without any database connection by [@deleugpn](https://github.com/deleugpn) in https://github.com/laravel/framework/pull/47705 -- [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/47709 -- [10.x] Allows HTTP exceptions to be thrown for views by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/47714 - -## [v10.14.1](https://github.com/laravel/framework/compare/v10.14.0...v10.14.1) - 2023-06-28 - -- [10.x] Fix `Dispatcher::until` return type by @Neol3108 in https://github.com/laravel/framework/pull/47585 -- [10.x] Add Collection::wrap to add method on BatchFake by @schonhoff in https://github.com/laravel/framework/pull/47589 -- [10.x] Fixes grammar in FoundationServiceProvider by @adampatterson in https://github.com/laravel/framework/pull/47593 -- [10.x] Ensure duration is present by @timacdonald in https://github.com/laravel/framework/pull/47596 - -## [v10.14.0](https://github.com/laravel/framework/compare/v10.13.5...v10.14.0) - 2023-06-27 - -- [10.x] Add test for `withCookies` method in RedirectResponse by @milwad-dev in https://github.com/laravel/framework/pull/47383 -- [10.x] Add new error message "SSL: Handshake timed out" handling to PDO Dete… by @yehorherasymchuk in https://github.com/laravel/framework/pull/47392 -- [10.x] Add new error messages for detecting lost connections by @mfn in https://github.com/laravel/framework/pull/47398 -- [10.x] Update phpdoc `except` method in Middleware by @milwad-dev in https://github.com/laravel/framework/pull/47408 -- [10.x] Fix inconsistent type hint for `$passwordTimeoutSeconds` by @devfrey in https://github.com/laravel/framework/pull/47414 -- Change visibility of `path` method in FileStore.php by @foremtehan in https://github.com/laravel/framework/pull/47413 -- [10.x] Fix return type of `buildException` method by @milwad-dev in https://github.com/laravel/framework/pull/47422 -- [10.x] Allow serialization of NotificationSent by @cosmastech in https://github.com/laravel/framework/pull/47375 -- [10.x] Incorrect comment in `PredisConnector` and `PhpRedisConnector` by @hungthai1401 in https://github.com/laravel/framework/pull/47438 -- [10.x] Can set custom Response for denial within `Gate@inspect()` by @cosmastech in https://github.com/laravel/framework/pull/47436 -- [10.x] Remove unnecessary param in `addSingletonUpdate` by @milwad-dev in https://github.com/laravel/framework/pull/47446 -- [10.x] Fix return type of `prefixedResource` & `prefixedResource` by @milwad-dev in https://github.com/laravel/framework/pull/47445 -- [10.x] Add Factory::getNamespace() by @tylernathanreed in https://github.com/laravel/framework/pull/47463 -- [10.x] Add `whenAggregated` method to `ConditionallyLoadsAttributes` trait by @akr4m in https://github.com/laravel/framework/pull/47417 -- [10.x] Add PendingRequest `withHeader()` method by @ralphjsmit in https://github.com/laravel/framework/pull/47474 -- [10.x] Fix $exceptTables to allow an array of table names by @cwilby in https://github.com/laravel/framework/pull/47477 -- [10.x] Fix `eachById` on `HasManyThrough` relation by @cristiancalara in https://github.com/laravel/framework/pull/47479 -- [10.x] Allow object caching to be disabled for custom class casters by @CalebDW in https://github.com/laravel/framework/pull/47423 -- [10.x] "Can" validation rule by @stevebauman in https://github.com/laravel/framework/pull/47371 -- [10.x] refactor(Parser.php): Removing the extra "else" statement by @saMahmoudzadeh in https://github.com/laravel/framework/pull/47483 -- [10.x] Add `UncompromisedVerifier::class` to `provides()` in `ValidationServiceProvider` by @xurshudyan in https://github.com/laravel/framework/pull/47500 -- [9.x] Fix SES V2 Transport "reply to" addresses by @jacobmllr95 in https://github.com/laravel/framework/pull/47522 -- [10.x] Reindex appends attributes by @hungthai1401 in https://github.com/laravel/framework/pull/47519 -- [10.x] Fix `ListenerMakeCommand` deprecations by @dammy001 in https://github.com/laravel/framework/pull/47517 -- [10.x] Add `HandlesPotentiallyTranslatedString` trait by @xurshudyan in https://github.com/laravel/framework/pull/47488 -- [10.x] update [JsonResponse]: using match expression instead of if-elseif-else by @saMahmoudzadeh in https://github.com/laravel/framework/pull/47524 -- [10.x] Add `withQueryParameters` to the HTTP client by @mnapoli in https://github.com/laravel/framework/pull/47297 -- [10.x] Allow `%` symbol in component attribute names by @JayBizzle in https://github.com/laravel/framework/pull/47533 -- [10.x] Fix Http client pool return type by @srdante in https://github.com/laravel/framework/pull/47530 -- [10.x] Use `match` expression in `resolveSynchronousFake` by @osbre in https://github.com/laravel/framework/pull/47540 -- [10.x] Use `match` expression in `compileHaving` by @osbre in https://github.com/laravel/framework/pull/47548 -- [10.x] Use `match` expression in `getArrayableItems` by @osbre in https://github.com/laravel/framework/pull/47549 -- [10.x] Fix return type in `SessionGuard` by @PerryvanderMeer in https://github.com/laravel/framework/pull/47553 -- [10.x] Fix return type in `DatabaseQueue` by @PerryvanderMeer in https://github.com/laravel/framework/pull/47552 -- [10.x] Fix return type in `DumpCommand` by @PerryvanderMeer in https://github.com/laravel/framework/pull/47556 -- [10.x] Fix return type in `MigrateMakeCommand` by @PerryvanderMeer in https://github.com/laravel/framework/pull/47557 -- [10.x] Add missing return to `Factory` by @PerryvanderMeer in https://github.com/laravel/framework/pull/47559 -- [10.x] Update doc in Eloquent model by @alirezasalehizadeh in https://github.com/laravel/framework/pull/47562 -- [10.x] Fix return types by @PerryvanderMeer in https://github.com/laravel/framework/pull/47561 -- [10.x] Fix PHPDoc throw type by @fernandokbs in https://github.com/laravel/framework/pull/47566 -- [10.x] Add hasAny function to ComponentAttributeBag, Allow multiple keys in has function by @indykoning in https://github.com/laravel/framework/pull/47569 -- [10.x] Ensure captured time is in configured timezone by @timacdonald in https://github.com/laravel/framework/pull/47567 -- [10.x] Add Method to Report only logged exceptions by @joelharkes in https://github.com/laravel/framework/pull/47554 -- [10.x] Add global middleware to `Http` client by @timacdonald in https://github.com/laravel/framework/pull/47525 -- [9.x] Fixes unable to use `trans()->has()` on JSON language files. by @crynobone in https://github.com/laravel/framework/pull/47582 - -## [v10.13.5](https://github.com/laravel/framework/compare/v10.13.3...v10.13.5) - 2023-06-08 - -- Revert "[10.x] Update Kernel::load() to use same `classFromFile` logic as events" by @taylorotwell in https://github.com/laravel/framework/pull/47382 - -## [v10.13.3](https://github.com/laravel/framework/compare/v10.13.2...v10.13.3) - 2023-06-08 - -### What's Changed - -- Narrow down array type for `$attributes` in `CastsAttributes` by @devfrey in https://github.com/laravel/framework/pull/47365 -- Add test for `assertViewHasAll` method by @milwad-dev in https://github.com/laravel/framework/pull/47366 -- Fix `schedule:list` to display named Jobs by @liamkeily in https://github.com/laravel/framework/pull/47367 -- Support `ConditionalRules` within `NestedRules` by @cosmastech in https://github.com/laravel/framework/pull/47344 -- Small test fixes by @stevebauman in https://github.com/laravel/framework/pull/47369 -- Pluralisation typo in queue:clear command output by @sebsobseb in https://github.com/laravel/framework/pull/47376 -- Add getForeignKeyFrom method by @iamgergo in https://github.com/laravel/framework/pull/47378 -- Add shouldHashKeys to ThrottleRequests middleware by @fosron in https://github.com/laravel/framework/pull/47368 - -## [v10.13.2 (2023-06-05)](https://github.com/laravel/framework/compare/v10.13.1...v10.13.2) - -### Added - -- Added `Illuminate/Http/Client/PendingRequest::replaceHeaders()` ([#47335](https://github.com/laravel/framework/pull/47335)) -- Added `Illuminate/Notifications/Messages/MailMessage::attachMany()` ([#47345](https://github.com/laravel/framework/pull/47345)) - -### Reverted - -- Revert "[10.x] Remove session on authenticatable deletion v2" ([#47354](https://github.com/laravel/framework/pull/47354)) - -### Fixed - -- Fixes usage of Redis::many() with empty array ([#47307](https://github.com/laravel/framework/pull/47307)) -- Fix mapped renderable exception handling ([#47347](https://github.com/laravel/framework/pull/47347)) -- Avoid duplicates in fillable/guarded on merge in Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php ([#47351](https://github.com/laravel/framework/pull/47351)) - -### Changed - -- Update Kernel::load() to use same classFromFile logic as events ([#47327](https://github.com/laravel/framework/pull/47327)) -- Remove redundant 'setAccessible' methods ([#47348](https://github.com/laravel/framework/pull/47348)) - -## [v10.13.1 (2023-06-02)](https://github.com/laravel/framework/compare/v10.13.0...v10.13.1) - -### Added - -- Added `Illuminate\Contracts\Database\Query\ConditionExpression` interface and functional for this ([#47210](https://github.com/laravel/framework/pull/47210)) -- Added return type for `Illuminate/Notifications/Channels/MailChannel::send()` ([#47310](https://github.com/laravel/framework/pull/47310)) - -### Reverted - -- Revert "[10.x] Fix inconsistency between report and render methods" ([#47326](https://github.com/laravel/framework/pull/47326)) - -### Changed - -- Display queue runtime in human readable format ([#47227](https://github.com/laravel/framework/pull/47227)) - -## [v10.13.0 (2023-05-30)](https://github.com/laravel/framework/compare/v10.12.0...v10.13.0) - -### Added - -- Added `Illuminate/Hashing/HashManager::isHashed()` ([#47197](https://github.com/laravel/framework/pull/47197)) -- Escaping functionality within the Grammar ([#46558](https://github.com/laravel/framework/pull/46558)) -- Provide testing hooks in `Illuminate/Support/Sleep.php` ([#47228](https://github.com/laravel/framework/pull/47228)) -- Added missing methods to AssertsStatusCodes ([#47277](https://github.com/laravel/framework/pull/47277)) -- Wrap response preparation in events ([#47229](https://github.com/laravel/framework/pull/47229)) - -### Fixed - -- Fixed bug when function wrapped around definition of related factory ([#47168](https://github.com/laravel/framework/pull/47168)) -- Fixed inconsistency between report and render methods ([#47201](https://github.com/laravel/framework/pull/47201)) -- Fixes Model::isDirty() when AsCollection or AsEncryptedCollection have arguments ([#47235](https://github.com/laravel/framework/pull/47235)) -- Fixed escaped String for JSON_CONTAINS ([#47244](https://github.com/laravel/framework/pull/47244)) -- Fixes missing output on ProcessFailedException exception ([#47285](https://github.com/laravel/framework/pull/47285)) - -### Changed - -- Remove useless else statements ([#47186](https://github.com/laravel/framework/pull/47186)) -- RedisStore improvement - don't open transaction unless all values are serialaizable ([#47193](https://github.com/laravel/framework/pull/47193)) -- Use carbon::now() to get current timestamp in takeUntilTimeout lazycollection-method ([#47200](https://github.com/laravel/framework/pull/47200)) -- Avoid duplicates in visible/hidden on merge ([#47264](https://github.com/laravel/framework/pull/47264)) -- Add a missing semicolon to CompilesClasses ([#47280](https://github.com/laravel/framework/pull/47280)) -- Send along value to InvalidPayloadException ([#47223](https://github.com/laravel/framework/pull/47223)) - -## [v10.12.0 (2023-05-23)](https://github.com/laravel/framework/compare/v10.11.0...v10.12.0) - -### Added - -- Added `Illuminate/Queue/Events/JobTimedOut.php` ([#47068](https://github.com/laravel/framework/pull/47068)) -- Added `when()` and `unless()` methods to `Illuminate/Support/Sleep` ([#47114](https://github.com/laravel/framework/pull/47114)) -- Adds inline attachments support for markdown mailables ([#47140](https://github.com/laravel/framework/pull/47140)) -- Added `Illuminate/Testing/Concerns/AssertsStatusCodes::assertMethodNotAllowed()` ([#47169](https://github.com/laravel/framework/pull/47169)) -- Added `forceCreateQuietly` method ([#47162](https://github.com/laravel/framework/pull/47162)) -- Added parameters to timezone validation rule ([#47171](https://github.com/laravel/framework/pull/47171)) - -### Fixed - -- Fixes singleton and api singletons creatable|destryoable|only|except combinations ([#47098](https://github.com/laravel/framework/pull/47098)) -- Don't use empty key or secret for DynamoDBClient ([#47144](https://github.com/laravel/framework/pull/47144)) - -### Changed - -- Remove session on authenticatable deletion ([#47141](https://github.com/laravel/framework/pull/47141)) -- Added error handling and ensure re-enabling of foreign key constraints in `Illuminate/Database/Schema/Builder::withoutForeignKeyConstraints()` ([#47182](https://github.com/laravel/framework/pull/47182)) - -### Refactoring - -- Remove useless else statements ([#47161](https://github.com/laravel/framework/pull/47161)) - -## [v10.11.0 (2023-05-16)](https://github.com/laravel/framework/compare/v10.10.1...v10.11.0) - -### Added - -- Added the ability to extend the generic types for DatabaseNotificationCollection ([#47048](https://github.com/laravel/framework/pull/47048)) -- Added `/Illuminate/Support/Carbon::createFromId()` ([#47046](https://github.com/laravel/framework/pull/47046)) -- Added Name attributes on slots ([#47065](https://github.com/laravel/framework/pull/47065)) -- Added Precognition-Success header ([#47081](https://github.com/laravel/framework/pull/47081)) -- Added Macroable trait to Sleep class ([#47099](https://github.com/laravel/framework/pull/47099)) - -### Fixed - -- Fixed `Illuminate/Database/Console/ShowModelCommand::getPolicy()` ([#47043](https://github.com/laravel/framework/pull/47043)) - -### Changed - -- Remove return from channelRoutes method ([#47059](https://github.com/laravel/framework/pull/47059)) -- Bug in `Illuminate/Database/Migrations/Migrator::reset()` with string path ([#47047](https://github.com/laravel/framework/pull/47047)) -- Unify logic around cursor paginate ([#47094](https://github.com/laravel/framework/pull/47094)) -- Clears resolved instance of Vite when using withoutVite ([#47091](https://github.com/laravel/framework/pull/47091)) -- Remove workarounds for old Guzzle versions ([#47084](https://github.com/laravel/framework/pull/47084)) - -## [v10.10.1 (2023-05-11)](https://github.com/laravel/framework/compare/v10.10.0...v10.10.1) - -### Added - -- Added `/Illuminate/Collections/Arr::mapWithKeys()` ([#47000](https://github.com/laravel/framework/pull/47000)) -- Added `dd` and `dump` methods to `Illuminate/Support/Carbon.php` ([#47002](https://github.com/laravel/framework/pull/47002)) -- Added `Illuminate/Queue/Failed/FileFailedJobProvider` ([#47007](https://github.com/laravel/framework/pull/47007)) -- Added arguments to the signed middleware to ignore properties ([#46987](https://github.com/laravel/framework/pull/46987)) - -### Fixed - -- Added keys length check to prevent mget error in `Illuminate/Cache/RedisStore::many()` ([#46998](https://github.com/laravel/framework/pull/46998)) -- 'hashed' cast - do not rehash already hashed value ([#47029](https://github.com/laravel/framework/pull/47029)) - -### Changed - -- Used `Carbon::now()` instead of `now()` ([#47017](https://github.com/laravel/framework/pull/47017)) -- Use file locks when writing failed jobs to disk ([b822d28](https://github.com/laravel/framework/commit/b822d2810d29ab1aedf667abc76ed969d28bbaf5)) -- Raise visibility of Mailable prepareMailableForDelivery() ([#47031](https://github.com/laravel/framework/pull/47031)) - -## [v10.10.0 (2023-05-09)](https://github.com/laravel/framework/compare/v10.9.0...v10.10.0) - -### Added - -- Added `$isolated` and `isolatedExitCode` properties to `Illuminate/Console/Command` ([#46925](https://github.com/laravel/framework/pull/46925)) -- Added ability to restore/set Global Scopes ([#46922](https://github.com/laravel/framework/pull/46922)) -- Added `Illuminate/Collections/Arr::sortRecursiveDesc()` ([#46945](https://github.com/laravel/framework/pull/46945)) -- Added `Illuminate/Support/Sleep` ([#46904](https://github.com/laravel/framework/pull/46904), [#46963](https://github.com/laravel/framework/pull/46963)) -- Added `Illuminate/Database/Eloquent/Concerns/HasAttributes::castAttributeAsHashedString()` ([#46947]https://github.com/laravel/framework/pull/46947) -- Added url support for mail config ([#46964](https://github.com/laravel/framework/pull/46964)) - -### Fixed - -- Fixed replace missing_unless ([89ac58a](https://github.com/laravel/framework/commit/89ac58aa9b4fb7ef9f3b2290921488da1454ed30)) -- Gracefully handle invalid code points in e() ([#46914](https://github.com/laravel/framework/pull/46914)) -- HasCasts returning false instead of true ([#46992](https://github.com/laravel/framework/pull/46992)) - -### Changed - -- Use method on UploadedFile to validate image dimensions ([#46912](https://github.com/laravel/framework/pull/46912)) -- Expose Js::json() helper ([#46935](https://github.com/laravel/framework/pull/46935)) -- Respect parents on middleware priority ([#46972](https://github.com/laravel/framework/pull/46972)) -- Do reconnect when redis throws connection lost error ([#46989](https://github.com/laravel/framework/pull/46989)) -- Throw timeoutException instead of maxAttemptsExceededException when a job times out ([#46968](https://github.com/laravel/framework/pull/46968)) - -## [v10.9.0 (2023-04-25)](https://github.com/laravel/framework/compare/v10.8.0...v10.9.0) - -### Added - -- Add new HTTP status assertions ([#46841](https://github.com/laravel/framework/pull/46841)) -- Allow pruning all cancelled and unfinished queue batches ([#46833](https://github.com/laravel/framework/pull/46833)) -- Added `IGNITION_LOCAL_SITES_PATH` to `$passthroughVariables` in `ServeCommand.php` ([#46857](https://github.com/laravel/framework/pull/46857)) -- Added named static methods for middleware ([#46362](https://github.com/laravel/framework/pull/46362)) - -### Fixed - -- Fix date_format rule throw ValueError ([#46824](https://github.com/laravel/framework/pull/46824)) - -### Changed - -- Allow separate directory for locks on filestore ([#46811](https://github.com/laravel/framework/pull/46811)) -- Allow to whereMorphedTo work with null model ([#46821](https://github.com/laravel/framework/pull/46821)) -- Use pivot model fromDateTime instead of assuming Carbon in `Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable::addTimestampsToAttachment()` ([#46822](https://github.com/laravel/framework/pull/46822)) -- Make rules method in FormRequest optional ([#46846](https://github.com/laravel/framework/pull/46846)) -- Throw LogicException when calling FileFactory@image() if mimetype is not supported ([#46859](https://github.com/laravel/framework/pull/46859)) -- Improve job release method to accept date instance ([#46854](https://github.com/laravel/framework/pull/46854)) -- Use foreignUlid if model uses HasUlids trait when call foreignIdFor ([#46876](https://github.com/laravel/framework/pull/46876)) - -## [v10.8.0 (2023-04-18)](https://github.com/laravel/framework/compare/v10.7.1...v10.8.0) - -### Added - -- Added syntax sugar to the Process::pipe method ([#46745](https://github.com/laravel/framework/pull/46745)) -- Allow specifying index name when calling ForeignIdColumnDefinition@constrained() ([#46746](https://github.com/laravel/framework/pull/46746)) -- Allow to customise redirect URL in AuthenticateSession Middleware ([#46752](https://github.com/laravel/framework/pull/46752)) -- Added Class based after validation rules ([#46757](https://github.com/laravel/framework/pull/46757)) -- Added max exceptions to broadcast event ([#46800](https://github.com/laravel/framework/pull/46800)) - -### Fixed - -- Fixed compiled view file ends with .php ([#46755](https://github.com/laravel/framework/pull/46755)) -- Fix validation rule names ([#46768](https://github.com/laravel/framework/pull/46768)) -- Fixed validateDecimal() ([#46809](https://github.com/laravel/framework/pull/46809)) - -### Changed - -- Add headers to exception in `Illuminate/Foundation/Application::abourd()` ([#46780](https://github.com/laravel/framework/pull/46780)) -- Minor skeleton slimming (framework edition) ([#46786](https://github.com/laravel/framework/pull/46786)) -- Release lock for job implementing ShouldBeUnique that is dispatched afterResponse() ([#46806](https://github.com/laravel/framework/pull/46806)) - -## [v10.7.1 (2023-04-11)](https://github.com/laravel/framework/compare/v10.7.0...v10.7.1) - -### Changed - -- Changed `Illuminate/Process/Factory::pipe()` method. It will be run pipes immediately ([e34ab39](https://github.com/laravel/framework/commit/e34ab392800bfc175334c90e9321caa7261c2d65)) - -## [v10.7.0 (2023-04-11)](https://github.com/laravel/framework/compare/v10.6.2...v10.7.0) - -### Added - -- Allow `Illuminate/Foundation/Testing/WithFaker` to be used when app is not bound ([#46529](https://github.com/laravel/framework/pull/46529)) -- Allow Event::assertListening to check for invokable event listeners ([#46683](https://github.com/laravel/framework/pull/46683)) -- Added `Illuminate/Process/Factory::pipe()` ([#46527](https://github.com/laravel/framework/pull/46527)) -- Added `Illuminate/Validation/Validator::setValue` ([#46716](https://github.com/laravel/framework/pull/46716)) - -### Fixed - -- PHP 8.0 fix for Closure jobs ([#46505](https://github.com/laravel/framework/pull/46505)) -- Fix preg_split error when there is a slash in the attribute in `Illuminate/Validation/ValidationData` ([#46549](https://github.com/laravel/framework/pull/46549)) -- Fixed Cache::spy incompatibility with Cache::get ([#46689](https://github.com/laravel/framework/pull/46689)) -- server command: Fixed server Closing output on invalid $requestPort ([#46726](https://github.com/laravel/framework/pull/46726)) -- Fix nested join when not JoinClause instance ([#46712](https://github.com/laravel/framework/pull/46712)) -- Fix query builder whereBetween method with carbon date period ([#46720](https://github.com/laravel/framework/pull/46720)) - -### Changed - -- Removes unnecessary parameters in `creatable()` / `destroyable()` methods in `Illuminate/Routing/PendingSingletonResourceRegistration` ([#46677](https://github.com/laravel/framework/pull/46677)) -- Return non-zero exit code for uncaught exceptions ([#46541](https://github.com/laravel/framework/pull/46541)) - -## [v10.6.2 (2023-04-05)](https://github.com/laravel/framework/compare/v10.6.1...v10.6.2) - -### Added - -- Added trait `Illuminate/Foundation/Testing/WithConsoleEvents` ([#46694](https://github.com/laravel/framework/pull/46694)) - -### Changed - -- Added missing ignored methods to `Illuminate/View/Component` ([#46692](https://github.com/laravel/framework/pull/46692)) -- console.stub: remove void return type from handle ([#46697](https://github.com/laravel/framework/pull/46697)) - -## [v10.6.1 (2023-04-04)](https://github.com/laravel/framework/compare/v10.6.0...v10.6.1) - -### Reverted - -- Reverted ["Set container instance on session manager"Set container instance on session manager](https://github.com/laravel/framework/pull/46621) ([#46691](https://github.com/laravel/framework/pull/46691)) - -## [v10.6.0 (2023-04-04)](https://github.com/laravel/framework/compare/v10.5.1...v10.6.0) - -### Added - -- Added ability to set a custom class for the AsCollection and AsEncryptedCollection casts ([#46619](https://github.com/laravel/framework/pull/46619)) - -### Changed - -- Set container instance on session manager ([#46621](https://github.com/laravel/framework/pull/46621)) -- Added empty string definition to Str::squish function ([#46660](https://github.com/laravel/framework/pull/46660)) -- Allow $sleepMilliseconds parameter receive a Closure in retry method from PendingRequest ([#46653](https://github.com/laravel/framework/pull/46653)) -- Support contextual binding on first class callables ([de8d515](https://github.com/laravel/framework/commit/de8d515fc6d1fabc8f14450342554e0eb67df725), [e511a3b](https://github.com/laravel/framework/commit/e511a3bdb15c294866428b4fe665a4ad14540038)) - -## [v10.5.1 (2023-03-29)](https://github.com/laravel/framework/compare/v10.5.0...v10.5.1) - -### Added - -- Added methods to determine if API resource has pivot loaded ([#46555](https://github.com/laravel/framework/pull/46555)) -- Added caseSensitive flag to Stringable replace function ([#46578](https://github.com/laravel/framework/pull/46578)) -- Allow insert..select (insertUsing()) to have empty $columns ([#46605](https://github.com/laravel/framework/pull/46605), [399bff9](https://github.com/laravel/framework/commit/399bff9331252e64a3439ea43e05f87f901dad55)) -- Added `Illuminate/Database/Connection::selectResultSets()` ([#46592](https://github.com/laravel/framework/pull/46592)) - -### Changed - -- Make sure pivot model has previously defined values ([#46559](https://github.com/laravel/framework/pull/46559)) -- Move SetUniqueIds to run before the creating event ([#46622](https://github.com/laravel/framework/pull/46622)) - -## [v10.5.0 (2023-03-28)](https://github.com/laravel/framework/compare/v10.4.1...v10.5.0) - -### Added - -- Added `Illuminate/Cache/CacheManager::setApplication()` ([#46594](https://github.com/laravel/framework/pull/46594)) - -### Fixed - -- Fix infinite loading on batches list on Horizon ([#46536](https://github.com/laravel/framework/pull/46536)) -- Fix whereNull queries with raw expressions for the MySql grammar ([#46538](https://github.com/laravel/framework/pull/46538)) -- Fix getDirty method when using AsEnumArrayObject / AsEnumCollection ([#46561](https://github.com/laravel/framework/pull/46561)) - -### Changed - -- Skip `Illuminate/Support/Reflector::isParameterBackedEnumWithStringBackingType` for non ReflectionNamedType ([#46511](https://github.com/laravel/framework/pull/46511)) -- Replace Deprecated DBAL Comparator creation with schema aware Comparator ([#46517](https://github.com/laravel/framework/pull/46517)) -- Added Storage::json() method to read and decode a json file ([#46548](https://github.com/laravel/framework/pull/46548)) -- Force cast json decoded failed_job_ids to array in DatabaseBatchRepository ([#46581](https://github.com/laravel/framework/pull/46581)) -- Handle empty arrays for DynamoDbStore multi-key operations ([#46579](https://github.com/laravel/framework/pull/46579)) -- Stop adding constraints twice on *Many to *One relationships via one() ([#46575](https://github.com/laravel/framework/pull/46575)) -- allow override of the Builder paginate() total ([#46415](https://github.com/laravel/framework/pull/46415)) -- Add a possibility to set a custom on_stats function for the Http Facade ([#46569](https://github.com/laravel/framework/pull/46569)) - -## [v10.4.1 (2023-03-18)](https://github.com/laravel/framework/compare/v10.4.0...v10.4.1) - -### Changed - -- Move Symfony events dispatcher registration to Console\Kernel ([#46508](https://github.com/laravel/framework/pull/46508)) - -## [v10.4.0 (2023-03-17)](https://github.com/laravel/framework/compare/v10.3.3...v10.4.0) - -### Added - -- Added `Illuminate/Testing/Concerns/AssertsStatusCodes::assertUnsupportedMediaType()` ([#46426](https://github.com/laravel/framework/pull/46426)) -- Added curl_error_code: 77 to DetectsLostConnections ([#46429](https://github.com/laravel/framework/pull/46429)) -- Allow for converting a HasMany to HasOne && MorphMany to MorphOne ([#46443](https://github.com/laravel/framework/pull/46443)) -- Add option to create macroable method for paginationInformation ([#46461](https://github.com/laravel/framework/pull/46461)) -- Added `Illuminate/Filesystem/Filesystem::json()` ([#46481](https://github.com/laravel/framework/pull/46481)) - -### Fixed - -- Fix parsed input arguments for command events using dispatcher rerouting ([#46442](https://github.com/laravel/framework/pull/46442)) -- Fix enums uses with optional implicit parameters ([#46483](https://github.com/laravel/framework/pull/46483)) -- Fix deprecations for embedded images in symfony mailer ([#46488](https://github.com/laravel/framework/pull/46488)) - -### Changed - -- Added alternative database port in Postgres DSN ([#46403](https://github.com/laravel/framework/pull/46403)) -- Allow calling getControllerClass on closure-based routes ([#46411](https://github.com/laravel/framework/pull/46411)) -- Remove obsolete method_exists(ReflectionClass::class, 'isEnum') call ([#46445](https://github.com/laravel/framework/pull/46445)) -- Convert eloquent builder to base builder in whereExists ([#46460](https://github.com/laravel/framework/pull/46460)) -- Refactor shared static methodExcludedByOptions method to trait ([#46498](https://github.com/laravel/framework/pull/46498)) - -## [v10.3.3 (2023-03-09)](https://github.com/laravel/framework/compare/v10.3.2...v10.3.3) - -### Reverted - -- Reverted ["Allow override of the Builder paginate() total"](https://github.com/laravel/framework/pull/46336) ([#46406](https://github.com/laravel/framework/pull/46406)) - -## [v10.3.2 (2023-03-08)](https://github.com/laravel/framework/compare/v10.3.1...v10.3.2) - -### Reverted - -- Reverted ["FIX on CanBeOneOfMany trait giving erroneous results"](https://github.com/laravel/framework/pull/46309) ([#46402](https://github.com/laravel/framework/pull/46402)) - -### Fixed - -- Fixes Expression no longer implements Stringable ([#46395](https://github.com/laravel/framework/pull/46395)) - -## [v10.3.1 (2023-03-08)](https://github.com/laravel/framework/compare/v10.3.0...v10.3.1) - -### Reverted - -- Reverted ["Use fallback when previous URL is the same as the current in `Illuminate/Routing/UrlGenerator::previous()`"](https://github.com/laravel/framework/pull/46234) ([#46392](https://github.com/laravel/framework/pull/46392)) - -## [v10.3.0 (2023-03-07)](https://github.com/laravel/framework/compare/v10.2.0...v10.3.0) - -### Added - -- Adding Pipeline Facade ([#46271](https://github.com/laravel/framework/pull/46271)) -- Add Support for SaveQuietly and Upsert with UUID/ULID Primary Keys ([#46161](https://github.com/laravel/framework/pull/46161)) -- Add charAt method to both Str and Stringable ([#46349](https://github.com/laravel/framework/pull/46349), [dfb59bc2](https://github.com/laravel/framework/commit/dfb59bc263a4e28ac8992deeabd2ccd9392d1681)) -- Adds Countable to the InvokedProcessPool class ([#46346](https://github.com/laravel/framework/pull/46346)) -- Add processors to logging (placeholders) ([#46344](https://github.com/laravel/framework/pull/46344)) - -### Fixed - -- Fixed `Illuminate/Mail/Mailable::buildMarkdownView()` ([791f8ea7](https://github.com/laravel/framework/commit/791f8ea70b5872ae4483a32f6aeb28dd2ed4b8d7)) -- FIX on CanBeOneOfMany trait giving erroneous results ([#46309](https://github.com/laravel/framework/pull/46309)) - -### Changed - -- Use fallback when previous URL is the same as the current in `Illuminate/Routing/UrlGenerator::previous()` ([#46234](https://github.com/laravel/framework/pull/46234)) -- Allow override of the Builder paginate() total ([#46336](https://github.com/laravel/framework/pull/46336)) - -## [v10.2.0 (2023-03-02)](https://github.com/laravel/framework/compare/v10.1.5...v10.2.0) - -### Added - -- Adding `Conditionable` train to Logger ([#46259](https://github.com/laravel/framework/pull/46259)) -- Added "dot" method to Illuminate\Support\Collection class ([#46265](https://github.com/laravel/framework/pull/46265)) -- Added a "channel:list" command ([#46248](https://github.com/laravel/framework/pull/46248)) -- Added JobPopping and JobPopped events ([#46220](https://github.com/laravel/framework/pull/46220)) -- Add isMatch method to Str and Stringable helpers ([#46303](https://github.com/laravel/framework/pull/46303)) -- Add ArrayAccess to Stringable ([#46279](https://github.com/laravel/framework/pull/46279)) - -### Reverted - -- Revert "[10.x] Fix custom themes not resetting on Markdown renderer" ([#46328](https://github.com/laravel/framework/pull/46328)) - -### Fixed - -- Fix typo in function `createMissingSqliteDatbase` name in `src/Illuminate/Database/Console/Migrations/MigrateCommand.php` ([#46326](https://github.com/laravel/framework/pull/46326)) - -### Changed - -- Generate default command name based on class name in `ConsoleMakeCommand` ([#46256](https://github.com/laravel/framework/pull/46256)) -- Do not mutate underlying values on redirect ([#46281](https://github.com/laravel/framework/pull/46281)) -- Do not use null to initialise $lastExecutionStartedAt in `ScheduleWorkCommand` ([#46285](https://github.com/laravel/framework/pull/46285)) -- Remove obsolete function_exists('enum_exists') calls ([#46319](https://github.com/laravel/framework/pull/46319)) -- Cast json decoded failed_job_ids to array in DatabaseBatchRepository::toBatch ([#46329](https://github.com/laravel/framework/pull/46329)) - -## [v10.1.5 (2023-02-24)](https://github.com/laravel/framework/compare/v10.1.4...v10.1.5) - -### Fixed - -- Fixed `Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase::expectsDatabaseQueryCount()` $connection parameter ([#46228](https://github.com/laravel/framework/pull/46228)) -- Fixed Facade Fake ([#46257](https://github.com/laravel/framework/pull/46257)) - -### Changed - -- Remove autoload dumping from make:migration ([#46215](https://github.com/laravel/framework/pull/46215)) - -## [v10.1.4 (2023-02-23)](https://github.com/laravel/framework/compare/v10.1.3...v10.1.4) - -### Changed - -- Improve Facade Fake Awareness ([#46188](https://github.com/laravel/framework/pull/46188), [#46232](https://github.com/laravel/framework/pull/46232)) - -## [v10.1.3 (2023-02-22)](https://github.com/laravel/framework/compare/v10.1.2...v10.1.3) - -### Added - -- Added protected method `Illuminate/Http/Resources/Json/JsonResource::newCollection()` for simplifies collection customisation ([#46217](https://github.com/laravel/framework/pull/46217)) - -### Fixed - -- Fixes constructable migrations ([#46223](https://github.com/laravel/framework/pull/46223)) - -### Changes - -- Accept time when generating ULID in `Str::ulid()` ([#46201](https://github.com/laravel/framework/pull/46201)) - -## [v10.1.2 (2023-02-22)](https://github.com/laravel/framework/compare/v10.1.1...v10.1.2) - -### Reverted - -- Revert changes from `Arr::random()` ([cf3eb90](https://github.com/laravel/framework/commit/cf3eb90a6473444bb7a78d1a3af1e9312a62020d)) - -## [v10.1.1 (2023-02-21)](https://github.com/laravel/framework/compare/v10.1.0...v10.1.1) - -### Added - -- Add the ability to re-resolve cache drivers ([#46203](https://github.com/laravel/framework/pull/46203)) - -### Fixed - -- Fixed `Illuminate/Collections/Arr::shuffle()` for empty array ([0c6cae0](https://github.com/laravel/framework/commit/0c6cae0ef647158b9554cad05ff39db7e7ad0d33)) - -## [v10.1.0 (2023-02-21)](https://github.com/laravel/framework/compare/v10.0.3...v10.1.0) - -### Fixed - -- Fixing issue where 0 is discarded as a valid timestamp ([#46158](https://github.com/laravel/framework/pull/46158)) -- Fix custom themes not resetting on Markdown renderer ([#46200](https://github.com/laravel/framework/pull/46200)) - -### Changed - -- Use secure randomness in Arr:random and Arr:shuffle ([#46105](https://github.com/laravel/framework/pull/46105)) -- Use mixed return type on controller stubs ([#46166](https://github.com/laravel/framework/pull/46166)) -- Use InteractsWithDictionary in Eloquent collection ([#46196](https://github.com/laravel/framework/pull/46196)) - -## [v10.0.3 (2023-02-17)](https://github.com/laravel/framework/compare/v10.0.2...v10.0.3) - -### Added - -- Added missing expression support for pluck in Builder ([#46146](https://github.com/laravel/framework/pull/46146)) - -## [v10.0.2 (2023-02-16)](https://github.com/laravel/framework/compare/v10.0.1...v10.0.2) - -### Added - -- Register policies automatically to the gate ([#46132](https://github.com/laravel/framework/pull/46132)) - -## [v10.0.1 (2023-02-16)](https://github.com/laravel/framework/compare/v10.0.0...v10.0.1) - -### Added - -- Standard Input can be applied to PendingProcess ([#46119](https://github.com/laravel/framework/pull/46119)) - -### Fixed - -- Fix Expression string casting ([#46137](https://github.com/laravel/framework/pull/46137)) - -### Changed - -- Add AddQueuedCookiesToResponse to middlewarePriority so it is handled in the right place ([#46130](https://github.com/laravel/framework/pull/46130)) -- Show queue connection in MonitorCommand ([#46122](https://github.com/laravel/framework/pull/46122)) - -## [v10.0.0 (2023-02-14)](https://github.com/laravel/framework/compare/v10.0.0...10.x) - -Please consult the [upgrade guide](https://laravel.com/docs/10.x/upgrade) and [release notes](https://laravel.com/docs/10.x/releases) in the official Laravel documentation. diff --git a/bin/release.sh b/bin/release.sh index 5f539280d37b..b629084461d7 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 diff --git a/bin/split.sh b/bin/split.sh index ed09b53c9a15..9536ec7a4f31 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() { 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 9e7705b73db9..84c53c70428d 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-ctype": "*", "ext-filter": "*", "ext-hash": "*", @@ -28,30 +28,31 @@ "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": "^0.1.12", "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.2", - "symfony/error-handler": "^6.2", - "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.4", - "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": "^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/polyfill-php83": "^1.28", + "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" @@ -95,9 +96,9 @@ "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.5", + "guzzlehttp/guzzle": "^7.6", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", "league/flysystem-path-prefixing": "^3.3", @@ -105,14 +106,14 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^8.15.1", + "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": "^6.2", - "symfony/http-client": "^6.2.4", - "symfony/psr-http-message-bridge": "^2.0" + "symfony/cache": "^7.0", + "symfony/http-client": "^7.0", + "symfony/psr-http-message-bridge": "^v7.0.0-BETA1" }, "provide": { "psr/container-implementation": "1.1|2.0", @@ -147,7 +148,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { @@ -163,10 +164,10 @@ "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.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).", @@ -180,11 +181,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": { @@ -193,6 +194,6 @@ "composer/package-versions-deprecated": true } }, - "minimum-stability": "stable", + "minimum-stability": "dev", "prefer-stable": true } 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..f030206fc946 --- /dev/null +++ b/config/database.php @@ -0,0 +1,169 @@ + 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' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | 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..f34179a8f19a --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,67 @@ + 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, + ], + + /* + |-------------------------------------------------------------------------- + | 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, + +]; 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..51c77f90af42 --- /dev/null +++ b/config/session.php @@ -0,0 +1,215 @@ + 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'), + + /* + |-------------------------------------------------------------------------- + | 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, + +]; 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/Access/Response.php b/src/Illuminate/Auth/Access/Response.php index b7bae487c00a..6461d0fce128 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/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/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/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/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..958229947daf --- /dev/null +++ b/src/Illuminate/Auth/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,80 @@ +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) + : $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 '/'; + } + + /** + * 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/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/Auth/composer.json b/src/Illuminate/Auth/composer.json index c4034e5f5b69..5b74a53b91c5 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -14,14 +14,14 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-hash": "*", - "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": { @@ -30,13 +30,13 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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/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/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/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index 40be802931a1..e9e6bbb4a211 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -14,14 +14,14 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "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": { @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { 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/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/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/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/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index 609224f0804c..3acfed639cf9 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -14,11 +14,11 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/pipeline": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/pipeline": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -27,7 +27,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { 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/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/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/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index dbbeb40a4b5c..369e2c6e3d7a 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/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index 38c471704fdb..ec0d26e18469 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -14,11 +14,11 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "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" @@ -30,17 +30,17 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { "ext-apcu": "Required to use the APC cache driver.", "ext-filter": "Required to use the DynamoDb cache driver.", "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).", - "symfony/cache": "Required to use PSR-6 cache bridge (^6.2)." + "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 (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index a561488e8a59..559e2ecbfec7 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. @@ -564,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 ce84977aad99..7d25e9d899d1 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()); } diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index dd4d5ab292a2..06aba9b6865b 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -13,7 +13,6 @@ use Illuminate\Support\HigherOrderCollectionProxy; use InvalidArgumentException; use JsonSerializable; -use Symfony\Component\VarDumper\VarDumper; use Traversable; use UnexpectedValueException; use UnitEnum; @@ -56,7 +55,7 @@ */ trait EnumeratesValues { - use Conditionable; + use Conditionable, Dumpable; /** * Indicates that the object's string representation should be escaped when __toString is invoked. @@ -199,31 +198,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/Collections/composer.json b/src/Illuminate/Collections/composer.json index 5e11a788e704..1924032ab915 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -14,10 +14,10 @@ } ], "require": { - "php": "^8.1", - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0" + "php": "^8.2", + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0" }, "autoload": { "psr-4": { @@ -29,11 +29,11 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.2)." + "symfony/var-dumper": "Required to use the dump method (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 235edceb1509..1854137ee6bf 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(is_array($target) ? $target : collect($target)->all()), + '\{last}' => '{last}', + '{last}' => array_key_last(is_array($target) ? $target : collect($target)->all()), + default => $segment, + }; + if (Arr::accessible($target) && Arr::exists($target, $segment)) { $target = $target[$segment]; } elseif (is_object($target) && isset($target->{$segment})) { 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 d3442b45a548..ccccc4303e09 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -14,9 +14,9 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0" + "php": "^8.2", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 98536ce41eec..9bfe73b9b43e 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -206,9 +206,10 @@ 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) + #[\Override] + public function add(SymfonyCommand $command): ?SymfonyCommand { if ($command instanceof Command) { $command->setLaravel($this->laravel); @@ -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 aa4e6ceedc4e..0cb40487ba27 100644 --- a/src/Illuminate/Console/BufferedConsoleOutput.php +++ b/src/Illuminate/Console/BufferedConsoleOutput.php @@ -27,10 +27,9 @@ public function fetch() /** * {@inheritdoc} - * - * @return void */ - protected function doWrite(string $message, bool $newline) + #[\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 7e1b3a1ff6ed..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( @@ -190,9 +191,9 @@ 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) + #[\Override] + protected function execute(InputInterface $input, OutputInterface $output): int { if ($this instanceof Isolatable && $this->option('isolated') !== false && ! $this->commandIsolationMutex()->create($this)) { @@ -258,6 +259,7 @@ protected function resolveCommand($command) * * @return bool */ + #[\Override] public function isHidden(): bool { return $this->hidden; @@ -266,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/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/Console/OutputStyle.php b/src/Illuminate/Console/OutputStyle.php index cbfc257220e7..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,7 +65,8 @@ public function askQuestion(Question $question): mixed /** * {@inheritdoc} */ - public function write(string|iterable $messages, bool $newline = false, int $options = 0) + #[\Override] + 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 +76,9 @@ 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) + #[\Override] + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void { $this->newLinesWritten = $this->trailingNewLineCount($messages) + 1; $this->newLineWritten = true; @@ -87,10 +88,9 @@ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORM /** * {@inheritdoc} - * - * @return void */ - public function newLine(int $count = 1) + #[\Override] + 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..6d8a648fde70 100644 --- a/src/Illuminate/Console/QuestionHelper.php +++ b/src/Illuminate/Console/QuestionHelper.php @@ -17,7 +17,8 @@ class QuestionHelper extends SymfonyQuestionHelper * * @return void */ - protected function writePrompt(OutputInterface $output, Question $question) + #[\Override] + protected function writePrompt(OutputInterface $output, Question $question): void { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 98fff2fec158..a50ceabfac14 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; @@ -636,15 +637,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. * 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/Console/composer.json b/src/Illuminate/Console/composer.json index 53fb6dbcd553..c26420bcc122 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -14,17 +14,18 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-mbstring": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "laravel/prompts": "^0.1.9", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", - "nunomaduro/termwind": "^1.13", - "symfony/console": "^6.2", - "symfony/process": "^6.2" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "illuminate/view": "^11.0", + "laravel/prompts": "^0.1.12", + "nunomaduro/termwind": "^2.0", + "symfony/console": "^7.0", + "symfony/polyfill-php83": "^1.28", + "symfony/process": "^7.0" }, "autoload": { "psr-4": { @@ -33,17 +34,17 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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).", - "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)." + "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).", + "illuminate/queue": "Required to use closures for scheduled jobs (^11.0)." }, "config": { "sort-packages": true 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/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index 02636c73fa45..ab9f51c8f545 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -14,8 +14,8 @@ } ], "require": { - "php": "^8.1", - "illuminate/contracts": "^10.0", + "php": "^8.2", + "illuminate/contracts": "^11.0", "psr/container": "^1.1.1|^2.0.1" }, "provide": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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/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/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); 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/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); } 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. * diff --git a/src/Illuminate/Contracts/composer.json b/src/Illuminate/Contracts/composer.json index acae4bef8d69..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" }, @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Cookie/Middleware/EncryptCookies.php b/src/Illuminate/Cookie/Middleware/EncryptCookies.php index c80e4c340e08..0066b24503d5 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. * @@ -206,7 +214,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/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 10277e08b715..e8514738ce54 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -14,14 +14,14 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-hash": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", - "symfony/http-foundation": "^6.4", - "symfony/http-kernel": "^6.2" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0" }, "autoload": { "psr-4": { @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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. * 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/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/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 87c06c2f96fe..8a230c0146fb 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -914,11 +914,11 @@ public function pluck($column, $key = null) * * @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 = func_num_args() === 5 ? value(func_get_arg(4)) : $this->toBase()->getCountForPagination(); + $total = value($total) ?? $this->toBase()->getCountForPagination(); $perPage = ($perPage instanceof Closure ? $perPage($total) 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 8385eef4eea9..a95fb62fb73a 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -40,6 +40,7 @@ use ReflectionMethod; use ReflectionNamedType; use RuntimeException; +use ValueError; trait HasAttributes { @@ -182,6 +183,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. * @@ -310,7 +323,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) { @@ -719,11 +732,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. * @@ -1162,10 +1203,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) ); } } @@ -1187,11 +1228,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; @@ -1528,7 +1574,7 @@ public function hasCast($key, $types = null) } /** - * Get the casts array. + * Get the attributes that should be cast. * * @return array */ @@ -1541,6 +1587,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/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index ee4c10aae4c4..d93193e12536 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, @@ -965,6 +966,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($this->newQueryWithoutScopes())->{$method}($column, $amount, $extra), function () use ($column) { $this->syncChanges(); 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/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/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 3aa2b542e10e..ba9ce025c45a 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -22,6 +22,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; @@ -30,7 +31,7 @@ class Builder implements BuilderContract { - use BuildsQueries, ExplainsQueries, ForwardsCalls, Macroable { + use BuildsQueries, Dumpable, ExplainsQueries, ForwardsCalls, Macroable { __call as macroCall; } @@ -2763,11 +2764,11 @@ protected function runSelect() * @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 = func_num_args() === 5 ? value(func_get_arg(4)) : $this->getCountForPagination(); + $total = value($total) ?? $this->getCountForPagination(); $perPage = $perPage instanceof Closure ? $perPage($total) : $perPage; @@ -3904,11 +3905,16 @@ 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; } 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..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. * @@ -91,4 +139,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..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. * @@ -99,6 +110,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/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index a3179742491a..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 = null, $places = null, $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 = null, $places = null, $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/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 07698e7fc206..94d655c0f1ca 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. * @@ -302,7 +312,9 @@ public function getColumnType($table, $column, $fullDefinition = false) if (! $this->connection->usingNativeSchemaOperations()) { $table = $this->connection->getTablePrefix().$table; - return $this->connection->getDoctrineColumn($table, $column)->getType()->getName(); + $type = $this->connection->getDoctrineColumn($table, $column)->getType(); + + return $type::lookupName($type); } $columns = $this->getColumns($table); @@ -357,6 +369,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/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/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 43875591e07f..07847fa23f8e 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. * @@ -774,7 +800,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 +815,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..b41575709042 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,24 @@ 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"; + } + + /** + * 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')"; } /** @@ -189,6 +206,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. * @@ -473,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() @@ -809,7 +869,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/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 a008fb73b0c6..744a9d691534 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; @@ -146,6 +147,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. * @@ -397,13 +415,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(), + ) + ); } } @@ -678,7 +715,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..dd56619c74d9 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. * @@ -708,6 +737,10 @@ protected function typeSmallInteger(Fluent $column) */ protected function typeFloat(Fluent $column) { + if ($column->precision) { + return "float({$column->precision})"; + } + return 'float'; } @@ -719,7 +752,7 @@ protected function typeFloat(Fluent $column) */ protected function typeDouble(Fluent $column) { - return 'float'; + return 'double precision'; } /** 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..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)); + } } /** @@ -220,6 +241,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/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/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index cf735d559f22..c840493bd621 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -15,14 +15,14 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-pdo": "*", "brick/math": "^0.9.3|^0.10.2|^0.11", - "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" }, "autoload": { "psr-4": { @@ -31,18 +31,18 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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 (^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).", - "symfony/finder": "Required to use Eloquent model factories (^6.2)." + "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 (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index 470919a75c5f..0c127430432d 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -14,12 +14,12 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -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 b23bc7209f94..df77fedb69eb 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -14,13 +14,13 @@ } ], "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" + "php": "^8.2", + "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": { @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index d81b31ff6e91..20ba1dd054f9 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'] ?? [] diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index 66925c92b553..b46be7d136d2 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -14,12 +14,12 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", - "symfony/finder": "^6.2" + "php": "^8.2", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "symfony/finder": "^7.0" }, "autoload": { "psr-4": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { @@ -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 (^7.0).", + "symfony/mime": "Required to enable support for guessing extensions (^7.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index a24cdef721d1..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; @@ -38,7 +41,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.35.0'; + const VERSION = '11.x-dev'; /** * The base path for the Laravel installation. @@ -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..bfefaeda3bbe --- /dev/null +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -0,0 +1,336 @@ +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')); + + $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 4cd54e8e4a79..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. * @@ -39,7 +46,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()); @@ -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..bc12f35cd4d8 --- /dev/null +++ b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php @@ -0,0 +1,100 @@ +getBaseConfigurationFiles(); + + 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.'); + + return 1; + } + + $this->publish($name, $config[$name], $this->laravel->configPath().'/'.$name.'.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/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'; 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/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/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 498be561b7dc..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. * @@ -360,7 +402,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); } @@ -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 254139403546..d39b09981a80 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, '/'); } @@ -216,6 +224,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 b92bd4ac91cb..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, @@ -292,7 +298,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']); }); } @@ -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. * @@ -508,7 +526,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 +656,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 +668,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 +680,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 +740,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 +752,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']); }); } 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/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/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 6a14fb1d30fe..4910778d651d 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -580,7 +580,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); } /** @@ -702,11 +702,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/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/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index c68b19bbd54d..ad1ae9dcac21 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]); + } + } } /** @@ -93,7 +86,13 @@ public function beginDatabaseTransaction() foreach ($this->connectionsToTransact() as $name) { $connection = $database->connection($name); + $connection->setTransactionManager($transactionsManager); + + 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. * diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index 330dddf9aba8..32d1701b01f7 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(); } @@ -166,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; } /** @@ -198,6 +188,7 @@ protected function tearDown(): void ParallelTesting::callTearDownTestCaseCallbacks($this); $database = $this->app['db'] ?? null; + foreach (array_keys($database?->getConnections() ?? []) as $name) { $database->purge($name); } 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.'); - } - } -} diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 1b9c02629410..79443fb9d020 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -14,9 +14,9 @@ } ], "require": { - "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 78e361feffad..6966a0f071bf 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -202,11 +202,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', @@ -231,6 +231,7 @@ public function __construct(Factory $factory = null, $middleware = []) $this->options = [ 'connect_timeout' => 10, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, 'http_errors' => false, 'timeout' => 30, ]; @@ -626,7 +627,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 ); }); @@ -997,9 +998,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 +1013,70 @@ protected function makePromise(string $method, string $url, array $options = []) ->otherwise(function (OutOfBoundsException|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. * @@ -1320,7 +1380,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 ); } diff --git a/src/Illuminate/Http/Client/Response.php b/src/Illuminate/Http/Client/Response.php index b73dd8706536..baa774c15efb 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/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/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/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/Http/Request.php b/src/Illuminate/Http/Request.php index 1a36ec2d0f1e..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,18 +534,20 @@ protected function filterFiles($files) /** * {@inheritdoc} */ + #[\Override] public function hasSession(bool $skipIfUninitialized = false): bool { - return ! is_null($this->session); + return $this->session instanceof SymfonySessionDecorator; } /** * {@inheritdoc} */ + #[\Override] public function getSession(): SessionInterface { return $this->hasSession() - ? new SymfonySessionDecorator($this->session()) + ? $this->session : throw new SessionNotFoundException; } @@ -560,7 +564,7 @@ public function session() throw new RuntimeException('Session store not set on request.'); } - return $this->session; + return $this->session->store; } /** @@ -571,7 +575,7 @@ public function session() */ public function setLaravelSession($session) { - $this->session = $session; + $this->session = new SymfonySessionDecorator($session); } /** 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; diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 94d03fc4e851..f683ad8e8390 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -14,17 +14,18 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-filter": "*", - "fruitcake/php-cors": "^1.2", + "fruitcake/php-cors": "^1.3", "guzzlehttp/uri-template": "^1.0", - "illuminate/collections": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/session": "^10.0", - "illuminate/support": "^10.0", - "symfony/http-foundation": "^6.4", - "symfony/http-kernel": "^6.2", - "symfony/mime": "^6.2" + "illuminate/collections": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/session": "^11.0", + "illuminate/support": "^11.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/polyfill-php83": "^1.28", + "symfony/mime": "^7.0" }, "autoload": { "psr-4": { @@ -33,11 +34,11 @@ }, "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": { - "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 054aee6a5e30..dfdcf13a4e7c 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -14,9 +14,9 @@ } ], "require": { - "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0", + "php": "^8.2", + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0", "monolog/monolog": "^3.0" }, "autoload": { @@ -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..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": { @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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 443ad934e031..7ef78eb582fa 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -4,13 +4,14 @@ use Aws\Exception\AwsException; use Aws\Ses\SesClient; +use Stringable; use Symfony\Component\Mailer\Exception\TransportException; 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 876630b9e1be..e874e583abd5 100644 --- a/src/Illuminate/Mail/Transport/SesV2Transport.php +++ b/src/Illuminate/Mail/Transport/SesV2Transport.php @@ -4,13 +4,14 @@ use Aws\Exception\AwsException; use Aws\SesV2\SesV2Client; +use Stringable; use Symfony\Component\Mailer\Exception\TransportException; 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/Mail/composer.json b/src/Illuminate/Mail/composer.json index a31bfc82feca..ad0211e96d7c 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -14,15 +14,15 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", + "php": "^8.2", + "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", + "symfony/mailer": "^7.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5" }, "autoload": { @@ -32,14 +32,14 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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 (^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/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/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index 40a4923a30c2..18321766911a 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -14,16 +14,16 @@ } ], "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" + "php": "^8.2", + "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": { @@ -32,11 +32,11 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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/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/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index 2fc61394bd14..908762c245bd 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -14,11 +14,11 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-filter": "*", - "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": { @@ -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 cd561194ae15..9cd62e83f78c 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -14,9 +14,9 @@ } ], "require": { - "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { diff --git a/src/Illuminate/Process/composer.json b/src/Illuminate/Process/composer.json index 8ac36b6ef2d1..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.2" + "symfony/process": "^7.0" }, "autoload": { "psr-4": { 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/Queue/Console/WorkCommand.php b/src/Illuminate/Queue/Console/WorkCommand.php index b4ed14443f62..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; @@ -13,6 +12,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 +21,8 @@ #[AsCommand(name: 'queue:work')] class WorkCommand extends Command { + use InteractsWithTime; + /** * The console command name. * @@ -217,7 +219,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 +252,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/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/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/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index 0ce7ad1ac1ce..ea2fd6842acb 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 ); } @@ -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/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index f232258da500..66bc9c0f4afd 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -14,18 +14,18 @@ } ], "require": { - "php": "^8.1", - "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", + "php": "^8.2", + "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" + "symfony/process": "^7.0" }, "autoload": { "psr-4": { @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { @@ -44,7 +44,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..1686aeaa13f6 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -14,11 +14,11 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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/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/Routing/composer.json b/src/Illuminate/Routing/composer.json index 1375ef059f9a..92d08b0908fd 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -14,20 +14,20 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-filter": "*", "ext-hash": "*", - "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", - "symfony/http-foundation": "^6.4", - "symfony/http-kernel": "^6.2", - "symfony/routing": "^6.2" + "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": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/routing": "^7.0" }, "autoload": { "psr-4": { @@ -36,11 +36,11 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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/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..b32e3ba50283 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. * @@ -44,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); } } @@ -53,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); } @@ -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/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 083ff581a229..56789edc0f78 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -14,15 +14,15 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-ctype": "*", "ext-session": "*", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0", - "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.4" + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/support": "^11.0", + "symfony/finder": "^7.0", + "symfony/http-foundation": "^7.0" }, "autoload": { "psr-4": { @@ -31,11 +31,11 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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/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/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/Auth.php b/src/Illuminate/Support/Facades/Auth.php index fd5ac5138aeb..32b2eb64aa95 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -28,19 +28,19 @@ * @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) - * @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 = []) * @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() 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/Request.php b/src/Illuminate/Support/Facades/Request.php index 4f94968d7a3d..e5c6affccb54 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) 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 @@ +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,17 @@ public function toArray() return $this->attributes; } + /** + * Convert the fluent instance to a Collection. + * + * @param string|null $key + * @return \Illuminate\Support\Collection + */ + public function collect($key = null) + { + return new Collection($this->get($key)); + } + /** * Convert the object into something JSON serializable. * @@ -114,7 +151,7 @@ public function offsetExists($offset): bool */ public function offsetGet($offset): mixed { - return $this->get($offset); + return $this->value($offset); } /** @@ -162,7 +199,7 @@ public function __call($method, $parameters) */ public function __get($key) { - return $this->get($key); + return $this->value($key); } /** 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/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'; + } } 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 4b303168d63f..8c8fb2ef916e 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/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index a46ba29690aa..561eb740902e 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,22 @@ 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); + + if ($this->app->config->get('database.migrations.update_date_on_publish', false)) { + static::$publishableMigrationPaths = array_unique(array_merge(static::$publishableMigrationPaths, array_keys($paths))); + } + } + /** * Register paths to be published by the publish command. * @@ -380,6 +403,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. * @@ -444,4 +477,38 @@ public static function defaultProviders() { return new DefaultProviders; } + + /** + * Add the given provider to the application's provider bootstrap file. + * + * @param string $provider + * @param string $path + * @return bool + */ + public static function addProviderToBootstrapFile(string $provider, string $path = null) + { + $path ??= app()->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 = '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. * @@ -1227,27 +1238,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/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() + { + // + } } 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/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/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/Support/composer.json b/src/Illuminate/Support/composer.json index 57c92e366238..e0b4a7e13119 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -14,15 +14,15 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-ctype": "*", "ext-filter": "*", "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.67", "voku/portable-ascii": "^2.0" }, @@ -39,16 +39,16 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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).", - "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 (^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/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/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/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/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 47e4cab56e47..38e7466e0095 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -16,6 +16,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Conditionable; +use Illuminate\Support\Traits\Dumpable; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use Illuminate\Support\ViewErrorBag; @@ -33,10 +34,17 @@ */ class TestResponse implements ArrayAccess { - use Concerns\AssertsStatusCodes, Conditionable, Tappable, Macroable { + use Concerns\AssertsStatusCodes, Conditionable, Dumpable, Tappable, Macroable { __call as macroCall; } + /** + * The original request. + * + * @var \Illuminate\Http\Request|null + */ + public $baseRequest; + /** * The response to delegate to. * @@ -62,11 +70,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 +84,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); } /** @@ -1205,26 +1216,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 + ); + } } } } @@ -1458,18 +1471,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. * 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/Testing/composer.json b/src/Illuminate/Testing/composer.json index 1c26f5dd029f..28cd945c18cf 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -14,12 +14,12 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-mbstring": "*", - "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": { @@ -28,14 +28,14 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "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|^10.0.7)." }, 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/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index 450a733ec00e..539a651b2696 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -14,12 +14,12 @@ } ], "require": { - "php": "^8.1", - "illuminate/collections": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0" + "php": "^8.2", + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/support": "^11.0" }, "autoload": { "psr-4": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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 18c70bdf59be..9d33842b8071 100644 --- a/src/Illuminate/Validation/Rules/In.php +++ b/src/Illuminate/Validation/Rules/In.php @@ -3,9 +3,11 @@ namespace Illuminate\Validation\Rules; use BackedEnum; +use Illuminate\Contracts\Support\Arrayable; +use Stringable; use UnitEnum; -class In +class In implements Stringable { /** * The name of the rule. @@ -24,12 +26,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..e6b392c60b1b 100644 --- a/src/Illuminate/Validation/Rules/NotIn.php +++ b/src/Illuminate/Validation/Rules/NotIn.php @@ -3,9 +3,11 @@ namespace Illuminate\Validation\Rules; use BackedEnum; +use Illuminate\Contracts\Support\Arrayable; +use Stringable; use UnitEnum; -class NotIn +class NotIn implements Stringable { /** * The name of the rule. @@ -24,12 +26,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/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/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; diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 611f05092f38..a1f6fe3e6992 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -14,19 +14,19 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-filter": "*", "ext-mbstring": "*", "brick/math": "^0.9.3|^0.10.2|^0.11", "egulias/email-validator": "^3.2.5|^4.0", - "illuminate/collections": "^10.0", - "illuminate/container": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "illuminate/support": "^10.0", - "illuminate/translation": "^10.0", - "symfony/http-foundation": "^6.4", - "symfony/mime": "^6.2" + "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": "^7.0", + "symfony/mime": "^7.0" }, "autoload": { "psr-4": { @@ -35,11 +35,11 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "suggest": { - "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/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 812b9829551a..98025abcd0fd 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -12,9 +12,10 @@ use Illuminate\Support\Traits\Macroable; use IteratorAggregate; use JsonSerializable; +use Stringable; use Traversable; -class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSerializable, Htmlable +class ComponentAttributeBag implements ArrayAccess, Htmlable, JsonSerializable, 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/Engines/CompilerEngine.php b/src/Illuminate/View/Engines/CompilerEngine.php index c69eab4acb75..95197953c868 100755 --- a/src/Illuminate/View/Engines/CompilerEngine.php +++ b/src/Illuminate/View/Engines/CompilerEngine.php @@ -2,6 +2,7 @@ namespace Illuminate\View\Engines; +use Illuminate\Database\RecordsNotFoundException; use Illuminate\Filesystem\Filesystem; use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\View\Compilers\CompilerInterface; @@ -102,7 +103,9 @@ public function get($path, array $data = []) */ protected function handleViewException(Throwable $e, $obLevel) { - if ($e instanceof HttpException || $e instanceof HttpResponseException) { + if ($e instanceof HttpException || + $e instanceof HttpResponseException || + $e instanceof RecordsNotFoundException) { parent::handleViewException($e, $obLevel); } diff --git a/src/Illuminate/View/InvokableComponentVariable.php b/src/Illuminate/View/InvokableComponentVariable.php index 4de508fc9545..63d46ce23b66 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 cdc6f8dfb5ba..597fb1ec0d0b 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; diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index db96ce1da7ff..41472bc1cff1 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -14,15 +14,15 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-tokenizer": "*", - "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": { @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "config": { 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/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']); } diff --git a/tests/Auth/AuthenticateMiddlewareTest.php b/tests/Auth/AuthenticateMiddlewareTest.php index bb9caa101e6b..1b40658ace6f 100644 --- a/tests/Auth/AuthenticateMiddlewareTest.php +++ b/tests/Auth/AuthenticateMiddlewareTest.php @@ -209,6 +209,8 @@ protected function authenticate(...$guards) { $request = m::mock(Request::class); + $request->shouldReceive('expectsJson')->andReturn(false); + $nextParam = null; $next = function ($param) use (&$nextParam) { 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..3fe602a38656 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; @@ -40,6 +41,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 +74,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 +203,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 +251,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 +293,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 +333,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]; @@ -414,9 +425,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); @@ -463,6 +472,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); 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 1f9bf5ae8569..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 @@ -25,7 +24,7 @@ public function testGetReturnsNullWhenNotFound() $memcache = $this->getMockBuilder(Memcached::class)->onlyMethods(['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')); } @@ -47,7 +46,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', @@ -115,9 +114,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:'); } } 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/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/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/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); } } 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/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/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 239e91eed71a..fc0744794691 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(); @@ -2215,7 +2312,7 @@ public function testRelationshipTouchOwnersIsNotPropagatedIfNoRelationshipResult $model->touchOwners(); } - public function testModelAttributesAreCastedWhenPresentInCastsArray() + public function testModelAttributesAreCastedWhenPresentInCastsPropertyOrCastsMethod() { $model = new EloquentModelCastingStub; $model->setDateFormat('Y-m-d H:i:s'); @@ -2233,6 +2330,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); @@ -2251,6 +2349,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); @@ -2363,7 +2462,7 @@ public function testModelAttributeCastingFailsOnUnencodableData() $model->getAttributes(); } - public function testModelAttributeCastingWithSpecialFloatValues() + public function testModelAttributeCastingWithFloats() { $model = new EloquentModelCastingStub; @@ -2389,6 +2488,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; @@ -2401,6 +2508,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; @@ -2773,6 +2898,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; @@ -3174,29 +3338,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']; diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 2a7821f31cb0..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() @@ -771,16 +771,6 @@ public function testAddingDouble() $this->assertSame('alter table `users` add `foo` double 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]); - } - public function testAddingDecimal() { $blueprint = new Blueprint('users'); 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/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/DatabaseSchemaBuilderIntegrationTest.php b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php index ce261ab4e94d..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,46 +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->assertContains( - 'table1_name_index', - array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') - ); - } - - 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->assertContains( - 'example_table1_name_index', - array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') - ); - } - public function testDropColumnWithTablePrefix() { $this->db->connection()->setTablePrefix('test_'); diff --git a/tests/Database/DatabaseSchemaBuilderTest.php b/tests/Database/DatabaseSchemaBuilderTest.php index e2619c667447..4f62ef285de8 100755 --- a/tests/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Database/DatabaseSchemaBuilderTest.php @@ -2,9 +2,12 @@ namespace Illuminate\Tests\Database; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Types\Type; use Illuminate\Database\Connection; use Illuminate\Database\Query\Processors\PostgresProcessor; use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Schema\Grammars\Grammar; use LogicException; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -74,16 +77,16 @@ 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); $connection->shouldReceive('usingNativeSchemaOperations')->once()->andReturn(false); $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/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/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/HttpClientTest.php b/tests/Http/HttpClientTest.php index df4f7165373c..437f34cfd0ef 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1491,11 +1491,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() @@ -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([ @@ -2432,11 +2702,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 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 75a0cf5571af..c184540a7070 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); @@ -1077,9 +1074,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/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/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/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'); + } +} 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/Cache/MemcachedCacheLockTestCase.php b/tests/Integration/Cache/MemcachedCacheLockTestCase.php index 8dbbd481f17e..e4fc82dca804 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/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/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/ConfigureCustomDoctrineTypeTest.php b/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php index a11959c828f8..fa177d48eb6b 100644 --- a/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php +++ b/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php @@ -27,7 +27,7 @@ public function testRegisterCustomDoctrineTypesWithNonDefaultDatabaseConnections { $this->assertTrue( DB::connection() - ->getDoctrineSchemaManager() + ->getDoctrineConnection() ->getDatabasePlatform() ->hasDoctrineTypeMappingFor('xml') ); @@ -36,7 +36,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') ); @@ -89,7 +89,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'; } @@ -102,7 +102,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 b8c2289d3961..5ecf225a52ec 100644 --- a/tests/Integration/Database/DBAL/TimestampTypeTest.php +++ b/tests/Integration/Database/DBAL/TimestampTypeTest.php @@ -22,7 +22,7 @@ public function testRegisterTimestampTypeOnConnection() { $this->assertTrue( $this->app['db']->connection() - ->getDoctrineSchemaManager() + ->getDoctrineConnection() ->getDatabasePlatform() ->hasDoctrineTypeMappingFor('timestamp') ); diff --git a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php index 8bbdb68c0271..e9a4fbab68e4 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/Database/DatabaseSchemaBlueprintIntegrationTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php similarity index 81% rename from tests/Database/DatabaseSchemaBlueprintIntegrationTest.php rename to tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 5fd34340606a..3dee06143062 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,13 +117,13 @@ public function testDroppingColumnsWithoutDoctrineWorks() public function testNativeColumnModifyingOnMySql() { - $connection = $this->db->connection(); + $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); $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(); @@ -160,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, ' @@ -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/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index dac1d066c475..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 */ @@ -181,7 +172,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 +385,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 +409,4 @@ class Member extends Model protected $casts = [ 'amount' => Euro::class, ]; - - public function incrementAmount(Euro $amount) - { - $this->increment('amount', $amount->value); - } } diff --git a/tests/Integration/Database/EloquentModelEnumCastingTest.php b/tests/Integration/Database/EloquentModelEnumCastingTest.php index e948703f3e88..cc47ef8bf19a 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_once 'Enums.php'; @@ -270,6 +271,39 @@ public function testFirstOrCreate() $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); + } + public function testCreateOrFirst() { $model1 = EloquentModelEnumCastingUniqueTestModel::createOrFirst([ diff --git a/tests/Integration/Database/EloquentWhereHasTest.php b/tests/Integration/Database/EloquentWhereHasTest.php index 9a9d1e03ac70..2e52257b1a18 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 afterRefreshingDatabase() /** * 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/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'; } 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 16b067eac3ca..62d0717dec65 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 7c2ed52652c2..e3cc3a897093 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 afterRefreshingDatabase() 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 53d70bd8292d..b2ebca20625f 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 540840953218..f1ca3ce4da41 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 afterRefreshingDatabase() diff --git a/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php b/tests/Integration/Database/Postgres/DatabasePostgresConnectionTest.php index 408b79dfe09a..164ccdbb74a5 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 afterRefreshingDatabase() @@ -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 3a8de1329264..b68b206d8d54 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 1315e101de65..7c8911f6c2b6 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 afterRefreshingDatabase() diff --git a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php index 99240f81b665..48f2a0e74d71 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) @@ -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') diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2e79303b0574..18010c5a58bc 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 + ]); } } @@ -182,6 +183,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) { @@ -261,4 +289,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'] + )); + } } diff --git a/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php b/tests/Integration/Database/SqlServer/DatabaseSqlServerConnectionTest.php index 5c3b1725552e..b575a54cf5f2 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 afterRefreshingDatabase() @@ -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 3068dd920280..d95b6b97f98b 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/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/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/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/Http/ThrottleRequestsTest.php b/tests/Integration/Http/ThrottleRequestsTest.php index 6ddd2a0ef51e..78f1df58ea32 100644 --- a/tests/Integration/Http/ThrottleRequestsTest.php +++ b/tests/Integration/Http/ThrottleRequestsTest.php @@ -4,12 +4,14 @@ 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; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Throwable; class ThrottleRequestsTest extends TestCase @@ -116,4 +118,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/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..27674d1e4709 100644 --- a/tests/Integration/Queue/JobChainingTest.php +++ b/tests/Integration/Queue/JobChainingTest.php @@ -14,39 +14,39 @@ use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\Attributes\WithMigration; -use Orchestra\Testbench\TestCase; +#[WithMigration] #[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 +55,8 @@ public function testJobsCanBeChainedOnSuccess() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -65,6 +67,8 @@ public function testJobsCanBeChainedOnSuccessUsingPendingChain() new JobChainingTestSecondJob, ])->dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -76,6 +80,8 @@ public function testJobsCanBeChainedOnSuccessUsingBusFacade() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -87,6 +93,8 @@ public function testJobsCanBeChainedOnSuccessUsingBusFacadeAsArguments() new JobChainingTestSecondJob ); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestFirstJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -97,6 +105,8 @@ public function testJobsChainedOnExplicitDelete() new JobChainingTestSecondJob, ]); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertTrue(JobChainingTestDeletingJob::$ran); $this->assertTrue(JobChainingTestSecondJob::$ran); } @@ -108,6 +118,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 +131,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 +194,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 +208,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 +227,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 +246,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 +262,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 +273,8 @@ public function testChainJobsCanBePrependedWithoutExistingChain() { JobChainAddingPrependingJob::dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); } @@ -248,6 +282,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 +293,8 @@ public function testChainJobsCanBeAppendedWithoutExistingChain() { JobChainAddingAppendingJob::dispatch(); + $this->runQueueWorkerCommand(['--stop-when-empty' => true]); + $this->assertNotNull(JobChainAddingAddedJob::$ranAt); } @@ -274,6 +312,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 +331,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 +360,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 +393,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 +415,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 +434,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 +454,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..6b5f4928538f 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -8,20 +8,28 @@ 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] +#[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 +38,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 +55,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 +70,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 +87,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); } @@ -116,7 +140,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/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/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..d3208b741cfb 100644 --- a/tests/Integration/Queue/RateLimitedTest.php +++ b/tests/Integration/Queue/RateLimitedTest.php @@ -4,25 +4,22 @@ 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; class RateLimitedTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testUnlimitedJobsAreExecuted() { $rateLimiter = $this->app->make(RateLimiter::class); @@ -188,6 +185,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/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..eccbf8433c38 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 */ + #[DataProvider('redisDriverProvider')] + #[RequiresPhpExtension('pcntl')] 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..2b583f416435 100644 --- a/tests/Integration/Queue/ThrottlesExceptionsTest.php +++ b/tests/Integration/Queue/ThrottlesExceptionsTest.php @@ -9,18 +9,13 @@ 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 { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testCircuitIsOpenedForJobErrors() { $this->assertJobWasReleasedImmediately(CircuitBreakerTestJob::class); @@ -105,6 +100,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 +279,7 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptions(2, 10))->by('test')]; + return [(new ThrottlesExceptions(2, 10 * 60))->by('test')]; } } @@ -139,6 +296,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..f035f85c00a2 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() @@ -144,7 +140,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 +164,6 @@ public function handle() public function middleware() { - return [(new ThrottlesExceptionsWithRedis(2, 10))->by($this->key)]; + return [(new ThrottlesExceptionsWithRedis(2, 10 * 60))->by($this->key)]; } } diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index eb259a73fb9b..7904ab781f78 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -9,22 +9,28 @@ 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] +#[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 +39,7 @@ public function testUniqueJobsAreNotDispatched() Bus::assertDispatchedTimes(UniqueTestJob::class); UniqueTestJob::dispatch(); + $this->runQueueWorkerCommand(['--once' => true]); Bus::assertDispatchedTimes(UniqueTestJob::class); $this->assertFalse( @@ -44,6 +51,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 +64,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 +73,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 +95,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 +116,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()); @@ -133,7 +132,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)).':'; } } @@ -169,8 +168,6 @@ class UniqueTestReleasedJob extends UniqueTestFailJob { public $tries = 1; - public $connection = 'database'; - public function handle() { static::$handled = true; @@ -182,13 +179,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..7d1d5ef12121 100644 --- a/tests/Integration/Queue/WorkCommandTest.php +++ b/tests/Integration/Queue/WorkCommandTest.php @@ -7,36 +7,39 @@ 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] #[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 +48,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 +62,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 +76,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 +87,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); 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/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/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 9984b34feca6..66dd9a87cda5 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() @@ -260,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'); @@ -287,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) { 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); + } } 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/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() + { + } +} 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 dc82699b9329..03f5551becaa 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -32,18 +32,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']); @@ -53,9 +49,7 @@ public function testFirstWithCallback($collection) $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWithCallbackAndDefault($collection) { $data = new $collection(['foo', 'bar']); @@ -65,9 +59,7 @@ public function testFirstWithCallbackAndDefault($collection) $this->assertSame('default', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWithDefaultAndWithoutCallback($collection) { $data = new $collection; @@ -79,9 +71,7 @@ public function testFirstWithDefaultAndWithoutCallback($collection) $this->assertSame('foo', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) { $collection = new $collection([ @@ -94,9 +84,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); @@ -109,9 +97,7 @@ public function testSoleThrowsExceptionIfNoItemsExist($collection) $collection->where('name', 'INVALID')->sole(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) { $this->expectExceptionObject(new MultipleItemsFoundException(2)); @@ -125,9 +111,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) $collection->where('name', 'foo')->sole(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection) { $data = new $collection(['foo', 'bar', 'baz']); @@ -137,9 +121,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback( $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -151,9 +133,7 @@ public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($collection) { $this->expectExceptionObject(new MultipleItemsFoundException(2)); @@ -165,9 +145,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($coll }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailReturnsFirstItemInCollection($collection) { $collection = new $collection([ @@ -180,9 +158,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); @@ -195,9 +171,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection) $collection->where('name', 'INVALID')->firstOrFail(); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection) { $collection = new $collection([ @@ -209,9 +183,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']); @@ -221,9 +193,7 @@ public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCa $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -235,9 +205,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collec }); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection) { $data = new $collection(['foo', 'bar', 'bar']); @@ -250,9 +218,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCa ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstOrFailStopsIteratingAtFirstMatch($collection) { $data = new $collection([ @@ -272,9 +238,7 @@ function () { })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testFirstWhere($collection) { $data = new $collection([ @@ -293,9 +257,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']); @@ -305,9 +267,7 @@ public function testLastReturnsLastItemInCollection($collection) $this->assertNull($c->last()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithCallback($collection) { $data = new $collection([100, 200, 300]); @@ -327,9 +287,7 @@ public function testLastWithCallback($collection) $this->assertNull($result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithCallbackAndDefault($collection) { $data = new $collection(['foo', 'bar']); @@ -345,9 +303,7 @@ public function testLastWithCallbackAndDefault($collection) $this->assertSame('bar', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testLastWithDefaultAndWithoutCallback($collection) { $data = new $collection; @@ -393,9 +349,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 @@ -452,9 +406,7 @@ public function testSliding($collection) $this->assertInstanceOf($collection, $chunks->skip(1)->first()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEmptyCollectionIsEmpty($collection) { $c = new $collection; @@ -462,9 +414,7 @@ public function testEmptyCollectionIsEmpty($collection) $this->assertTrue($c->isEmpty()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEmptyCollectionIsNotEmpty($collection) { $c = new $collection(['foo', 'bar']); @@ -473,9 +423,7 @@ public function testEmptyCollectionIsNotEmpty($collection) $this->assertTrue($c->isNotEmpty()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionIsConstructed($collection) { $data = new $collection('foo'); @@ -494,9 +442,7 @@ public function testCollectionIsConstructed($collection) $this->assertEmpty($data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollectionShuffleWithSeed($collection) { $data = new $collection(range(0, 100, 10)); @@ -507,9 +453,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]); @@ -521,9 +465,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]); @@ -559,9 +501,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]); @@ -597,9 +537,7 @@ public function testSkipWhile($collection) $this->assertSame([3, 3, 4, 4], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetArrayableItems($collection) { $data = new $collection; @@ -637,9 +575,7 @@ public function testGetArrayableItems($collection) $this->assertSame(['foo' => 'bar'], $array); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testToArrayCallsToArrayOnEachItemInCollection($collection) { $item1 = m::mock(Arrayable::class); @@ -664,9 +600,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); @@ -679,9 +613,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(); @@ -690,9 +622,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(); @@ -813,18 +743,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']); @@ -837,9 +763,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([ @@ -849,9 +773,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']); @@ -877,9 +799,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()); @@ -894,18 +814,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']]); @@ -925,9 +841,7 @@ public function testFilter($collection) $this->assertEquals([1, 2, 3], $c->filter()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderKeyBy($collection) { $c = new $collection([ @@ -938,9 +852,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([ @@ -951,9 +863,7 @@ public function testHigherOrderUnique($collection) $this->assertCount(1, $c->unique->id); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderFilter($collection) { $c = new $collection([ @@ -980,9 +890,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]]); @@ -1120,9 +1028,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']]); @@ -1133,9 +1039,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]); @@ -1144,9 +1048,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]]); @@ -1155,18 +1057,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]]); @@ -1174,18 +1072,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']]); @@ -1194,18 +1088,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']]); @@ -1223,9 +1113,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]]); @@ -1236,9 +1124,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]]); @@ -1248,9 +1134,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 @@ -1286,9 +1170,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 @@ -1303,9 +1185,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 @@ -1317,54 +1197,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']]); @@ -1374,18 +1242,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']); @@ -1398,9 +1262,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']); @@ -1422,18 +1284,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']]); @@ -1443,9 +1301,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']]); @@ -1455,45 +1311,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']); @@ -1503,27 +1349,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']); @@ -1531,9 +1371,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']); @@ -1544,9 +1382,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']); @@ -1554,9 +1390,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']); @@ -1567,9 +1401,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(); @@ -1589,9 +1421,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']]; @@ -1604,9 +1434,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']]; @@ -1616,9 +1444,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(); @@ -1638,9 +1464,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']); @@ -1661,9 +1485,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']]); @@ -1696,27 +1518,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']); @@ -1724,9 +1540,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']); @@ -1734,9 +1548,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']); @@ -1744,9 +1556,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']); @@ -1755,9 +1565,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']); @@ -1765,9 +1573,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']); @@ -1776,18 +1582,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]); @@ -1797,9 +1599,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']); @@ -1809,9 +1609,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([ @@ -1844,9 +1642,7 @@ public function testUniqueWithCallback($collection) })->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUniqueStrict($collection) { $c = new $collection([ @@ -1876,27 +1672,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(', ')); @@ -1910,9 +1700,7 @@ public function testJoin($collection) $this->assertSame('', (new $collection([]))->join(', ', ' and ')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCrossJoin($collection) { // Cross join with an array @@ -1942,9 +1730,7 @@ public function testCrossJoin($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSort($collection) { $data = (new $collection([5, 3, 1, 2, 4]))->sort(); @@ -1963,9 +1749,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(); @@ -1984,9 +1768,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) { @@ -2000,9 +1782,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']); @@ -2020,9 +1800,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']]); @@ -2036,9 +1814,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]]); @@ -2047,9 +1823,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']); @@ -2077,9 +1851,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']); @@ -2087,9 +1859,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']); @@ -2097,9 +1867,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']); @@ -2107,9 +1875,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']); @@ -2123,18 +1889,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]); @@ -2147,9 +1909,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]); @@ -2160,9 +1920,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]); @@ -2173,9 +1931,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]); @@ -2189,9 +1945,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'])) @@ -2206,9 +1960,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])) @@ -2225,9 +1977,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([]); @@ -2257,9 +2007,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']); @@ -2274,18 +2022,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']]); @@ -2293,9 +2037,7 @@ public function testPluckWithArrayAndObjectValues($collection) $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckWithArrayAccessValues($collection) { $data = new $collection([ @@ -2307,9 +2049,7 @@ public function testPluckWithArrayAccessValues($collection) $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPluckWithDotNotation($collection) { $data = new $collection([ @@ -2330,9 +2070,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([ @@ -2345,9 +2083,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']); @@ -2358,9 +2094,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']); @@ -2374,9 +2108,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']]); @@ -2404,9 +2136,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']); @@ -2459,9 +2189,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]); @@ -2509,9 +2237,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; @@ -2525,9 +2251,7 @@ public function testRandomOnEmptyCollection($collection) $this->assertCount(0, $random); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeLast($collection) { $data = new $collection(['taylor', 'dayle', 'shawn']); @@ -2535,9 +2259,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]); @@ -2547,9 +2269,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]); @@ -2561,9 +2281,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]); @@ -2579,9 +2297,7 @@ public function testTakeUntilReturnsAllItemsForUnmetValue($collection) $this->assertSame($data->toArray(), $actual->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeUntilCanBeProxied($collection) { $data = new $collection([ @@ -2597,9 +2313,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]); @@ -2609,9 +2323,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]); @@ -2623,9 +2335,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]); @@ -2641,9 +2351,7 @@ public function testTakeWhileReturnsNoItemsForUnmetValue($collection) $this->assertSame([], $actual->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTakeWhileCanBeProxied($collection) { $data = new $collection([ @@ -2660,9 +2368,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 @@ -2679,9 +2385,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) { @@ -2697,18 +2401,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); @@ -2718,9 +2418,7 @@ public function testMakeMethodFromNull($collection) $this->assertEquals([], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMakeMethodFromCollection($collection) { $firstCollection = $collection::make(['foo' => 'bar']); @@ -2728,72 +2426,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'])); @@ -2801,34 +2483,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(); @@ -2836,9 +2510,7 @@ public function testEmptyMethod($collection) $this->assertCount(0, $collection->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testTimesMethod($collection) { $two = $collection::times(2, function ($number) { @@ -2861,9 +2533,7 @@ public function testTimesMethod($collection) $this->assertEquals(range(1, 5), $range->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRangeMethod($collection) { $this->assertSame( @@ -2897,9 +2567,7 @@ public function testRangeMethod($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMakeFromObject($collection) { $object = new stdClass; @@ -2908,18 +2576,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); @@ -2929,9 +2593,7 @@ public function testConstructMethodFromNull($collection) $this->assertEquals([], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConstructMethodFromCollection($collection) { $firstCollection = new $collection(['foo' => 'bar']); @@ -2939,18 +2601,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; @@ -3000,9 +2658,7 @@ public function testSplice() $this->assertEquals(['foo', 'bar', 'baz'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGetPluckValueWithAccessors($collection) { $model = new TestAccessorEloquentTestStub(['some' => 'foo']); @@ -3012,9 +2668,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]); @@ -3031,9 +2685,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']]); @@ -3055,9 +2707,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([ @@ -3070,9 +2720,7 @@ public function testFlatMap($collection) $this->assertEquals(['programming', 'basketball', 'music', 'powerlifting'], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapToDictionary($collection) { $data = new $collection([ @@ -3091,9 +2739,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]); @@ -3105,9 +2751,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([ @@ -3126,9 +2770,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]); @@ -3141,9 +2783,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([ @@ -3160,9 +2800,7 @@ public function testMapWithKeys($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysIntegerKeys($collection) { $data = new $collection([ @@ -3179,9 +2817,7 @@ public function testMapWithKeysIntegerKeys($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysMultipleRows($collection) { $data = new $collection([ @@ -3205,9 +2841,7 @@ public function testMapWithKeysMultipleRows($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysCallbackKey($collection) { $data = new $collection([ @@ -3224,9 +2858,7 @@ public function testMapWithKeysCallbackKey($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapInto($collection) { $data = new $collection([ @@ -3239,9 +2871,7 @@ public function testMapInto($collection) $this->assertSame('second', $data->get(1)->value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testNth($collection) { $data = new $collection([ @@ -3266,9 +2896,7 @@ public function testNth($collection) $this->assertEquals(['e'], $data->nth(2, -2)->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMapWithKeysOverwritingKeys($collection) { $data = new $collection([ @@ -3297,9 +2925,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']]); @@ -3311,9 +2937,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 = [ @@ -3335,9 +2959,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']]); @@ -3359,9 +2981,7 @@ public function sortByUrl(array $value) return $value['url']; } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByAttributeWithBackedEnumKey($collection) { $data = new $collection([ @@ -3373,9 +2993,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']]); @@ -3390,9 +3008,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']]); @@ -3404,9 +3020,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']]); @@ -3423,9 +3037,7 @@ public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($colle $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveMultipleGroups($collection) { $data = new $collection([ @@ -3455,9 +3067,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroups($collection) $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($collection) { $data = new $collection([ @@ -3487,9 +3097,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($co $this->assertEquals($expected_result, $result->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testGroupByMultiLevelAndClosurePreservingKeys($collection) { $data = new $collection([ @@ -3532,9 +3140,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']]); @@ -3548,9 +3154,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([ @@ -3566,9 +3170,7 @@ public function testKeyByClosure($collection) ], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeyByObject($collection) { $data = new $collection([ @@ -3584,9 +3186,7 @@ public function testKeyByObject($collection) ], $result->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContains($collection) { $c = new $collection([1, 3, 5]); @@ -3645,9 +3245,7 @@ public function testContains($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDoesntContain($collection) { $c = new $collection([1, 3, 5]); @@ -3706,9 +3304,7 @@ public function testDoesntContain($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSome($collection) { $c = new $collection([1, 3, 5]); @@ -3747,9 +3343,7 @@ public function testSome($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testContainsStrict($collection) { $c = new $collection([1, 3, 5, '02']); @@ -3795,9 +3389,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]]); @@ -3808,9 +3400,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]]); @@ -3822,27 +3412,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([ @@ -3909,9 +3493,7 @@ public function testPullReturnsDefault() $this->assertSame('foo', $value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRejectRemovesElementsPassingTruthTest($collection) { $c = new $collection(['foo', 'bar']); @@ -3939,9 +3521,7 @@ public function testRejectRemovesElementsPassingTruthTest($collection) })->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRejectWithoutAnArgumentRemovesTruthyValues($collection) { $data1 = new $collection([ @@ -3962,9 +3542,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']); @@ -3980,9 +3558,7 @@ public function testSearchReturnsIndexOfFirstFoundItem($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSearchInStrictMode($collection) { $c = new $collection([false, 0, 1, [], '']); @@ -3995,9 +3571,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']); @@ -4012,9 +3586,7 @@ public function testSearchReturnsFalseWhenItemIsNotFound($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testKeys($collection) { $c = new $collection(['name' => 'taylor', 'framework' => 'laravel']); @@ -4024,9 +3596,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']); @@ -4102,9 +3672,7 @@ public function testPushWithMultipleItems() $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testZip($collection) { $c = new $collection([1, 2, 3]); @@ -4133,9 +3701,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]); @@ -4155,9 +3721,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]]); @@ -4178,9 +3742,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]]); @@ -4211,9 +3773,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']); @@ -4228,9 +3788,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]]); @@ -4272,9 +3830,7 @@ public function testGettingAvgItemsFromCollection($collection) $this->assertEquals(3, $c->avg('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testJsonSerialize($collection) { $c = new $collection([ @@ -4294,9 +3850,7 @@ public function testJsonSerialize($collection) ], $c->jsonSerialize()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCombineWithArray($collection) { $c = new $collection([1, 2, 3]); @@ -4337,9 +3891,7 @@ public function testCombineWithArray($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCombineWithCollection($collection) { $expected = [ @@ -4355,9 +3907,7 @@ public function testCombineWithCollection($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConcatWithArray($collection) { $expected = [ @@ -4383,9 +3933,7 @@ public function testConcatWithArray($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testConcatWithCollection($collection) { $expected = [ @@ -4413,9 +3961,7 @@ public function testConcatWithCollection($collection) $this->assertSame($expected, $actual); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDump($collection) { $log = new Collection; @@ -4426,14 +3972,12 @@ 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); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduce($collection) { $data = new $collection([1, 2, 3]); @@ -4450,9 +3994,7 @@ public function testReduce($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduceSpread($collection) { $data = new $collection([-1, 0, 1, 2, 3, 4, 5]); @@ -4470,9 +4012,7 @@ public function testReduceSpread($collection) $this->assertEquals(-1, $min); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($collection) { $data = new $collection([1]); @@ -4484,9 +4024,7 @@ public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($ }, null); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($collection) { $this->expectException(InvalidArgumentException::class); @@ -4495,9 +4033,7 @@ public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($ $data->random(4); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipe($collection) { $data = new $collection([1, 2, 3]); @@ -4507,9 +4043,7 @@ public function testPipe($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipeInto($collection) { $data = new $collection([ @@ -4521,9 +4055,7 @@ public function testPipeInto($collection) $this->assertSame($data, $instance->value); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPipeThrough($collection) { $data = new $collection([1, 2, 3]); @@ -4540,9 +4072,7 @@ function ($data) { $this->assertEquals(15, $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianValueWithArrayCollection($collection) { $data = new $collection([1, 2, 2, 4]); @@ -4550,9 +4080,7 @@ public function testMedianValueWithArrayCollection($collection) $this->assertEquals(2, $data->median()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianValueByKey($collection) { $data = new $collection([ @@ -4564,9 +4092,7 @@ public function testMedianValueByKey($collection) $this->assertEquals(2, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianOnCollectionWithNull($collection) { $data = new $collection([ @@ -4578,9 +4104,7 @@ public function testMedianOnCollectionWithNull($collection) $this->assertEquals(2, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEvenMedianCollection($collection) { $data = new $collection([ @@ -4590,9 +4114,7 @@ public function testEvenMedianCollection($collection) $this->assertEquals(1.5, $data->median('foo')); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testMedianOutOfOrderCollection($collection) { $data = new $collection([ @@ -4603,27 +4125,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]); @@ -4631,9 +4147,7 @@ public function testMode($collection) $this->assertEquals([4], $data->mode()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testModeValueByKey($collection) { $data = new $collection([ @@ -4652,108 +4166,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']); @@ -4784,9 +4274,7 @@ public function testSplitCollectionWithADivisibleCount($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionWithAnUndivisableCount($collection) { $data = new $collection(['a', 'b', 'c']); @@ -4803,9 +4291,7 @@ public function testSplitCollectionWithAnUndivisableCount($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionWithCountLessThenDivisor($collection) { $data = new $collection(['a']); @@ -4822,9 +4308,7 @@ public function testSplitCollectionWithCountLessThenDivisor($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionIntoThreeWithCountOfFour($collection) { $data = new $collection(['a', 'b', 'c', 'd']); @@ -4842,9 +4326,7 @@ public function testSplitCollectionIntoThreeWithCountOfFour($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitCollectionIntoThreeWithCountOfFive($collection) { $data = new $collection(['a', 'b', 'c', 'd', 'e']); @@ -4862,9 +4344,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']); @@ -4885,9 +4365,7 @@ public function testSplitCollectionIntoSixWithCountOfTen($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testSplitEmptyCollection($collection) { $data = new $collection; @@ -4904,9 +4382,7 @@ public function testSplitEmptyCollection($collection) ); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderCollectionGroupBy($collection) { $data = new $collection([ @@ -4927,9 +4403,7 @@ public function testHigherOrderCollectionGroupBy($collection) ], $data->groupBy->uppercase()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderCollectionMap($collection) { $person1 = (object) ['name' => 'Taylor']; @@ -4944,9 +4418,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']; @@ -4961,9 +4433,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)); @@ -4976,9 +4446,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']); @@ -4991,9 +4459,7 @@ public function testPartitionCallbackWithKey($collection) $this->assertEquals(['one', 'three'], $odd->values()->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionByKey($collection) { $courses = new $collection([ @@ -5006,9 +4472,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([ @@ -5043,9 +4507,7 @@ public function testPartitionWithOperators($collection) ], $minors->values()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionPreservesKeys($collection) { $courses = new $collection([ @@ -5058,9 +4520,7 @@ public function testPartitionPreservesKeys($collection) $this->assertSame(['b' => ['free' => false]], $premium->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPartitionEmptyCollection($collection) { $data = new $collection; @@ -5070,9 +4530,7 @@ public function testPartitionEmptyCollection($collection) })); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testHigherOrderPartition($collection) { $courses = new $collection([ @@ -5086,9 +4544,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]); @@ -5105,9 +4561,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']); @@ -5127,9 +4581,7 @@ public function testWhen($collection) $this->assertSame(['michael', 'tom'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5143,9 +4595,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']); @@ -5165,9 +4615,7 @@ public function testWhenEmpty($collection) $this->assertSame(['adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5181,9 +4629,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']); @@ -5203,9 +4649,7 @@ public function testWhenNotEmpty($collection) $this->assertSame([], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhenNotEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5219,9 +4663,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']); @@ -5243,9 +4685,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']); @@ -5267,9 +4707,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']); @@ -5289,9 +4727,7 @@ public function testUnless($collection) $this->assertSame(['michael', 'tom'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5305,9 +4741,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']); @@ -5327,9 +4761,7 @@ public function testUnlessEmpty($collection) $this->assertSame([], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5343,9 +4775,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']); @@ -5365,9 +4795,7 @@ public function testUnlessNotEmpty($collection) $this->assertSame(['adam'], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUnlessNotEmptyDefault($collection) { $data = new $collection(['michael', 'tom']); @@ -5381,9 +4809,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']); @@ -5405,9 +4831,7 @@ public function testPutAddsItemToCollection() $this->assertSame(['foo' => 3, 'bar' => ['nested' => 'two']], $data->toArray()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collection) { $data = new $collection; @@ -5416,27 +4840,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']); @@ -5446,9 +4864,7 @@ public function testGetWithCallbackAsDefaultValue($collection) $this->assertEquals('taylor@example.com', $result); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNull($collection) { $data = new $collection([ @@ -5466,9 +4882,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]); @@ -5477,9 +4891,7 @@ public function testWhereNullWithoutKey($collection) ], $collection->whereNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testWhereNotNull($collection) { $data = new $collection($originalData = [ @@ -5500,9 +4912,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]); @@ -5516,9 +4926,7 @@ public function testWhereNotNullWithoutKey($collection) ], $data->whereNotNull()->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testCollect($collection) { $data = $collection::make([ @@ -5536,9 +4944,7 @@ public function testCollect($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testUndot($collection) { $data = $collection::make([ @@ -5572,9 +4978,7 @@ public function testUndot($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testDot($collection) { $data = $collection::make([ @@ -5608,9 +5012,7 @@ public function testDot($collection) ], $data->all()); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForScalar($collection) { $data = $collection::make([1, 2, 3]); @@ -5621,9 +5023,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]); @@ -5634,9 +5034,7 @@ public function testEnsureForObjects($collection) $data->ensure(stdClass::class); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForInheritance($collection) { $data = $collection::make([new \Error, new \Error]); @@ -5647,9 +5045,7 @@ public function testEnsureForInheritance($collection) $data->ensure(\Throwable::class); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testEnsureForMultipleTypes($collection) { $data = $collection::make([new \Error, 123]); @@ -5660,9 +5056,7 @@ public function testEnsureForMultipleTypes($collection) $data->ensure([\Throwable::class, 'int']); } - /** - * @dataProvider collectionClassProvider - */ + #[DataProvider('collectionClassProvider')] public function testPercentageWithFlatCollection($collection) { $collection = new $collection([1, 1, 2, 2, 2, 3]); @@ -5673,9 +5067,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([ @@ -5691,9 +5083,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([ @@ -5706,9 +5096,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/SupportFluentTest.php b/tests/Support/SupportFluentTest.php index 515b4817da10..07c883c55a2a 100755 --- a/tests/Support/SupportFluentTest.php +++ b/tests/Support/SupportFluentTest.php @@ -110,6 +110,29 @@ 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()); + + $fluent = new Fluent(['authors' => ['taylor' => ['products' => ['forge', 'vapour', 'spark']]]]); + $this->assertEquals(['forge', 'vapour', 'spark'], $fluent->collect('authors.taylor.products')->all()); + } } class FluentArrayIteratorStub implements IteratorAggregate diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index b91fa854e43b..3570323c252a 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; @@ -251,6 +252,122 @@ 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'], + ], + ], + ], + 'empty' => [], + ]; + + $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')); + + $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() + { + $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']; @@ -1045,9 +1162,7 @@ public static function providesPregReplaceArrayData() ]; } - /** - * @dataProvider providesPregReplaceArrayData - */ + #[DataProvider('providesPregReplaceArrayData')] public function testPregReplaceArray($pattern, $replacements, $subject, $expectedOutput) { $this->assertSame( @@ -1087,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 @@ -1132,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 diff --git a/tests/Support/SupportServiceProviderTest.php b/tests/Support/SupportServiceProviderTest.php index bceb3046cfdd..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); @@ -39,7 +49,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 +64,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() @@ -105,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 @@ -119,6 +174,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']); } } diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 698ba8bba4ab..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)); @@ -525,6 +518,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())); @@ -1070,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)); @@ -1098,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/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 5cf433fa4973..6672922c9439 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -1268,6 +1268,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); 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 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/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 = [ 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/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); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 6ce65cc2b6d5..2fdc729bec86 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; @@ -1791,7 +1793,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(); @@ -3385,9 +3387,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(); @@ -4283,9 +4284,7 @@ public function testValidateUrlWithProtocols() $this->assertFalse($v->passes()); } - /** - * @dataProvider validUrls - */ + #[DataProvider('validUrls')] public function testValidateUrlWithValidUrls($validUrl) { $trans = $this->getIlluminateArrayTranslator(); @@ -4293,9 +4292,7 @@ public function testValidateUrlWithValidUrls($validUrl) $this->assertTrue($v->passes()); } - /** - * @dataProvider invalidUrls - */ + #[DataProvider('invalidUrls')] public function testValidateUrlWithInvalidUrls($invalidUrl) { $trans = $this->getIlluminateArrayTranslator(); @@ -4567,9 +4564,7 @@ public static function invalidUrls() ]; } - /** - * @dataProvider activeUrlDataProvider - */ + #[DataProvider('activeUrlDataProvider')] public function testValidateActiveUrl($data, $outcome) { $trans = $this->getIlluminateArrayTranslator(); @@ -4907,9 +4902,7 @@ public function testValidateMimeEnforcesPhpCheck() $this->assertTrue($v->passes()); } - /** - * @requires extension fileinfo - */ + #[RequiresPhpExtension('fileinfo')] public function testValidateFile() { $trans = $this->getIlluminateArrayTranslator(); @@ -8158,9 +8151,7 @@ public function testMultiplePassesCalls() $this->assertFalse($v->passes()); } - /** - * @dataProvider validUuidList - */ + #[DataProvider('validUuidList')] public function testValidateWithValidUuid($uuid) { $trans = $this->getIlluminateArrayTranslator(); @@ -8168,9 +8159,7 @@ public function testValidateWithValidUuid($uuid) $this->assertTrue($v->passes()); } - /** - * @dataProvider invalidUuidList - */ + #[DataProvider('invalidUuidList')] public function testValidateWithInvalidUuid($uuid) { $trans = $this->getIlluminateArrayTranslator(); @@ -8448,9 +8437,7 @@ public static function providesPassingExcludeIfData() ]; } - /** - * @dataProvider providesPassingExcludeIfData - */ + #[DataProvider('providesPassingExcludeIfData')] public function testExcludeIf($rules, $data, $expectedValidatedData) { $validator = new Validator( @@ -8563,9 +8550,7 @@ public static function providesFailingExcludeIfData() ]; } - /** - * @dataProvider providesFailingExcludeIfData - */ + #[DataProvider('providesFailingExcludeIfData')] public function testExcludeIfWhenValidationFails($rules, $data, $expectedMessages) { $validator = new Validator( @@ -8605,9 +8590,7 @@ public static function providesPassingExcludeData() ]; } - /** - * @dataProvider providesPassingExcludeData - */ + #[DataProvider('providesPassingExcludeData')] public function testExclude($rules, $data, $expectedValidatedData) { $validator = new Validator( @@ -9181,7 +9164,7 @@ public function testItTrimsSpaceFromParameters() ], $validator->messages()->keys()); } - /** @dataProvider outsideRangeExponents */ + #[DataProvider('outsideRangeExponents')] public function testItLimitsLengthOfScientificNotationExponent($value) { $trans = $this->getIlluminateArrayTranslator(); @@ -9205,7 +9188,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(); 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) {