diff --git a/.gitattributes b/.gitattributes index 7e0ca4441814..ba7452152c0d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,9 @@ *.md diff=markdown *.php diff=php +*.stub linguist-language=php +*.neon.dist linguist-language=neon + /.github export-ignore /bin export-ignore /tests export-ignore diff --git a/.github/workflows/databases-nightly.yml b/.github/workflows/databases-nightly.yml index 0c08e2dd8f03..059c35ce1321 100644 --- a/.github/workflows/databases-nightly.yml +++ b/.github/workflows/databases-nightly.yml @@ -36,7 +36,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -82,7 +82,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 834166049942..961af6983a22 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -40,7 +40,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -87,7 +87,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -133,7 +133,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -180,7 +180,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -229,7 +229,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -276,7 +276,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -293,53 +293,53 @@ jobs: DB_USERNAME: SA DB_PASSWORD: Forge123 - mssql_2017: - runs-on: ubuntu-20.04 - timeout-minutes: 5 - - services: - sqlsrv: - image: mcr.microsoft.com/mssql/server:2017-latest - env: - ACCEPT_EULA: Y - SA_PASSWORD: Forge123 - ports: - - 1433:1433 - - strategy: - fail-fast: true - - name: SQL Server 2017 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr - tools: composer:v2 - coverage: none - - - name: Set Framework version - run: composer config version "11.x-dev" - - - name: Install dependencies - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - - name: Execute tests - run: vendor/bin/phpunit tests/Integration/Database - env: - DB_CONNECTION: sqlsrv - DB_DATABASE: master - DB_USERNAME: SA - DB_PASSWORD: Forge123 + # mssql_2017: + # runs-on: ubuntu-20.04 + # timeout-minutes: 5 + + # services: + # sqlsrv: + # image: mcr.microsoft.com/mssql/server:2017-latest + # env: + # ACCEPT_EULA: Y + # SA_PASSWORD: Forge123 + # ports: + # - 1433:1433 + + # strategy: + # fail-fast: true + + # name: SQL Server 2017 + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Setup PHP + # uses: shivammathur/setup-php@v2 + # with: + # php-version: 8.3 + # extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr + # tools: composer:v2 + # coverage: none + + # - name: Set Framework version + # run: composer config version "12.x-dev" + + # - name: Install dependencies + # uses: nick-fields/retry@v3 + # with: + # timeout_minutes: 5 + # max_attempts: 5 + # command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + # - name: Execute tests + # run: vendor/bin/phpunit tests/Integration/Database + # env: + # DB_CONNECTION: sqlsrv + # DB_DATABASE: master + # DB_USERNAME: SA + # DB_PASSWORD: Forge123 sqlite: runs-on: ubuntu-24.04 @@ -363,7 +363,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index c163a9bd375b..3a3bf57c2f06 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -32,7 +32,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index d5dc81d7ef9a..3df36c7d6c82 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -29,7 +29,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -64,7 +64,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -112,7 +112,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -150,7 +150,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -178,7 +178,16 @@ jobs: beanstalkd: runs-on: ubuntu-24.04 - name: Beanstalkd Driver + strategy: + fail-fast: true + matrix: + include: + - php: 8.2 + pheanstalk: 5 + - php: 8.3 + pheanstalk: 7 + + name: Beanstalkd Driver (pda/pheanstalk:^${{ matrix.pheanstalk }}) steps: - name: Checkout code @@ -194,20 +203,20 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, :php-psr tools: composer:v2 coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress --with="pda/pheanstalk:^${{ matrix.pheanstalk }}" - name: Daemonize beanstalkd run: ./beanstalkd-1.13/beanstalkd & diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 76e5aa646e7f..da698e647f91 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -30,7 +30,7 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 @@ -40,4 +40,4 @@ jobs: command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - name: Execute type checking - run: vendor/bin/phpstan --configuration="phpstan.${{ matrix.directory }}.neon.dist" + run: vendor/bin/phpstan --configuration="phpstan.${{ matrix.directory }}.neon.dist" --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70b4ff968674..2a156dd33351 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,11 +40,13 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3, 8.4] - phpunit: ['10.5.35', '11.3.2', '12.0.0'] + phpunit: ['10.5.35', '11.5.3', '12.0.0', '12.1.0'] stability: [prefer-lowest, prefer-stable] exclude: - php: 8.2 phpunit: '12.0.0' + - php: 8.2 + phpunit: '12.1.0' name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} @@ -65,30 +67,14 @@ jobs: REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev - name: Set Framework version - run: composer config version "11.x-dev" - - - name: Set Minimum PHP 8.4 Versions - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require nesbot/carbon:^3.4 --no-interaction --no-update - shell: bash - if: matrix.php >= 8.4 - - - name: Set PHPUnit - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 - command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --with="phpunit/phpunit:~${{ matrix.phpunit }}" - name: Execute tests run: vendor/bin/phpunit --display-deprecation ${{ matrix.stability == 'prefer-stable' && '--fail-on-deprecation' || '' }} @@ -115,11 +101,13 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3, 8.4] - phpunit: ['10.5', '11.0.1'] + phpunit: ['10.5.35', '11.5.3', '12.0.0', '12.1.0'] stability: [prefer-lowest, prefer-stable] exclude: - - php: 8.4 - stability: prefer-lowest + - php: 8.2 + phpunit: '12.0.0' + - php: 8.2 + phpunit: '12.1.0' name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} - Windows @@ -141,22 +129,14 @@ jobs: coverage: none - name: Set Framework version - run: composer config version "11.x-dev" - - - name: Set PHPUnit - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update - shell: bash + run: composer config version "12.x-dev" - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 - command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --with="phpunit/phpunit:~${{ matrix.phpunit }}" - name: Execute tests run: vendor/bin/phpunit diff --git a/.github/workflows/update-assets.yml b/.github/workflows/update-assets.yml new file mode 100644 index 000000000000..8ecd63efce0a --- /dev/null +++ b/.github/workflows/update-assets.yml @@ -0,0 +1,31 @@ +name: 'update assets' + +on: + push: + branches: + - '12.x' + paths: + - '/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json' + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Update Exception Renderer Assets + run: | + npm ci --prefix "./src/Illuminate/Foundation/resources/exceptions/renderer" + npm run build --prefix "./src/Illuminate/Foundation/resources/exceptions/renderer" + + - name: Commit Compiled Files + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: Update Assets diff --git a/.styleci.yml b/.styleci.yml index 079b642b8987..05af66632431 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,8 +1,6 @@ php: preset: laravel version: 8.2 - enabled: - - nullable_type_declarations finder: not-name: - bad-syntax-strategy.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 901a0c1ec378..9ca39ba4620a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1412 +1,413 @@ -# Release Notes for 11.x - -## [Unreleased](https://github.com/laravel/framework/compare/v11.44.0...11.x) - -## [v11.44.0](https://github.com/laravel/framework/compare/v11.43.2...v11.44.0) - 2025-02-24 - +# Release Notes for 12.x + +## [Unreleased](https://github.com/laravel/framework/compare/v12.11.1...12.x) + +## [v12.11.1](https://github.com/laravel/framework/compare/v12.11.0...v12.11.1) - 2025-04-30 + +* Revert "[12.x]`ScheduledTaskFailed` not dispatched on scheduled task failing" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55612 +* [12.x] Resolve issue with BelongsToManyRelationship factory by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/55608 + +## [v12.11.0](https://github.com/laravel/framework/compare/v12.10.2...v12.11.0) - 2025-04-29 + +* Add payload creation and original delay info to job payload by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55529 +* Add config option to ignore view cache timestamps by [@pizkaz](https://github.com/pizkaz) in https://github.com/laravel/framework/pull/55536 +* [12.x] Dispatch NotificationFailed when sending fails by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55507 +* [12.x] Option to disable dispatchAfterResponse in a test by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55456 +* [12.x] Pass flags to custom Json::$encoder by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55548 +* [12.x] Use pendingAttributes of relationships when creating relationship models via model factories by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55558 +* [12.x] Fix double query in model relation serialization by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55547 +* [12.x] Improve circular relation check in Automatic Relation Loading by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55542 +* [12.x] Prevent relation autoload context from being serialized by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55582 +* Remove `@internal` Annotation from `$components` Property in `InteractsWithIO` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/55580 +* Ensure fake job implements job contract by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55574 +* [12.x] Fix `AnyOf` constructor parameter type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/55577 +* Sync changes to Illuminate components before release by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/55591 +* [12.x] Set class-string generics on `Enum` rule by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55588 +* [12.x] added detailed doc types to bindings related methods by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55576 +* [12.x] Improve [@use](https://github.com/use) directive to support function and const modifiers by [@rodolfosrg](https://github.com/rodolfosrg) in https://github.com/laravel/framework/pull/55583 +* 12.x scheduled task failed not dispatched on scheduled task failing by [@achrafAa](https://github.com/achrafAa) in https://github.com/laravel/framework/pull/55572 +* [12.x] Introduce Reflector methods for accessing class attributes by [@daniser](https://github.com/daniser) in https://github.com/laravel/framework/pull/55568 +* [12.x] Typed getters for Arr helper by [@tibbsa](https://github.com/tibbsa) in https://github.com/laravel/framework/pull/55567 + +## [v12.10.2](https://github.com/laravel/framework/compare/v12.10.1...v12.10.2) - 2025-04-24 + +* [12.x] Address Model@relationLoaded when relation is null by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55531 + +## [v12.10.1](https://github.com/laravel/framework/compare/v12.10.0...v12.10.1) - 2025-04-23 + +* Revert "Use value() helper in 'when' method to simplify code" #55465 by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55514 +* [12.x] Use xxh128 when comparing views for changes by [@shawnlindstrom](https://github.com/shawnlindstrom) in https://github.com/laravel/framework/pull/55517 +* [12.x] Ensure related models is iterable on `HasRelationships@relationLoaded()` by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/55519 +* [12.x] Add Enum support for assertJsonPath in AssertableJsonString.php by [@azim-kordpour](https://github.com/azim-kordpour) in https://github.com/laravel/framework/pull/55516 + +## [v12.10.0](https://github.com/laravel/framework/compare/v12.9.2...v12.10.0) - 2025-04-22 + +* Use value() helper in 'when' method by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55465 +* [12.x] Test `@use` directive without quotes by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/55462 +* [12.x] Enhance Broadcast Events Test Coverage by [@roshandelpoor](https://github.com/roshandelpoor) in https://github.com/laravel/framework/pull/55458 +* [12.x] Add `Conditionable` Trait to `Fluent` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/55455 +* [12.x] Fix relation auto loading with manually set relations by [@patrickweh](https://github.com/patrickweh) in https://github.com/laravel/framework/pull/55452 +* Add missing types to RateLimiter by [@ClaudioEyzaguirre](https://github.com/ClaudioEyzaguirre) in https://github.com/laravel/framework/pull/55445 +* [12.x] Fix for global autoload relationships not working in certain cases by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55443 +* [12.x] Fix adding `setTags` method on new cache flush events by [@erikn69](https://github.com/erikn69) in https://github.com/laravel/framework/pull/55405 +* Fix: Unique lock not being released after transaction rollback in ShouldBeUnique jobs with afterCommit() by [@toshitsuna-otsuka](https://github.com/toshitsuna-otsuka) in https://github.com/laravel/framework/pull/55420 +* [12.x] Extends `AsCollection` to map items into objects or other values by [@DarkGhostHunter](https://github.com/DarkGhostHunter) in https://github.com/laravel/framework/pull/55383 +* [12.x] Fix group imports in Blade `@use` directive by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/55461 +* chore(tests): align test names with idiomatic naming style by [@kauffinger](https://github.com/kauffinger) in https://github.com/laravel/framework/pull/55496 +* Update compiled views only if they actually changed by [@pizkaz](https://github.com/pizkaz) in https://github.com/laravel/framework/pull/55450 +* Improve performance of Arr::dot method - 300x in some cases by [@cyppe](https://github.com/cyppe) in https://github.com/laravel/framework/pull/55495 +* [12.x] Add tests for `CacheBasedSessionHandler` by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/55487 +* [12.x] Add tests for `FileSessionHandler` by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/55484 +* [12.x] Add tests for `DatabaseSessionHandler` by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/55485 +* [12.x] Fix many to many detach without IDs broken with custom pivot class by [@amir9480](https://github.com/amir9480) in https://github.com/laravel/framework/pull/55490 +* [12.x] Support nested relations on `relationLoaded` method by [@tmsperera](https://github.com/tmsperera) in https://github.com/laravel/framework/pull/55471 +* Bugfix for Cache::memo()->many() returning the wrong value with an integer key type by [@bmckay959](https://github.com/bmckay959) in https://github.com/laravel/framework/pull/55503 +* [12.x] Allow Container to build `Migrator` from class name by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55501 + +## [v12.9.2](https://github.com/laravel/framework/compare/v12.9.1...v12.9.2) - 2025-04-16 + +* [12.x] Fixed a bug in using `illuminate/console` in external apps by [@andrey-helldar](https://github.com/andrey-helldar) in https://github.com/laravel/framework/pull/55430 +* Disable SQLServer 2017 CI as `ubuntu-20.24` has been removed by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55425 + +## [v12.9.1](https://github.com/laravel/framework/compare/v12.9.0...v12.9.1) - 2025-04-16 + +* [12.x] Forward only passed arguments into Illuminate\Database\Eloquent\Collection::partition method by [@MarekVikartovsky](https://github.com/MarekVikartovsky) in https://github.com/laravel/framework/pull/55422 +* [12.x] Add test for complex context manipulation in Logger by [@roshandelpoor](https://github.com/roshandelpoor) in https://github.com/laravel/framework/pull/55423 +* [12.x] Remove unused var from `DumpCommand` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55431 +* [12.x] Fix the serve command sometimes fails to destructure the request pool array by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/55427 +* [12.x] Changes to `package-lock.json` should trigger `npm run build` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55426 + +## [v12.9.0](https://github.com/laravel/framework/compare/v12.8.1...v12.9.0) - 2025-04-15 + +* Add types to ViewErrorBag by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/55329 +* Add types to MessageBag by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/55327 +* [12.x] add generics to commonly used methods in Schema/Builder by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55330 +* Return frozen time for easier testing by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/55323 +* Enhance DetectsLostConnections to Support AWS Aurora Credential Rotation Scenario by [@msaifmfz](https://github.com/msaifmfz) in https://github.com/laravel/framework/pull/55331 +* [12.x] Rename test method of failedRequest() by [@LKaemmerling](https://github.com/LKaemmerling) in https://github.com/laravel/framework/pull/55332 +* feat: Add a callback to be called on transaction failure by [@dshafik](https://github.com/dshafik) in https://github.com/laravel/framework/pull/55338 +* [12.x] Add withRelationshipAutoloading method to model by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55344 +* [12.x] Enable HTTP client retries when middleware throws an exception by [@27pchrisl](https://github.com/27pchrisl) in https://github.com/laravel/framework/pull/55343 +* [12.x] Fix Closure serialization error in automatic relation loading by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/55345 +* Add test for Unique validation rule with WhereIn constraints by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55351 +* Add [@throws](https://github.com/throws) in doc-blocks by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55361 +* [12.x] Update `propagateRelationAutoloadCallbackToRelation` method doc-block by [@derian-all-win-software](https://github.com/derian-all-win-software) in https://github.com/laravel/framework/pull/55363 +* [12.x] - Redis - Establish connection first, before set the options by [@alexmontoanelli](https://github.com/alexmontoanelli) in https://github.com/laravel/framework/pull/55370 +* [12.x] Fix translation FileLoader overrides with a missing key by [@fabio-ivona](https://github.com/fabio-ivona) in https://github.com/laravel/framework/pull/55342 +* [12.x] Fix pivot model events not working when using the `withPivotValue` by [@amir9480](https://github.com/amir9480) in https://github.com/laravel/framework/pull/55280 +* [12.x] Introduce memoized cache driver by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55304 +* [12.x] Add test for Filesystem::lastModified() method by [@roshandelpoor](https://github.com/roshandelpoor) in https://github.com/laravel/framework/pull/55389 +* [12.x] Supports `pda/pheanstalk` 7 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55397 +* [12.x] Add comprehensive filesystem operation tests to FilesystemTest by [@roshandelpoor](https://github.com/roshandelpoor) in https://github.com/laravel/framework/pull/55399 +* Bump vite from 5.4.17 to 5.4.18 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/55402 +* Add descriptive error messages to assertViewHas() by [@3Descape](https://github.com/3Descape) in https://github.com/laravel/framework/pull/55392 +* Use Generic Types Annotations for LazyCollection Methods by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55380 +* [12.x] Add test coverage for Process sequence with multiple env variables by [@roshandelpoor](https://github.com/roshandelpoor) in https://github.com/laravel/framework/pull/55406 +* [12.x] Fix cc/bcc/replyTo address merging in `MailMessage` by [@onlime](https://github.com/onlime) in https://github.com/laravel/framework/pull/55404 +* [12.x] Add a `make` function in the `Fluent` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/55417 + +## [v12.8.1](https://github.com/laravel/framework/compare/v12.8.0...v12.8.1) - 2025-04-08 + +## [v12.8.0](https://github.com/laravel/framework/compare/v12.7.2...v12.8.0) - 2025-04-08 + +* [12.x] only check for soft deletes once when mass-pruning by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55274 +* [12.x] Add createMany mass-assignment variants to `HasOneOrMany` relation by [@onlime](https://github.com/onlime) in https://github.com/laravel/framework/pull/55262 +* cosmetic: include is_array() case in match construct of getArrayableItems by [@epic-64](https://github.com/epic-64) in https://github.com/laravel/framework/pull/55275 +* Add tests for InvokeSerializedClosureCommand by [@Amirhf1](https://github.com/Amirhf1) in https://github.com/laravel/framework/pull/55281 +* [12.x] Temporarily prevents PHPUnit 12.1 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55297 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55306 +* Bump vite from 5.4.12 to 5.4.17 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/55301 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55307 +* [12.x] add generics to array types for Schema Grammars by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55314 +* [12.x] fix missing nullable for Query/Grammar::compileInsertGetId by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/55311 +* [12.x] Adds `fromJson()` to Collection by [@DarkGhostHunter](https://github.com/DarkGhostHunter) in https://github.com/laravel/framework/pull/55310 +* [12.x] Fix `illuminate/database` usage as standalone package by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/55309 +* Correct array key in InteractsWithInput by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/55287 +* [12.x] Fix support for adding custom observable events from traits by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/55286 +* [12.x] Added Automatic Relation Loading (Eager Loading) Feature by [@litvinchuk](https://github.com/litvinchuk) in https://github.com/laravel/framework/pull/53655 +* [12.x] Modify PHPDoc for Collection::chunkWhile functions to support preserving keys by [@jsvdvis](https://github.com/jsvdvis) in https://github.com/laravel/framework/pull/55324 +* [12.x] Introduce Rule::anyOf() for Validating Against Multiple Rule Sets by [@brianferri](https://github.com/brianferri) in https://github.com/laravel/framework/pull/55191 + +## [v12.7.2](https://github.com/laravel/framework/compare/v12.7.1...v12.7.2) - 2025-04-03 + +## [v12.7.1](https://github.com/laravel/framework/compare/v12.7.0...v12.7.1) - 2025-04-03 + +## [v12.7.0](https://github.com/laravel/framework/compare/v12.6.0...v12.7.0) - 2025-04-03 + +* [12.x] `AbstractPaginator` should implement `CanBeEscapedWhenCastToString` by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55256 +* [12.x] Add `whereAttachedTo()` Eloquent builder method by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/55245 +* Make Illuminate\Support\Uri Macroable by [@riesjart](https://github.com/riesjart) in https://github.com/laravel/framework/pull/55260 +* [12.x] Add resource helper functions to Model/Collections by [@TimKunze96](https://github.com/TimKunze96) in https://github.com/laravel/framework/pull/55107 +* [12.x]: Use char(36) for uuid type on MariaDB < 10.7.0 by [@boedah](https://github.com/boedah) in https://github.com/laravel/framework/pull/55197 +* [12.x] Introducing `toArray` to `ComponentAttributeBag` class by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/55258 + +## [v12.6.0](https://github.com/laravel/framework/compare/v12.5.0...v12.6.0) - 2025-04-02 + +* [12.x] Dont stop pruning if pruning one model fails by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55237 +* [12.x] Update Date Facade Docblocks by [@fdalcin](https://github.com/fdalcin) in https://github.com/laravel/framework/pull/55235 +* Make `db:seed` command prohibitable by [@spawnia](https://github.com/spawnia) in https://github.com/laravel/framework/pull/55238 +* [12.x] Introducing `Rules\Password::appliedRules` Method by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/55206 +* [12.x] Allowing merging model attributes before insert via `Model::fillAndInsert()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55038 +* [12.x] Fix type hints for DateTimeZone and DateTimeInterface on DateFactory by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55243 +* [12.x] Fix DateFactory docblock type hints by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55244 +* List missing `migrate:rollback` in DB::prohibitDestructiveCommands PhpDoc by [@spawnia](https://github.com/spawnia) in https://github.com/laravel/framework/pull/55252 +* [12.x] Add `Http::requestException()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55241 +* New: Uri `pathSegments()` helper method by [@chester-sykes](https://github.com/chester-sykes) in https://github.com/laravel/framework/pull/55250 +* [12.x] Do not require returning a Builder instance from a local scope method by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55246 + +## [v12.5.0](https://github.com/laravel/framework/compare/v12.4.1...v12.5.0) - 2025-04-01 + +* Correct misspellings by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/55218 +* [12.x] Add ability to flush state on Vite helper by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55228 +* [12.x] Support taggeable store flushed cache events by [@erikn69](https://github.com/erikn69) in https://github.com/laravel/framework/pull/55223 +* Revert "[12.x] Support taggeable store flushed cache events" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55232 +* [12.x] Allow configuration of retry period for RoundRobin and Failover mail transports by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/55222 +* [12.x] Add --json option to EventListCommand by [@hotsaucejake](https://github.com/hotsaucejake) in https://github.com/laravel/framework/pull/55207 + +## [v12.4.1](https://github.com/laravel/framework/compare/v12.4.0...v12.4.1) - 2025-03-30 + +* [12.x] Add `Expression` type to param `$value` of `QueryBuilder` `orHaving()` method by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/55202 +* [12.x] Fix URL generation with optional parameters (regression in #54811) by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/55213 +* [12.x] Fix failing tests on windows OS by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/55210 + +## [v12.4.0](https://github.com/laravel/framework/compare/v12.3.0...v12.4.0) - 2025-03-29 + +* [12.x] Reset PHP’s peak memory usage when resetting scope for queue worker by [@TimWolla](https://github.com/TimWolla) in https://github.com/laravel/framework/pull/55069 +* [12.x] Add `AsHtmlString` cast by [@ralphjsmit](https://github.com/ralphjsmit) in https://github.com/laravel/framework/pull/55071 +* [12.x] Add `Arr::sole()` method by [@ralphjsmit](https://github.com/ralphjsmit) in https://github.com/laravel/framework/pull/55070 +* Improve warning message in `ApiInstallCommand` by [@sajjadhossainshohag](https://github.com/sajjadhossainshohag) in https://github.com/laravel/framework/pull/55081 +* [12.x] use already determined `related` property by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55075 +* [12.x] use "class-string" where appropriate in relations by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55074 +* [12.x] `QueueFake::listenersPushed()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55063 +* [12.x] Added except() method to Model class for excluding attributes by [@vishal2931](https://github.com/vishal2931) in https://github.com/laravel/framework/pull/55072 +* [12.x] fix: add TPivotModel default and define pivot property in {Belongs,Morph}ToMany by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/55086 +* [12.x] remove `@return` docblocks on constructors by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55076 +* [12.x] Add NamedScope attribute by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/54450 +* [12.x] Improve syntax highlighting for stub type files by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/55094 +* [12.x] Prefer `new Collection` over `Collection::make` by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/55091 +* [12.x] Fix except() method to support casted values by [@vishal2931](https://github.com/vishal2931) in https://github.com/laravel/framework/pull/55124 +* [12.x] Add testcase for findSole method by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/55115 +* [12.x] Types: PasswordBroker::reset by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/55109 +* [12.x] assertThrowsNothing by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/55100 +* [12.x] Fix type nullability on PasswordBroker.events property by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/55097 +* [12.x] Fix return type annotation in decrementPendingJobs method by [@shane-zeng](https://github.com/shane-zeng) in https://github.com/laravel/framework/pull/55133 +* [12.x] Fix return type annotation in compile method by [@shane-zeng](https://github.com/shane-zeng) in https://github.com/laravel/framework/pull/55132 +* [12.x] feat: Add `whereNull` and `whereNotNull` to `Assertablejson` by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/55131 +* [12.x] fix: use contextual bindings in class dependency resolution by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/55090 +* Better return types for `Illuminate\Queue\Jobs\Job::getJobId()` and `Illuminate\Queue\Jobs\DatabaseJob::getJobId()` methods by [@petrknap](https://github.com/petrknap) in https://github.com/laravel/framework/pull/55138 +* Remove remaining [@return](https://github.com/return) tags from constructors by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55136 +* [12.x] Various URL generation bugfixes by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/54811 +* Add an optional `shouldRun` method to migrations. by [@danmatthews](https://github.com/danmatthews) in https://github.com/laravel/framework/pull/55011 +* [12.x] `Uri` prevent empty query string by [@rojtjo](https://github.com/rojtjo) in https://github.com/laravel/framework/pull/55146 +* [12.x] Only call the ob_flush function if there is active buffer in eventStream by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/55141 +* [12.x] Add CacheFlushed Event by [@tech-wolf-tw](https://github.com/tech-wolf-tw) in https://github.com/laravel/framework/pull/55142 +* [12.x] Update DateFactory method annotations for Carbon v3 compatibility by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/55151 +* [12.x] Improve docblocks for file related methods of InteractsWithInput by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/55156 +* [12.x] Enhance `FileViewFinder` doc-blocks by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/55183 +* Support using null-safe operator with `null` value by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/55175 +* [12.x] Fix: Make Paginated Queries Consistent Across Pages by [@tomchkk](https://github.com/tomchkk) in https://github.com/laravel/framework/pull/55176 +* [12.x] Add `pipe` method query builders by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55171 +* [12.x] fix: one of many subquery constraints by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/55168 +* [12.x] fix(postgres): missing parentheses in whereDate/whereTime for json columns by [@saibotk](https://github.com/saibotk) in https://github.com/laravel/framework/pull/55159 +* Fix factory creation through attributes by [@davidstoker](https://github.com/davidstoker) in https://github.com/laravel/framework/pull/55190 +* [12.x] Fix Concurrency::run to preserve callback result order by [@chaker2710](https://github.com/chaker2710) in https://github.com/laravel/framework/pull/55161 +* [12.x] Log: Add optional keys parameter to `Log::withoutContext` to remove selected context from future logs by [@mattroylloyd](https://github.com/mattroylloyd) in https://github.com/laravel/framework/pull/55181 +* [12.x] Add `Expression` type to param `$value` of `QueryBuilder` `having()` method by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/55200 +* [12.x] Add flag to disable where clauses for `withAttributes` method on Eloquent Builder by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55199 + +## [v12.3.0](https://github.com/laravel/framework/compare/v12.2.0...v12.3.0) - 2025-03-18 + +* [12.x] fixes https://github.com/laravel/octane/issues/1010 by [@mihaileu](https://github.com/mihaileu) in https://github.com/laravel/framework/pull/55008 +* Added the missing 'trashed' event to getObservablesEvents() by [@duemti](https://github.com/duemti) in https://github.com/laravel/framework/pull/55004 +* [12.x] Enhance PHPDoc for Manager classes with `@param-closure-this` by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/55002 +* [12.x] Fix `PendingRequest` typehints for `post`, `patch`, `put`, `delete` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54998 +* [12.x] Add test for untested methods in LazyCollection by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54996 +* [12.x] fix indentation by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54995 +* [12.x] apply final Pint fixes by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55014 +* Enhance validation tests: Add test for connection name detection in Unique rule by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54993 +* [12.x] Add json:unicode cast to support JSON_UNESCAPED_UNICODE encoding by [@fuwasegu](https://github.com/fuwasegu) in https://github.com/laravel/framework/pull/54992 +* [12.x] Add “Storage Linked” to the `about` command by [@adampatterson](https://github.com/adampatterson) in https://github.com/laravel/framework/pull/54949 +* [12.x] Add support for native JSON/JSONB column types in SQLite Schema builder by [@fuwasegu](https://github.com/fuwasegu) in https://github.com/laravel/framework/pull/54991 +* [12.x] Fix `LogManager::configurationFor()` typehint by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/55016 +* [12.x] Add missing tests for LazyCollection methods by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/55022 +* [12.x] Refactor: Structural improvement for clarity by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/55018 +* Improve `toKilobytes` to handle spaces and case-insensitive units by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/55019 +* [12.x] Fix mistake in `asJson` call in `HasAttributes.php` that was recently introduced by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55017 +* [12.x] reapply Pint style changes by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55015 +* Add validation test for forEach with null and empty array values by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/55047 +* [12.x] Types: EnumeratesValues Sum by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/55044 +* [12.x] Ensure Consistent Formatting in Generated Invokable Classes by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/55034 +* Add element type to return array in Filesystem by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/55031 +* [12.x] Add support for PostgreSQL "unique nulls not distinct" by [@thierry2015](https://github.com/thierry2015) in https://github.com/laravel/framework/pull/55025 +* [12.x] standardize multiline ternaries by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55056 +* [12.x] improved readability for `aliasedPivotColumns` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55055 +* [12.x] remove progress bar from PHPStan output by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55054 +* [12.x] Fixes how the fluent Date rule builder handles `date_format` by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/55052 +* Adding SSL encryption and support for MySQL connection by [@mdiktushar](https://github.com/mdiktushar) in https://github.com/laravel/framework/pull/55048 +* Revert "Adding SSL encryption and support for MySQL connection" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/55057 +* Ensure queue property is nullable by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/55058 +* [12.x] return `$this` for chaining by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55060 +* [12.x] prefer `new Collection` over `collect()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55059 +* [12.x] use "class-string" type for `using` pivot model by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55053 +* [12.x] multiline chaining on Collections by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/55061 + +## [v12.2.0](https://github.com/laravel/framework/compare/v12.1.1...v12.2.0) - 2025-03-12 + +* Add dates to allowed PHPDoc types of Builder::having() by [@miken32](https://github.com/miken32) in https://github.com/laravel/framework/pull/54899 +* [11.x] Fix double negative in `whereNotMorphedTo()` query by [@owenvoke](https://github.com/owenvoke) in https://github.com/laravel/framework/pull/54902 +* Add test for Arr::partition by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54913 +* [11.x] Expose process checkTimeout method by [@mattmcdev](https://github.com/mattmcdev) in https://github.com/laravel/framework/pull/54912 +* [12.x] Compilable for Validation Contract by [@peterfox](https://github.com/peterfox) in https://github.com/laravel/framework/pull/54882 +* [11.x] Backport "Change `paginate()` method return types to `\Illuminate\Pagination\LengthAwarePaginator`" by [@carestad](https://github.com/carestad) in https://github.com/laravel/framework/pull/54917 +* [11.x] Revert faulty change to `EnumeratesValues::ensure()` doc block by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/54919 +* Ensure ValidationEmailRuleTest skips tests requiring the intl extension when unavailable by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54918 +* ✅ Ensure Enum validation is case-sensitive by adding a new test case. by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54922 +* [12.x] Feature: Collection chunk without preserving keys by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54916 +* [12.x] Add test coverage for Uri::withQueryIfMissing method by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54923 +* Fix issue with using RedisCluster with compression or serialization by [@rzv-me](https://github.com/rzv-me) in https://github.com/laravel/framework/pull/54934 +* [12.x] Add test coverage for Str::replaceMatches method by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54930 +* [12.x] Types: Collection chunk without preserving keys by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54924 +* [12.x] Add `ddBody` method to TestResponse for dumping various response payloads by [@Sammyjo20](https://github.com/Sammyjo20) in https://github.com/laravel/framework/pull/54933 +* [11.x] Backport "Fix issue with using `RedisCluster` with compression or serialization" by [@rzv-me](https://github.com/rzv-me) in https://github.com/laravel/framework/pull/54935 +* [12.x] feat: add `CanBeOneOfMany` support to `HasOneThrough` by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/54759 +* [12.x] Hotfix - Add function_exists check to ddBody in TestResponse by [@Sammyjo20](https://github.com/Sammyjo20) in https://github.com/laravel/framework/pull/54937 +* [12.x] Refactor: Remove unnecessary variables in Str class methods by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54963 +* Add Tests for Str::pluralPascal Method by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54957 +* [12.x] Fix visibility of setUp and tearDown in tests by [@naopusyu](https://github.com/naopusyu) in https://github.com/laravel/framework/pull/54950 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54944 +* Fix missing return in `assertOnlyInvalid` by [@parth391](https://github.com/parth391) in https://github.com/laravel/framework/pull/54941 +* Handle case when migrate:install command is called and table exists by [@joe-tito](https://github.com/joe-tito) in https://github.com/laravel/framework/pull/54938 +* [11.x] Fix callOnce in Seeder so it handles arrays properly by [@lbovit](https://github.com/lbovit) in https://github.com/laravel/framework/pull/54985 +* Change "exceptoin" spelling mistake to "exception" by [@hvlucas](https://github.com/hvlucas) in https://github.com/laravel/framework/pull/54979 +* [12.x] Add test for after method in LazyCollection by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54978 +* [12.x] Add `increment` and `decrement` methods to `Context` by [@mattmcdev](https://github.com/mattmcdev) in https://github.com/laravel/framework/pull/54976 +* Ensure ExcludeIf correctly rejects a null value as an invalid condition by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54973 +* [12.x] apply Pint rule "no_spaces_around_offset" by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54970 +* [12.x] apply Pint rule "single_line_comment_style" by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54969 +* [12.x] do not use mix of newline and inline formatting by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54967 +* [12.x] use single indent for multiline ternaries by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54971 + +## [v12.1.1](https://github.com/laravel/framework/compare/v12.1.0...v12.1.1) - 2025-03-05 + +* [11.x] Add valid values to ensure method by [@lancepioch](https://github.com/lancepioch) in https://github.com/laravel/framework/pull/54840 +* Fix attribute name used on `Validator` instance within certain rule classes by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54845 +* [11.x] Fix `Application::interBasePath()` fails to resolve application when project name is "vendor" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54871 +* [11.x] Test improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54879 +* [12.x] DocBlock: Changed typehint for `Arr::partition` method by [@AndrewMast](https://github.com/AndrewMast) in https://github.com/laravel/framework/pull/54896 +* Enhance Email and Image Dimensions Validation Tests by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54897 +* [12.x] Apply default styling rules to the notification stub by [@ahinkle](https://github.com/ahinkle) in https://github.com/laravel/framework/pull/54895 + +## [v12.1.0](https://github.com/laravel/framework/compare/v12.0.1...v12.1.0) - 2025-03-04 + +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54782 +* [12.x] Fix incorrect typehints in `BuildsWhereDateClauses` traits by [@mohprilaksono](https://github.com/mohprilaksono) in https://github.com/laravel/framework/pull/54784 +* [12.x] Improve queries readablility by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54791 +* [12.x] Enhance eventStream to Support Custom Events and Start Messages by [@devhammed](https://github.com/devhammed) in https://github.com/laravel/framework/pull/54776 +* [12.x] Make the PendingCommand class tappable. by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/54801 +* [12.x] Add missing union type in event stream docblock by [@devhammed](https://github.com/devhammed) in https://github.com/laravel/framework/pull/54800 +* Change return types of `paginage()` methods to `\Illuminate\Pagination\LengthAwarePaginator` by [@carestad](https://github.com/carestad) in https://github.com/laravel/framework/pull/54826 +* [12.x] Check if internal `Hasher::verifyConfiguration()` method exists on driver before forwarding call by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/54833 +* [11.x] Fix using `AsStringable` cast on Notifiable's key by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54818 +* Add Tests for Handling Null Primary Keys and Special Values in Unique Validation Rule by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54823 +* Improve docblock for with() method to clarify it adds to existing eag… by [@igorlealantunes](https://github.com/igorlealantunes) in https://github.com/laravel/framework/pull/54838 +* [12.x] Fix dropping schema-qualified prefixed tables by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54834 +* [12.x] Add `Context::scope()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54799 +* Allow Http requests to be recorded without requests being faked by [@kemp](https://github.com/kemp) in https://github.com/laravel/framework/pull/54850 +* [12.x] Adds a new method "getRawSql" (with embedded bindings) to the QueryException class by [@erickcomp](https://github.com/erickcomp) in https://github.com/laravel/framework/pull/54849 +* Update Inspiring.php by [@ju-gow](https://github.com/ju-gow) in https://github.com/laravel/framework/pull/54846 +* [12.x] Correct use of named argument in `Date` facade and fix a return type. by [@lmottasin](https://github.com/lmottasin) in https://github.com/laravel/framework/pull/54847 +* Add additional tests for Rule::array validation scenarios by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54844 +* [12.x] Remove return statement by [@mohprilaksono](https://github.com/mohprilaksono) in https://github.com/laravel/framework/pull/54842 +* Fix typos by [@co63oc](https://github.com/co63oc) in https://github.com/laravel/framework/pull/54839 +* [12.x] Do not loop through middleware when excluded is empty by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54837 +* Add test for Arr::reject method in Illuminate Support by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/laravel/framework/pull/54863 +* [12.x] Feature: Array partition by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54859 +* [12.x] Introduce `ContextLogProcessor` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54851 + +## [v12.0.1](https://github.com/laravel/framework/compare/v12.0.0...v12.0.1) - 2025-02-24 + +## [v12.0.0](https://github.com/laravel/framework/compare/v11.44.0..v12.0.0...v12.0.0) - 2025-02-24 + +* [12.x] Prep Laravel v12 by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50406 +* [12.x] Make `Str::is()` match multiline strings by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/51196 +* [12.x] Use native MariaDB CLI commands by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51505 +* [12.x] Adds missing streamJson() to ResponseFactory contract by [@wilsenhc](https://github.com/wilsenhc) in https://github.com/laravel/framework/pull/51544 +* [12.x] Preserve numeric keys on the first level of the validator rules by [@Tofandel](https://github.com/Tofandel) in https://github.com/laravel/framework/pull/51516 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52248 +* [12.x] mergeIfMissing allows merging with nested arrays by [@KIKOmanasijev](https://github.com/KIKOmanasijev) in https://github.com/laravel/framework/pull/52242 +* [12.x] Fix chunked queries not honoring user-defined limits and offsets by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/52093 +* [12.x] Replace md5 with much faster xxhash by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/52301 +* [12.x] Switch models to UUID v7 by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/52433 +* [12.x] Improved algorithm for Number::pairs() by [@hotmeteor](https://github.com/hotmeteor) in https://github.com/laravel/framework/pull/52641 +* Removed Duplicated Prefix on DynamoDbStore.php by [@felipehertzer](https://github.com/felipehertzer) in https://github.com/laravel/framework/pull/52986 +* [12.x] feat: configure default datetime precision on per-grammar basis by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51821 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53150 +* [12.x] Fix laravel/prompt dependency version constraint for illuminate/console by [@wouterj](https://github.com/wouterj) in https://github.com/laravel/framework/pull/53146 +* [12.x] Add generic return type to Container::instance() by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/53161 +* Map output of concurrecy calls to the index of the input by [@ovp87](https://github.com/ovp87) in https://github.com/laravel/framework/pull/53135 +* Change Composer hasPackage to public by [@buihanh2304](https://github.com/buihanh2304) in https://github.com/laravel/framework/pull/53282 +* [12.x] force `Eloquent\Collection::partition` to return a base `Collection` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53304 +* [12.x] Better support for multi-dbs in the `RefreshDatabase` trait by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/53231 +* [12.x] Validate UUID's version optionally by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53341 +* [12.x] Validate UUID version 2 and max by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53368 +* [12.x] Add step parameter to LazyCollection range method by [@Ashot1995](https://github.com/Ashot1995) in https://github.com/laravel/framework/pull/53473 +* [12.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53524 +* [12.x] Avoid breaking change `RefreshDatabase::usingInMemoryDatabase()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53587 +* [12.x] fix: container resolution order when resolving class dependencies by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/53522 +* [12.x] Change the default for scheduled command `emailOutput()` to only send email if output exists by [@onlime](https://github.com/onlime) in https://github.com/laravel/framework/pull/53774 +* [12.x] Add `hasMorePages()` to `CursorPaginator` contract by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/53762 +* [12.x] modernize `DatabaseTokenRepository` and make consistent with `CacheTokenRepository` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53746 +* [12.x] chore: remove support for Carbon v2 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/53825 +* [12.x] use promoted properties for Auth events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53847 +* [12.x] use promoted properties for Database events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53848 +* [12.x] use promoted properties for Console events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53851 +* [12.x] use promoted properties for Mail events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53852 +* [12.x] use promoted properties for Notification events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53853 +* [12.x] use promoted properties for Routing events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53854 +* [12.x] use promoted properties for Queue events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53855 +* [12.x] Restore database token repository property documentation by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53908 +* [12.x] Use reject() instead of a negated filter() by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53925 +* [12.x] Use first-class callable syntax to improve static analysis by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53924 +* [12.x] add type declarations for Console Events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53947 +* [12.x] use type declaration on property by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53970 +* [12.x] Update Symfony and PHPUnit dependencies by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54019 +* [12.x] Allow `when()` helper to accept Closure condition parameter by [@ziadoz](https://github.com/ziadoz) in https://github.com/laravel/framework/pull/54005 +* [12.x] Add test for collapse in collections by [@amirmohammadnajmi](https://github.com/amirmohammadnajmi) in https://github.com/laravel/framework/pull/54032 +* [12.x] Add test for benchmark utilities by [@amirmohammadnajmi](https://github.com/amirmohammadnajmi) in https://github.com/laravel/framework/pull/54055 +* [12.x] Fix once() cache when used in extended static class by [@FrittenKeeZ](https://github.com/FrittenKeeZ) in https://github.com/laravel/framework/pull/54094 +* [12.x] Ignore querystring parameters using closure when validating signed url by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54104 +* Make `dropForeignIdFor` method complementary to `foreignIdFor` by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/54102 +* Allow scoped disks to be scoped from other scoped disks by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/54124 +* [12.x] Add test for Util::getParameterClassName() by [@amirmohammadnajmi](https://github.com/amirmohammadnajmi) in https://github.com/laravel/framework/pull/54209 +* Improve eloquent attach parameter consistency by [@fabpl](https://github.com/fabpl) in https://github.com/laravel/framework/pull/54225 +* [12.x] Enhance multi-database support by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54274 +* [12.x] Fix Session's `getCookieExpirationDate` incompatibility with Carbon 3 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54313 +* [12.x] Update minimum PHPUnit versions by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54323 +* [12.x] Prevent XSS vulnerabilities by excluding SVGs by default in image validation by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54331 +* [12.x] Convert interfaces from docblock to method by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54348 +* [12.x] Validate paths for UTF-8 characters by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/54370 +* [12.x] Fix aggregate alias when using expression by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/54418 +* Added flash method to Session interface to fix IDE issues by [@eldair](https://github.com/eldair) in https://github.com/laravel/framework/pull/54421 +* Adding the withQueryString method to the paginator interface. by [@dvlpr91](https://github.com/dvlpr91) in https://github.com/laravel/framework/pull/54462 +* [12.x] feat: --memory=0 should mean skip memory exceeded verification (Breaking Change) by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54393 +* Auto-discover nested policies following conventional, parallel hierarchy by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/54493 +* [12.x] Reintroduce PHPUnit 10.5 supports by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54490 +* [12.x] Allow limiting bcrypt hashing to 72 bytes to prevent insecure hashes. by [@waxim](https://github.com/waxim) in https://github.com/laravel/framework/pull/54509 +* [12.x] Fix accessing `Connection` property in `Grammar` classes by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54487 +* [12.x] Configure connection on SQLite connector by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54588 +* [12.x] Introduce Job@resolveQueuedJobClass() by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54613 +* [12.x] Bind abstract from concrete's return type by [@peterfox](https://github.com/peterfox) in https://github.com/laravel/framework/pull/54628 +* [12.x] Query builder PDO fetch modes by [@bert-w](https://github.com/bert-w) in https://github.com/laravel/framework/pull/54443 +* [12.x] Fix Illuminate components `composer.json` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54700 +* [12.x] Bump minimum `brick/math` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54694 * [11.x] Fix parsing `PHP_CLI_SERVER_WORKERS` as `string` instead of `int` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54724 * [11.x] Rename Redis parse connection for cluster test method to follow naming conventions by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/54721 * [11.x] Allow `readAt` method to use in database channel by [@utsavsomaiya](https://github.com/utsavsomaiya) in https://github.com/laravel/framework/pull/54729 * [11.x] Fix: Custom Exceptions with Multiple Arguments does not properly rein… by [@pandiselvamm](https://github.com/pandiselvamm) in https://github.com/laravel/framework/pull/54705 * [11.x] Update ConcurrencyTest exception reference to use namespace by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/framework/pull/54732 * [11.x] Deprecate `Factory::$modelNameResolver` by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/54736 +* Update `config/app.php` to reflect laravel/laravel change for compatibility by [@askdkc](https://github.com/askdkc) in https://github.com/laravel/framework/pull/54752 * [11x.] Improved typehints for `InteractsWithDatabase` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54748 * [11.x] Improved typehints for `InteractsWithExceptionHandling` && `ExceptionHandlerFake` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54747 - -## [v11.43.2](https://github.com/laravel/framework/compare/v11.43.1...v11.43.2) - 2025-02-19 - -* [11.x] Add missing test for `implode()` by [@nuernbergerA](https://github.com/nuernbergerA) in https://github.com/laravel/framework/pull/54704 -* [11.x] Enhance eventStream to Support Custom Events and Start Messages by [@devhammed](https://github.com/devhammed) in https://github.com/laravel/framework/pull/54695 -* Revert "[11.x] Enhance eventStream to Support Custom Events and Start Messages" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/54714 -* [11.x] Replace MD5 with xxh128 in File::hasSameHash() by [@vlakoff](https://github.com/vlakoff) in https://github.com/laravel/framework/pull/54690 -* [11.x] Add parameter typing for closure to addGlobalScope method by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/54677 -* [11.x] `assertOnlyJsonValidationErrors` / `assertOnlyInvalid` by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54678 -* [11.x] Allow for assertions against `QueueFake::pushRaw()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54703 -* [11.x] Fix: Handles non nested explode of multiple Date and Numeric rules in ValidationRuleParser by [@AlexandreMeledandri](https://github.com/AlexandreMeledandri) in https://github.com/laravel/framework/pull/54718 - -## [v11.43.1](https://github.com/laravel/framework/compare/v11.43.0...v11.43.1) - 2025-02-19 - -* [11.x] Fix "Divide by Zero" regression bug introduced in #54650 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54685 -* Revert "Fix Collection::implode with \Stringable objects" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54691 - -## [v11.43.0](https://github.com/laravel/framework/compare/v11.42.1...v11.43.0) - 2025-02-18 - -* Remove Incorrect [@mixin](https://github.com/mixin) Annotation in BuildsQueries Trait by [@daniel-de-wit](https://github.com/daniel-de-wit) in https://github.com/laravel/framework/pull/54596 -* make withoutScopedBindings usable on RouteRegistrar by [@ssninnni](https://github.com/ssninnni) in https://github.com/laravel/framework/pull/54592 -* [11.x] Update Broadcasting Install Command For Bun Version 1.1.39^ by [@realpoke](https://github.com/realpoke) in https://github.com/laravel/framework/pull/54605 -* [11.x] Add isTtySupported to Process facade by [@Riley19280](https://github.com/Riley19280) in https://github.com/laravel/framework/pull/54604 -* [11.x] fix: pagination generics by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/54601 -* Convert closures to arrow functions in the Model class by [@alikhosravidev](https://github.com/alikhosravidev) in https://github.com/laravel/framework/pull/54599 -* [11.x] Document hashedValue as non-nullable by [@JurianArie](https://github.com/JurianArie) in https://github.com/laravel/framework/pull/54615 -* [11.x] Prohibited If Declined and Prohibited If Accepted validation rules by [@osama-98](https://github.com/osama-98) in https://github.com/laravel/framework/pull/54608 -* [11.x] Fix param types for `orWhereHasMorph` method by [@simonellensohn](https://github.com/simonellensohn) in https://github.com/laravel/framework/pull/54659 -* [11.x] Add pascal alias for studly string helper by [@da-mask](https://github.com/da-mask) in https://github.com/laravel/framework/pull/54655 -* [11.x] make the Eloquent missing attribute handler more accurate by changing offsetExists by [@koenvu](https://github.com/koenvu) in https://github.com/laravel/framework/pull/54654 -* [11.x] use exec function if the symlink function is unavailable by [@aisuvro](https://github.com/aisuvro) in https://github.com/laravel/framework/pull/54651 -* [11.x] use value helper for $perPage as used for $total by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/54650 -* [11.x] [cleanup] used illuminate str contains by [@daison12006013](https://github.com/daison12006013) in https://github.com/laravel/framework/pull/54647 -* [11.x] Allow can attribute on group by [@utsavsomaiya](https://github.com/utsavsomaiya) in https://github.com/laravel/framework/pull/54648 -* Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54645 -* Fixes Factory Using Wrong Model Name by [@SameOldNick](https://github.com/SameOldNick) in https://github.com/laravel/framework/pull/54644 -* [11.x] fix 'parsePipeString' in pipeline helper by [@igzard](https://github.com/igzard) in https://github.com/laravel/framework/pull/54643 -* Update old() docblock by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/54641 -* [11.x] Feature: Array reject by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54638 -* [11.x] Blade [@include](https://github.com/include) performance by [@AlliBalliBaba](https://github.com/AlliBalliBaba) in https://github.com/laravel/framework/pull/54633 -* Fix Collection::implode with \Stringable objects by [@timkelty](https://github.com/timkelty) in https://github.com/laravel/framework/pull/54630 -* [11.x] Fix `serve` command with `PHP_CLI_SERVER_WORKERS` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54606 -* [11.x] new: `ddJson` method on `TestResponse` class by [@chester-sykes](https://github.com/chester-sykes) in https://github.com/laravel/framework/pull/54673 -* [11.x] Add find sole query builder method by [@zepfietje](https://github.com/zepfietje) in https://github.com/laravel/framework/pull/54667 -* [11.x] Fix regression bug with global `Factory::guessModelNamesUsing()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54665 -* [11.x] Fix routeCollection get method return value when searching by dot not… by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/framework/pull/54672 -* [11.x] Add `withWhereRelation` method to builder by [@utsavsomaiya](https://github.com/utsavsomaiya) in https://github.com/laravel/framework/pull/54668 - -## [v11.42.1](https://github.com/laravel/framework/compare/v11.42.0...v11.42.1) - 2025-02-12 - -* Add Taylor's inspiring quote - We must ship by [@1weiho](https://github.com/1weiho) in https://github.com/laravel/framework/pull/54579 -* Type the callback for Relation::noConstraints by [@simon-tma](https://github.com/simon-tma) in https://github.com/laravel/framework/pull/54572 -* [11.x] fix: getQualified{Created,Updated}AtColumn never returning null by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/54568 -* [11.x] `assertStreamed` and `assertNotStreamed` by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54566 -* [11.x] Add `assertJsonFragments` assertion by [@lioneaglesolutions](https://github.com/lioneaglesolutions) in https://github.com/laravel/framework/pull/54576 -* [11.x] `doesntContain` on eloquent collection by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54567 -* [11.x] Allow batching a Closure by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54587 - -## [v11.42.0](https://github.com/laravel/framework/compare/v11.41.3...v11.42.0) - 2025-02-11 - -* docs: clarify use of hasOption() by [@jezmck](https://github.com/jezmck) in https://github.com/laravel/framework/pull/54415 -* Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54427 -* [11.x] add Generics to Paginator's ArrayAccess methods by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/54428 -* [11.x] Fix docblocks for code that calls `enum_value()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54432 -* [11.x] Fix assertContent on laravel test that respond with Symfony Response Object by [@tben](https://github.com/tben) in https://github.com/laravel/framework/pull/54467 -* [11.x] Add Higher Order Messaging support for last by [@fernandokbs](https://github.com/fernandokbs) in https://github.com/laravel/framework/pull/54459 -* [11.x] Database testing traits has impact to artisan calls by [@nivseb](https://github.com/nivseb) in https://github.com/laravel/framework/pull/54458 -* [11.x] Add precision to `Number::currency()` by [@benjibee](https://github.com/benjibee) in https://github.com/laravel/framework/pull/54456 -* [11.x] Add generics to lazy queries by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/54453 -* [11.x] Merge in eager loads from nested where queries by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/54455 -* [11.x] Fluent numeric validation by [@xoesae](https://github.com/xoesae) in https://github.com/laravel/framework/pull/54425 -* [11.x] Fix casts + `withAttributes` by [@tontonsb](https://github.com/tontonsb) in https://github.com/laravel/framework/pull/54422 -* [11.x] Ensure batched jobs are actually batchable by [@josepostiga](https://github.com/josepostiga) in https://github.com/laravel/framework/pull/54442 -* [11.x] Update PHPStan to 2.x by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/53716 -* Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54475 -* Add relative date shorthands to Query Builder by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/54408 -* [11.x] feat: add better closure typing in QueriesRelationships by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/54452 -* [11.x] Fix the method explodeExplicitRule to support Numeric Validation by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/54478 -* Add `Builder` On Clone callback support by [@ralphjsmit](https://github.com/ralphjsmit) in https://github.com/laravel/framework/pull/54477 -* Support relative paths to SQLite databases by [@LukeTowers](https://github.com/LukeTowers) in https://github.com/laravel/framework/pull/54480 -* [11.x] Where doesnt have nullable morph by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/54363 -* [11.x] Add the ability to skip migrations within tests by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54441 -* Queue Integration Tests with Redis Cluster by [@vadimonus](https://github.com/vadimonus) in https://github.com/laravel/framework/pull/54218 -* [11.x] Optimize `PendingBatch@ensureJobIsBatchable` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54485 -* [11.x] Supports PHPUnit 12.0 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54316 -* [11.x] Fix spelling in comment by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/54503 -* [11.x] Add Context "missing" method by [@vbergerondev](https://github.com/vbergerondev) in https://github.com/laravel/framework/pull/54499 -* [11.x] feat: add generics to Container methods by [@MrMeshok](https://github.com/MrMeshok) in https://github.com/laravel/framework/pull/54543 -* [11.x] Add a setAssetRoot method to the UrlGenerator class by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/54530 -* [11.x] Handle Null Check in Str::startsWith and Str::endsWith by [@onairmarc](https://github.com/onairmarc) in https://github.com/laravel/framework/pull/54520 -* [11.x] Improve check for relative sqlite databases by [@LukeTowers](https://github.com/LukeTowers) in https://github.com/laravel/framework/pull/54513 -* Revert "[11.x] Use Str::wrap() instead of nesting Str::start() inside Str::finish()" by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/54528 -* [11.x] Job Batches with Redis Cluster by [@vadimonus](https://github.com/vadimonus) in https://github.com/laravel/framework/pull/54522 -* [11.x] fix: specify type of TClass generic in Container by [@MrMeshok](https://github.com/MrMeshok) in https://github.com/laravel/framework/pull/54545 -* [11.x] Improve docblocks for morph maps in `Relation` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54560 -* docs: fix return type documentation for initializeSignal method by [@nzsys](https://github.com/nzsys) in https://github.com/laravel/framework/pull/54553 -* [11.x] Add support for middlewares & failed handler on broadcastable events by [@Jacobs63](https://github.com/Jacobs63) in https://github.com/laravel/framework/pull/54562 -* [11.x] json assertions on streamed content by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54565 - -## [v11.41.3](https://github.com/laravel/framework/compare/v11.41.2...v11.41.3) - 2025-01-30 - -## [v11.41.2](https://github.com/laravel/framework/compare/v11.41.1...v11.41.2) - 2025-01-30 - -## [v11.41.1](https://github.com/laravel/framework/compare/v11.41.0...v11.41.1) - 2025-01-30 - -* [11.x] Allow secret key Updates Without Bringing the Site Up by [@rashidlaasri](https://github.com/rashidlaasri) in https://github.com/laravel/framework/pull/54389 -* [11.x] use Auth::userResolver when resolving the authenticated user by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/54382 -* [11.x] Add `Macroable` and `fill()` to `Support\Fluent` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/54404 -* [11.x] Optimize pluck() to avoid redundant column selection by [@zsocakave](https://github.com/zsocakave) in https://github.com/laravel/framework/pull/54396 -* [11.x] Optimize `loadTranslationsFrom` function for simplicity and clarity by [@selcukcukur](https://github.com/selcukcukur) in https://github.com/laravel/framework/pull/54407 -* feat: gracefully handle command not found exception - avoid creds exposure by [@chinmaypurav](https://github.com/chinmaypurav) in https://github.com/laravel/framework/pull/54406 -* Handle pooled Postgres connections for Laravel Cloud by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/54346 - -## [v11.41.0](https://github.com/laravel/framework/compare/v11.40.0...v11.41.0) - 2025-01-28 - -* [11.x] more pint rules by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54332 -* [11.x] Allow `TestComponent` to be macroable by [@ziadoz](https://github.com/ziadoz) in https://github.com/laravel/framework/pull/54359 -* [11.x] Fix validator return fails if using different date formats by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/54350 -* [11.x] fix the method `explodeExplicitRule` to support Customizable Date Validation by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/54353 -* [11.x] Adds the `addPath()` method to the `Lang` facade and the `Translator` class. by [@selcukcukur](https://github.com/selcukcukur) in https://github.com/laravel/framework/pull/54347 -* Improve: add fire failed event at once by [@cesarMtorres](https://github.com/cesarMtorres) in https://github.com/laravel/framework/pull/54376 -* [11.x] feat: Create missing pgsql database when running migrations by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54314 -* [11.x] Proper rate limiter fix with phpredis serialization/compression enabled by [@TheLevti](https://github.com/TheLevti) in https://github.com/laravel/framework/pull/54372 -* Update Stringable Rule testcases by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/54387 -* [11.x] Use `Date` facade for storing the password confirmation timestamp by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54383 - -## [v11.40.0](https://github.com/laravel/framework/compare/v11.39.1...v11.40.0) - 2025-01-24 - -* draft: fix: Don't release lock for ShouldBeUniqueUntilProcessing Job that gets released by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54261 -* [11.x] Add Laravel Pint by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53835 -* Add self to HasCollection type param in Model by [@thena-seer-sfg](https://github.com/thena-seer-sfg) in https://github.com/laravel/framework/pull/54311 -* [11.x] Add pending attributes by [@tontonsb](https://github.com/tontonsb) in https://github.com/laravel/framework/pull/53720 -* fix: `schedule:test` on commands using runInBackground by [@dallyger](https://github.com/dallyger) in https://github.com/laravel/framework/pull/54321 -* [11.x] Helper methods to dump responses of the Laravel HTTP client by [@morrislaptop](https://github.com/morrislaptop) in https://github.com/laravel/framework/pull/54317 -* Add support for cursor editor in ResolvesDumpSource by [@tuxfamily](https://github.com/tuxfamily) in https://github.com/laravel/framework/pull/54318 -* [11.x] Add Customizable Date Validation Rule with Flexible Date Constraints by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/53465 -* [11.x] start syncing StyleCI rules to Pint by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54326 -* [11.x] apply our new Pint rule to the `/tests` directory by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54325 -* fix(Collection::pop()): count < 1 by [@artumi-richard](https://github.com/artumi-richard) in https://github.com/laravel/framework/pull/54340 -* Patch CVE-2025-22145 in nesbot/carbon package by [@dennis-koster](https://github.com/dennis-koster) in https://github.com/laravel/framework/pull/54335 -* [11.x] Prevent unintended serialization and compression by [@JeppeKnockaert](https://github.com/JeppeKnockaert) in https://github.com/laravel/framework/pull/54337 -* [11.x] Pass collection of models to `whereMorphedTo` / `whereNotMorphedTo` by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/54324 - -## [v11.39.1](https://github.com/laravel/framework/compare/v11.39.0...v11.39.1) - 2025-01-22 - -* fix: collapseWithKeys on empty collection by [@benatoff](https://github.com/benatoff) in https://github.com/laravel/framework/pull/54290 -* fix(broadcaster): incorrect channel matching because of dot in pattern by [@021-projects](https://github.com/021-projects) in https://github.com/laravel/framework/pull/54303 -* [11.x] Use constructor property promotion for database query condition expression by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/54302 -* [11.x] Add IncrementOrCreate method to Eloquent by [@carloeusebi](https://github.com/carloeusebi) in https://github.com/laravel/framework/pull/54300 -* [11.x] Add additional test cases for Arr helper to enhance coverage by [@mrvipchien](https://github.com/mrvipchien) in https://github.com/laravel/framework/pull/54298 -* Bump vite from 5.2.14 to 5.4.12 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/54296 -* [11.x] Fix unique jobs that have a uniqueVia method by [@DougSisk](https://github.com/DougSisk) in https://github.com/laravel/framework/pull/54294 - -## [v11.39.0](https://github.com/laravel/framework/compare/v11.38.2...v11.39.0) - 2025-01-21 - -* [11.x] Replace duplicate `ValidatedInput` functions with `InteractsWithData` trait by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/54208 -* [11.x] Improve `Email` validation rule custom translation messages by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54202 -* [11.x] Fix deprecation warnings in `optimize:clear` and `optimize` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54197 -* [11.x] Add support for phpredis backoff and max retry config options by [@TheLevti](https://github.com/TheLevti) in https://github.com/laravel/framework/pull/54191 -* Introduces UseFactory attribute by [@christopherarter](https://github.com/christopherarter) in https://github.com/laravel/framework/pull/54065 -* [11.x] Set class-string generic on `UseFactory` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54215 -* [11.x] switch LazyCollection::make() for new LazyCollection() by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/54216 -* support style file name hashes with query strings in manifest by [@newapx](https://github.com/newapx) in https://github.com/laravel/framework/pull/54219 -* [11.x] Solidify `Rule::email()` tests by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54226 -* [11.x] Fix line-ending mismatch in CliDumperTest::testArray and CliDumperTest::testObject by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/54222 -* Add a report/log option to filesystem exceptions without throwing by [@lotharthesavior](https://github.com/lotharthesavior) in https://github.com/laravel/framework/pull/54212 -* [11.x] Fix Cache component to be aware of phpredis serialization and compression settings by [@TheLevti](https://github.com/TheLevti) in https://github.com/laravel/framework/pull/54221 -* [11.x] fix: Forcing DB Session driver to always use the write connection by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54231 -* [11.x] Fix line-ending mismatch in `BladeComponentTagCompilerTest` under `Illuminate\Tests\View\Blade` by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/54233 -* [11.x] Fix job not logged in failed_jobs table if timeout occurs within database transaction by [@decaylala](https://github.com/decaylala) in https://github.com/laravel/framework/pull/54173 -* [11.x] Fix unique job lock is not released on model not found exception, lock gets stuck. by [@zackAJ](https://github.com/zackAJ) in https://github.com/laravel/framework/pull/54000 -* [11.x] Fix line-ending mismatch on Windows test by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/54236 -* Added support in DB::prohibitDestructiveCommands to preventing destructive Rollback… by [@hexathos](https://github.com/hexathos) in https://github.com/laravel/framework/pull/54238 -* [11.x] Add applyAfterQueryCallbacks Support to Non-Mutator Cases in pluck Method by [@batinmustu](https://github.com/batinmustu) in https://github.com/laravel/framework/pull/54268 -* [11.x] `addPath()` Allow adding new path for translation loader. by [@selcukcukur](https://github.com/selcukcukur) in https://github.com/laravel/framework/pull/54277 - -## [v11.38.2](https://github.com/laravel/framework/compare/v11.38.1...v11.38.2) - 2025-01-15 - -* [11.x] Simplify Codebase by Using `qualifyColumn` Helper Method by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54187 -* Revert "Add support for missing Postgres connection options" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54195 -* Revert "[11.x] Support DB aggregate by group (new methods)" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54196 - -## [v11.38.1](https://github.com/laravel/framework/compare/v11.38.0...v11.38.1) - 2025-01-14 - -* Fix breaking change - Revert "[11.x] Replace string class names with ::class constants" by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54185 -* Add failing test for #54185 by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54186 - -## [v11.38.0](https://github.com/laravel/framework/compare/v11.37.0...v11.38.0) - 2025-01-14 - -* Fix offset range in docblock by [@simon-tma](https://github.com/simon-tma) in https://github.com/laravel/framework/pull/54062 -* [11.x] Fix breaking change in `RefreshDatabase` by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/54075 -* [11.x] Fallback to parent methods on `HasUniqueStringIds` trait by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/54096 -* [11.x] Adds `finally` method to pipeline helper by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/54110 -* Add support for missing Postgres connection options by [@Maniload](https://github.com/Maniload) in https://github.com/laravel/framework/pull/54101 -* fix: Don't set newLineWritten to true unless verbosity allows output by [@ConnySjoblom](https://github.com/ConnySjoblom) in https://github.com/laravel/framework/pull/54127 -* [11.x] Adds support for Attribute return mutators to the `Eloquent/Builder` pluck method by [@MattBradleyDev](https://github.com/MattBradleyDev) in https://github.com/laravel/framework/pull/54130 -* [11.x] Fixes wrong `@mixin` on `SoftDeletes` trait by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/54140 -* [11.x] Replace string class names with ::class constants by [@panakour](https://github.com/panakour) in https://github.com/laravel/framework/pull/54134 -* [11.x] fix `times()` calls by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54141 -* [11.x] minor readability by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/54117 -* Handles factory=null in ConnectException while recording request-response in PendingRequest by [@StSarc](https://github.com/StSarc) in https://github.com/laravel/framework/pull/54121 -* [11.x] Refine error messages for detecting lost connections (Debian bookworm compatibility) by [@mfn](https://github.com/mfn) in https://github.com/laravel/framework/pull/54111 -* [11.x] fix: filter vendor paths from registered loaders in Application::inferBasePath by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/54119 -* [11.x] Allow exceptions to the `optimize` and `optimize:clear` commands by [@jonerickson](https://github.com/jonerickson) in https://github.com/laravel/framework/pull/54070 -* Add action filter to route:list by [@miccehedin](https://github.com/miccehedin) in https://github.com/laravel/framework/pull/54135 -* No explicit `USE database` statement by [@TheLevti](https://github.com/TheLevti) in https://github.com/laravel/framework/pull/54132 -* Add support for custom payloads and channels in broadcasting by [@JanneDeVos](https://github.com/JanneDeVos) in https://github.com/laravel/framework/pull/54099 -* [11.x] Add fluent `Email` validation rule by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54067 -* [11.x] middleware support for specific method in resource routes by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/53313 -* [11.x] Support DB aggregate by group (new methods) by [@GromNaN](https://github.com/GromNaN) in https://github.com/laravel/framework/pull/53679 -* Correct return type to match functionality by [@willpower232](https://github.com/willpower232) in https://github.com/laravel/framework/pull/54148 -* [11.x] Renaming Traveler to Passable and Stops to Pipes by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54142 -* [11.x] Add `Dispatchable::newPendingDispatch()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54153 -* [11.x] Add `FormRequest::array($key)` and `Fluent::array($key)` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/54177 -* [11.x] Make methods of `HasRelationships` generic by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54174 -* [11.x] Make tests pass on Herd by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/54171 -* Revert "Fix: Handle mixed-type values in compileInsert" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54169 -* [11.x] Fix docblock for `PendingDispatch@getJob()` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/54158 -* pass options to migration events by [@willpower232](https://github.com/willpower232) in https://github.com/laravel/framework/pull/54151 -* Encode cache values for SQLite with base64 to prevent failing on \0 characters by [@adamkiss](https://github.com/adamkiss) in https://github.com/laravel/framework/pull/54178 -* [11.x] Fix invokable validation rule return type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/54179 - -## [v11.37.0](https://github.com/laravel/framework/compare/v11.36.1...v11.37.0) - 2025-01-02 - -* [11.x] Update Collection::hasAny by [@JeftaAtSiip](https://github.com/JeftaAtSiip) in https://github.com/laravel/framework/pull/53963 -* [11.x] Update DetectsLostConnections trait by [@holgerk](https://github.com/holgerk) in https://github.com/laravel/framework/pull/53966 -* Fix: (Queue Worker) firing the JobPopped event when $popCallbacks returns null by [@rudenav](https://github.com/rudenav) in https://github.com/laravel/framework/pull/53962 -* [11.x] Add `Dumpable` trait to `Uri` by [@nuernbergerA](https://github.com/nuernbergerA) in https://github.com/laravel/framework/pull/53960 -* Fix: Handle mixed-type values in compileInsert by [@alipadron](https://github.com/alipadron) in https://github.com/laravel/framework/pull/53948 -* [11.x] Add `$ignoreCase` option to `Str::is` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53981 -* [11.x] Updates component dependencies by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53975 -* [11.x] Update Uri `withoutQuery` method to accept string or array input by [@1weiho](https://github.com/1weiho) in https://github.com/laravel/framework/pull/53973 -* [11.x] Fix cached health endpoint not working when in maintenance mode by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53974 -* Add PHPDoc type hints by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53984 -* [11.x] Allow passing bool to facade Http@preventStrayRequests() by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53992 -* [11.x] Use Str::wrap() instead of nesting Str::start() inside Str::finish() by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53987 -* Fix day range in docblock by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53985 -* [11.x] Fixes `Illuminate\Http\Response` to output empty string if `$content` is set to `null` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53872 -* [11.x] Fix/Improve Resend transport response handling by [@markovic-nikola](https://github.com/markovic-nikola) in https://github.com/laravel/framework/pull/54004 -* [11.x] Update View::withErrors() docblock to reflect string parameter support by [@cheack](https://github.com/cheack) in https://github.com/laravel/framework/pull/54009 -* 11.x improve resend transport response handling - fix by [@markovic-nikola](https://github.com/markovic-nikola) in https://github.com/laravel/framework/pull/54006 -* [11.x] Added new Eloquent methods: `whereDoesntHaveRelation`, `whereMorphDoesntHaveRelation` and their variants with `OR` by [@andrey-helldar](https://github.com/andrey-helldar) in https://github.com/laravel/framework/pull/53996 -* [11.x] Re-refresh the database if the `RefreshDatabase` transaction was committed by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/53997 -* [11.x] add assertFailedWith to InteractsWithQueue trait by [@teddy-francfort](https://github.com/teddy-francfort) in https://github.com/laravel/framework/pull/53980 -* Quick doc fix by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54040 -* [11.x] Allow using `Illuminate\Support\Uri` on testing HTTP Requests by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54038 -* [11.x] Adding tests for Overlapping Routes by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/54050 -* [11.x] adding tests for `null` & `*` key given in `data_get` by [@jwjenkin](https://github.com/jwjenkin) in https://github.com/laravel/framework/pull/54059 - -## [v11.36.1](https://github.com/laravel/framework/compare/v11.36.0...v11.36.1) - 2024-12-17 - -* Once Remember Null Values by [@dbpolito](https://github.com/dbpolito) in https://github.com/laravel/framework/pull/53949 -* [11.x] Add wildcard directory discovery to the EventServiceProvider by [@jared-cannon](https://github.com/jared-cannon) in https://github.com/laravel/framework/pull/53932 -* [11.x] Add `getJob()` method to `PendingDispatch` class + Introduced tests by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/53951 -* Avoid writing multiple keys when using redis in cluster mode by [@bentleyo](https://github.com/bentleyo) in https://github.com/laravel/framework/pull/53940 -* Revert "[11.x] fix: allows injection using multiple interfaces with the same concrete implementation" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53955 -* Revert "[11.x] No need to redeclare variables" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53954 - -## [v11.36.0](https://github.com/laravel/framework/compare/v11.35.1...v11.36.0) - 2024-12-17 - -* [11.x] Update `config/mail.php` with supported configuration by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53874 -* [11.x] Allows `enum_value()` to be use in standalone `illuminate/collections` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53871 -* [11.x] `Uri` and `UriQueryString` implement `Stringable` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53873 -* [11.x] Prefer `new Stringable` over `Str::of` and `str()` by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53883 -* [11.x] No need to redeclare variables by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53887 -* [11.x] Add PHP 8.4 with herd to passthrough variables by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53885 -* Add new `Uri` class to default, global aliases by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/53884 -* [11.x] Fix attribute mutator access in `loadMissing` by [@SychO9](https://github.com/SychO9) in https://github.com/laravel/framework/pull/53879 -* [11.x] Fix `numericAggregate` on eloquent builder by [@AmirRezaM75](https://github.com/AmirRezaM75) in https://github.com/laravel/framework/pull/53880 -* [11.x] Prefer `new Fluent` over `fluent()` helper by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53890 -* Patch by [@angelej](https://github.com/angelej) in https://github.com/laravel/framework/pull/53869 -* [11.x] `Collection::wrap` by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53891 -* [11.x] Bump minimum league/commonmark by [@ah-rahimi](https://github.com/ah-rahimi) in https://github.com/laravel/framework/pull/53899 -* [11.x] `Collection::range` by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53895 -* [11.x] Added an event that reports files being deleted when calling the `schema:dump --prune` command by [@andrey-helldar](https://github.com/andrey-helldar) in https://github.com/laravel/framework/pull/53870 -* [11.x] fix: allows injection using multiple interfaces with the same concrete implementation by [@jamiethorpe](https://github.com/jamiethorpe) in https://github.com/laravel/framework/pull/53275 -* [11.x] Early return in Factory::modelName() by [@shaedrich](https://github.com/shaedrich) in https://github.com/laravel/framework/pull/53912 -* [11.x] Prevent `blank` Helper from Serializing Eloquent Models by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/53911 -* [11.x] Add word-break to mail links by [@seblavoie](https://github.com/seblavoie) in https://github.com/laravel/framework/pull/53906 -* Preserve dynamic database connections on reconnect by [@nickakitch](https://github.com/nickakitch) in https://github.com/laravel/framework/pull/53914 -* Fix mutexName inconsistency caused by different PHP binary paths on multiple servers by [@waska14](https://github.com/waska14) in https://github.com/laravel/framework/pull/53811 -* [11.x] Add `Fluent::set` method by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53946 -* [11.x] Fix inspecting columns of raw indexes by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/53945 -* [11.x] Allow easier overriding of the exception thrown by invalid ID in route binding by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53944 -* [11.x] Fix client path value in file uploads by [@gyaaniguy](https://github.com/gyaaniguy) in https://github.com/laravel/framework/pull/53941 - -## [v11.35.1](https://github.com/laravel/framework/compare/v11.35.0...v11.35.1) - 2024-12-12 - -* [11.x] Fix incorrect typechange in `Illuminate\Database\Query\Builder` by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/53841 -* Fixed Enum usage in whereHasMorph condition when morph attribute casting by [@Outsidaz](https://github.com/Outsidaz) in https://github.com/laravel/framework/pull/53839 -* [11.x] Fix unescaped table names issue of `DatabaseTruncation` trait by introducing `Connection::withoutTablePrefix()` method by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/53842 -* Specify node when sending scan to RedisCluster by [@bentleyo](https://github.com/bentleyo) in https://github.com/laravel/framework/pull/53837 -* [11.x] fix: cast session lifetime to int by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/53846 -* [11.x] allow sub second timeout value for http requests by [@mihaileu](https://github.com/mihaileu) in https://github.com/laravel/framework/pull/53850 -* Revert "set schema to smtps if MAIL_ENCRYPTION === tls" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53863 - -## [v11.35.0](https://github.com/laravel/framework/compare/v11.34.2...v11.35.0) - 2024-12-10 - -* [11.x] Supports Symfony 7.2 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53585 -* [11.x] Fix database reconnecting logic by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/53693 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53708 -* [11.x] Fix foreignIdFor() when the foreign key is a non-incrementing integer other than ULID by [@edgrosvenor](https://github.com/edgrosvenor) in https://github.com/laravel/framework/pull/53696 -* [11.x] Allow sorting routes by precedence in artisan routes:list. by [@mathieutu](https://github.com/mathieutu) in https://github.com/laravel/framework/pull/53706 -* [11.x] Update the message for the schedule:work command. by [@AbdelElrafa](https://github.com/AbdelElrafa) in https://github.com/laravel/framework/pull/53710 -* [11.x] Support auto-discovery of PSR-17 implementations by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/53711 -* [11.x] Improve Error Handler in the ProcessDriver by [@WillTorres10](https://github.com/WillTorres10) in https://github.com/laravel/framework/pull/53712 -* [11.x] Comment grammar fixes by [@nexxai](https://github.com/nexxai) in https://github.com/laravel/framework/pull/53714 -* [11.x] Replace get_called_class with static::class by [@fernandokbs](https://github.com/fernandokbs) in https://github.com/laravel/framework/pull/53725 -* [11.x] Add the pivot's related model when creating from attributes by [@alexwass-lr](https://github.com/alexwass-lr) in https://github.com/laravel/framework/pull/53694 -* [11.x] use a consistent alias for `Illuminate\Database\Eloquent\Collection` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53730 -* [11.x] switch `Collection::make()` for `new Collection()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53733 -* [11.x] always alias the `Illuminate\Database\Eloquent\Collection` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53735 -* [11.x] convert `collect()` helper to `new Collection()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53726 -* [11.x] Improves `Collection` support for enums using `firstWhere()` and `value()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53777 -* [11.x] Add Conditionable Trait to Request by [@ahmeti](https://github.com/ahmeti) in https://github.com/laravel/framework/pull/53775 -* [11.x] Ignore health endpoint when in maintenance mode by [@joshmanders](https://github.com/joshmanders) in https://github.com/laravel/framework/pull/53772 -* [11.x] Add ability to transform `Http\Client\Response` into `Fluent` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53771 -* set schema to smtps if MAIL_ENCRYPTION === tls by [@danielrona](https://github.com/danielrona) in https://github.com/laravel/framework/pull/53749 -* [11.x] more consistent and readable chaining by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53748 -* [11.x] Fix the RateLimiter issue when using dynamic keys by [@MilesChou](https://github.com/MilesChou) in https://github.com/laravel/framework/pull/53763 -* [11.x] Add ability to customize or disable `Http\Client\RequestException` message truncation by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53734 -* [11.x] Include the initial value in the return types of `reduce()` by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/53798 -* [11.x] Add pingOnSuccessIf & pingOnFailureIf to Schedule handling by [@lucacastelnuovo](https://github.com/lucacastelnuovo) in https://github.com/laravel/framework/pull/53795 -* [11.x] Improve PHPDoc for nullable properties in `Illuminate\Database\Query\Builder` class by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/53793 -* [11.x] Remove usage of `compact()` in Container by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/53789 -* [11.x] Make `Exceptions@dontTruncateRequestExceptions()` fluent by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53786 -* [11.x] Make mailables tappable by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53788 -* URI by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53731 -* [11.x] Require `laravel/serializable-closure` on Database component by [@patrickcarlohickman](https://github.com/patrickcarlohickman) in https://github.com/laravel/framework/pull/53822 -* [11.x] use new PHP 8 `str_` functions by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53817 -* [11.x] handle `password_hash()` failures better by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53821 -* [11.x] remove unnecessary `return` statement by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53816 -* [11.x] simplify passing arguments to `when()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53815 -* [11.x] remove redundant `array_values` call by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53814 -* [11.x] prefer assignment over `array_push` for 1 element by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53813 -* [11.x] fix `chopStart` and `chopEnd` tests by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53812 -* [11.x] remove temporary variables by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53810 -* [11.x] fix `$events` docblock type by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53808 -* [11.x] Fix docblock for URI by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53804 -* Bump nanoid from 3.3.7 to 3.3.8 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/53831 -* [11.x] use promoted properties by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53807 -* Revert "[11.x] use promoted properties" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53832 -* Using throw config of filesystem disks when faking by [@emulgeator](https://github.com/emulgeator) in https://github.com/laravel/framework/pull/53779 -* [11.x] Fix schema names on `DatabaseTruncation` trait (PostgreSQL and SQLServer) by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/53787 - -## [v11.34.2](https://github.com/laravel/framework/compare/v11.34.1...v11.34.2) - 2024-11-27 - -* Revert "[11.x] Add non-static JsonResource wrapping" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53686 -* [11.x] groupBy() return type phpdoc by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/53684 -* [11.x] Fix `withoutOverlapping` for grouped scheduled closures by [@istiak-tridip](https://github.com/istiak-tridip) in https://github.com/laravel/framework/pull/53680 -* [11.x] Fix `ResendTransport` missing custom headers by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53687 - -## [v11.34.1](https://github.com/laravel/framework/compare/v11.34.0...v11.34.1) - 2024-11-26 - -* Configure cloud log socket by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53666 - -## [v11.34.0](https://github.com/laravel/framework/compare/v11.33.2...v11.34.0) - 2024-11-26 - -* [10.x] Fix append and prepend batch to chain by [@Bencute](https://github.com/Bencute) in https://github.com/laravel/framework/pull/53455 -* [11.x] Allow `BackedEnum` when using `fromRoute()` in `MakesHttpRequests` by [@wietsewarendorff](https://github.com/wietsewarendorff) in https://github.com/laravel/framework/pull/53593 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53586 -* [11.x] Move `$ownerKey` check for `null` to `MorphTo` as `BelongsTo` relationship will always return a `string` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53592 -* Unset eloquent model's cached cast attribute by [@adamthehutt](https://github.com/adamthehutt) in https://github.com/laravel/framework/pull/53583 -* [11.x] Add non-static JsonResource wrapping by [@SanderMuller](https://github.com/SanderMuller) in https://github.com/laravel/framework/pull/53543 -* [10.x] PHP 8.4 Code Compatibility by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53612 -* Add assertCount() for fake storage by [@ahmadreza1383](https://github.com/ahmadreza1383) in https://github.com/laravel/framework/pull/53620 -* Fix Paginator __construct parameter typehint for phpstan by [@Afrowson](https://github.com/Afrowson) in https://github.com/laravel/framework/pull/53615 -* [11.x] Add generics for Arr::last() by [@talkinnl](https://github.com/talkinnl) in https://github.com/laravel/framework/pull/53619 -* Add typed closure to bootstrappers in console application by [@MatusBoa](https://github.com/MatusBoa) in https://github.com/laravel/framework/pull/53613 -* refactor: Some minor performance & readability enhancements by [@dshafik](https://github.com/dshafik) in https://github.com/laravel/framework/pull/53596 -* [11.x] Improve doc blocks for interacting with enum inputs by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/53625 -* [11.x] Skip object construction if no rebound callbacks are set by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/53502 -* Make the bearerToken method case-insensitive by [@samtlewis](https://github.com/samtlewis) in https://github.com/laravel/framework/pull/53627 -* [11.x] Fix attribute inheritance for nested scheduled groups by [@istiak-tridip](https://github.com/istiak-tridip) in https://github.com/laravel/framework/pull/53626 -* [11.x] Supports PHP 8.4 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53468 -* ☁️ by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53623 -* [11.x] Fix typo in docblock by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/53636 -* Add Number::spellOrdinal() to spell ordinals as words. by [@joelstein](https://github.com/joelstein) in https://github.com/laravel/framework/pull/53661 -* [11.x] Add PausePrompt fallback by [@jwpage](https://github.com/jwpage) in https://github.com/laravel/framework/pull/53660 -* [11.x] Fix `SyntaxError` on Vite prefetch with empty assets by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/53659 -* [11.x] Improved `class-string` types by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53657 -* [11.x] Use `never` type for methods that always throws by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/53643 -* [11.x] Adds conditional to routes by [@Boorinio](https://github.com/Boorinio) in https://github.com/laravel/framework/pull/53654 -* [11.x] Make `withoutDefer` also return `$this` by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/53644 -* Add shorthands for fake HTTP responses by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/53663 -* Use the environment method instead of the isLocal method by [@NaokiTsuchiya](https://github.com/NaokiTsuchiya) in https://github.com/laravel/framework/pull/53638 -* [11.x] Fix: Ensure generated policies return boolean values by [@Aluisio-Pires](https://github.com/Aluisio-Pires) in https://github.com/laravel/framework/pull/53632 -* Bus assertempty by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/53664 -* [11.x] Improve schedule group behaviors by [@istiak-tridip](https://github.com/istiak-tridip) in https://github.com/laravel/framework/pull/53641 -* [11.x] Add `Request::fluent` method by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53662 -* [11.x] Support named in-memory SQLite connections by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/53635 -* event name & listener callback types by [@rudiedirkx](https://github.com/rudiedirkx) in https://github.com/laravel/framework/pull/53642 -* Test cleanup from #53664 by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/53672 -* [11.x] Fix: Prevent invalid AWS credentials options being created by [@robchett](https://github.com/robchett) in https://github.com/laravel/framework/pull/53633 -* [11.x] Expand `Support\Fluent` data access and transformation capabilities by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53665 - -## [v11.33.2](https://github.com/laravel/framework/compare/v11.33.1...v11.33.2) - 2024-11-19 - -* Support ObservedBy on parent model classes by [@adamthehutt](https://github.com/adamthehutt) in https://github.com/laravel/framework/pull/53579 -* Revert "[11.x] Support DB aggregate by group" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53582 -* [11.x] Fix: Improve Request Port Extraction Handling in ServeCommand.php to Prevent Artisan Command Failures by [@ahmad-cit22](https://github.com/ahmad-cit22) in https://github.com/laravel/framework/pull/53538 - -## [v11.33.1](https://github.com/laravel/framework/compare/v11.33.0...v11.33.1) - 2024-11-19 - -* Marking password params in Database connector with SensitiveParameter attribute by [@philo23](https://github.com/philo23) in https://github.com/laravel/framework/pull/53580 - -## [v11.33.0](https://github.com/laravel/framework/compare/v11.32.0...v11.33.0) - 2024-11-19 - -* [11.x] Add "createQuietly" method by [@bramr94](https://github.com/bramr94) in https://github.com/laravel/framework/pull/53558 -* [11.x] Trim log channel names by [@mathieutu](https://github.com/mathieutu) in https://github.com/laravel/framework/pull/53554 -* [11.x] Fix `withoutOverlapping` via `PendingEventAttributes` proxy by [@istiak-tridip](https://github.com/istiak-tridip) in https://github.com/laravel/framework/pull/53553 -* [11.x] Update docblocks using latest documenter by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53550 -* fix: use qualifyColumn rather than assuming format by [@willtj](https://github.com/willtj) in https://github.com/laravel/framework/pull/53559 -* [11.x] Add `Request::enums` method to retrieve an array of enums by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53540 -* [11.x] hash the token going into the cache by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53561 -* Output clean error page on health check route by [@chrispage1](https://github.com/chrispage1) in https://github.com/laravel/framework/pull/53528 -* [11.x] Extract `ShowModelCommand` functionality to separate class by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53541 -* Add Collection/Generator generic types to public query builder methods by [@rudiedirkx](https://github.com/rudiedirkx) in https://github.com/laravel/framework/pull/53567 -* [11.x] consistent multiline constructors by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53566 -* [11.x] prefer `new Collection()` over `collect()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53563 -* [11.x] Add builder and collection to `ModelInspector` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53565 -* [11.x] Support DB aggregate by group by [@GromNaN](https://github.com/GromNaN) in https://github.com/laravel/framework/pull/53209 -* [11.x] add ability to disable relationships in factories by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53450 -* Revert "fix: use qualifyColumn rather than assuming format (#53559)" by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53568 -* Bump cross-spawn from 7.0.3 to 7.0.6 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/53569 -* [11.x] Removing unused var assignment in Illuminate Router by [@Carnicero90](https://github.com/Carnicero90) in https://github.com/laravel/framework/pull/53575 -* [11.x] PHP 8.4 Code compatibility by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53571 -* [11.x] Supports `laravel/serializable-closure` 2 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53552 -* [11.x] Use getQualifiedOwnerKeyName in relations by [@willtj](https://github.com/willtj) in https://github.com/laravel/framework/pull/53573 - -## [v11.32.0](https://github.com/laravel/framework/compare/v11.31.0...v11.32.0) - 2024-11-15 - -* [11.x] Http Client: fake connection exception by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/53485 -* [11.x] update the docblock of the runCommand method. by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53490 -* [11.x] Fix extensions of contextual bindings by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/53514 -* Update the docblock of the fluentCommands property in the MySqlGrammar by [@tisuchi](https://github.com/tisuchi) in https://github.com/laravel/framework/pull/53509 -* [11.x] Don't overwrite custom replacements for count in `trans_choice` by [@patrickrobrecht](https://github.com/patrickrobrecht) in https://github.com/laravel/framework/pull/53517 -* [11.x] Allow BackedEnum when asserting redirect-routes by [@wietsewarendorff](https://github.com/wietsewarendorff) in https://github.com/laravel/framework/pull/53498 -* Updates docblock for duplicates collection method to correct its return type by [@gms8994](https://github.com/gms8994) in https://github.com/laravel/framework/pull/53499 -* [11.x] Add support for syncing associations with array or base collection of models by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/53495 -* [11.x] SqsQueue pushRaw options: pass to sendMessage by [@Niush](https://github.com/Niush) in https://github.com/laravel/framework/pull/53507 -* [11.x] Allow BackedEnum when using redirectToRoute in ResponseFactory by [@wietsewarendorff](https://github.com/wietsewarendorff) in https://github.com/laravel/framework/pull/53518 -* Improve type saftey for Config/Repository.php by [@tisuchi](https://github.com/tisuchi) in https://github.com/laravel/framework/pull/53520 -* Fix issue where overwriting middleware variable when setting middleware priority by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/53504 -* [11.x] Introduce method `Blueprint::rawColumn()` by [@Jacobs63](https://github.com/Jacobs63) in https://github.com/laravel/framework/pull/53496 -* [11.x] Introduce Schedule Grouping by [@istiak-tridip](https://github.com/istiak-tridip) in https://github.com/laravel/framework/pull/53427 -* [11.x] Added generics to paginators by [@EranNL](https://github.com/EranNL) in https://github.com/laravel/framework/pull/53512 -* Fix `unless` code comment by [@fritz-c](https://github.com/fritz-c) in https://github.com/laravel/framework/pull/53529 -* [11.x] Add "head" slot to email layout by [@hivokas](https://github.com/hivokas) in https://github.com/laravel/framework/pull/53531 -* [11.x] Http client: record request when faking connection exception by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/53530 - -## [v11.31.0](https://github.com/laravel/framework/compare/v11.30.0...v11.31.0) - 2024-11-12 - -* [11.x] Refactor: return Command::FAILURE by [@fernandokbs](https://github.com/fernandokbs) in https://github.com/laravel/framework/pull/53354 -* Allow the Batch and Chain onQueue method to accept Backed Enums by [@onlime](https://github.com/onlime) in https://github.com/laravel/framework/pull/53359 -* Add transaction generics by [@MatusBoa](https://github.com/MatusBoa) in https://github.com/laravel/framework/pull/53357 -* Add laravel default exception blade files to view:cache by [@SamuelWei](https://github.com/SamuelWei) in https://github.com/laravel/framework/pull/53353 -* [11.x] Added `useCascadeTruncate` method for `PostgresGrammar` by [@korkoshko](https://github.com/korkoshko) in https://github.com/laravel/framework/pull/53343 -* Add Application::removeDeferredServices method by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/53362 -* Add the ability to append and prepend middleware priority from the application builder by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/53326 -* Fix typo in Translator code comment by [@caendesilva](https://github.com/caendesilva) in https://github.com/laravel/framework/pull/53366 -* [11.x] Handle HtmlString constructed with a null by [@sperelson](https://github.com/sperelson) in https://github.com/laravel/framework/pull/53367 -* [11.x] Add `URL::forceHttps()` to enforce HTTPS scheme for URLs by [@dasundev](https://github.com/dasundev) in https://github.com/laravel/framework/pull/53381 -* [11.x] Refactor and add remaining test cases for the DatabaseUuidFailedJobProviderTest class by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53408 -* [11.X] Postgres Aurora failover - DetectsLostConnections by [@vifer](https://github.com/vifer) in https://github.com/laravel/framework/pull/53404 -* `whereFullText` case consistency by [@parth391](https://github.com/parth391) in https://github.com/laravel/framework/pull/53395 -* [11.x] Add `HasFactory` trait to `make:model` generation command using `--all` options by [@adel007gh](https://github.com/adel007gh) in https://github.com/laravel/framework/pull/53391 -* Introduce support for popping items from a stackable context item by [@denjaland](https://github.com/denjaland) in https://github.com/laravel/framework/pull/53403 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53414 -* [11.x] Add ability to dynamically build mailers on-demand using `Mail::build` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53411 -* [11.x] Refactor and add remaining test cases for the DatabaseFailedJobProviderTest class by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53409 -* [11.x] Fix error event listener in Vite prefetching by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/53439 -* [11.x] Ensure datetime cache durations account for script execution time by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53431 -* [11.x] Fix fluent syntax for HasManyThrough when combining HasMany followed by HasOne by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/53335 -* Correct parameter type of Collection::diffKeys() and Collection::diffKeysUsing() by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/53441 -* Correct parameter type of Collection::intersectByKeys() by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/53444 -* Fix schema foreign ID support for tables with non-standard primary key by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/53442 -* [11.x] Cache token repository by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53428 -* Fix validation message when there is a parameter with escaped dot "." by [@mdmahbubhelal](https://github.com/mdmahbubhelal) in https://github.com/laravel/framework/pull/53416 -* [11.x] add optional prefix for cache key by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53448 -* [11.x] Do not overwrite existing link header(s) in `AddLinkHeadersForPreloadedAssets` middleware by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/53463 -* [11.x] use assertTrue and assertFalse method, instead of using assertE… by [@iamyusuf](https://github.com/iamyusuf) in https://github.com/laravel/framework/pull/53453 -* [11.x] Add `DB::build` method by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53464 -* [11.x] Add ability to dynamically build cache repositories on-demand using `Cache::build` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/53454 -* [11.x] Skip the number of connections transacting while testing to run callbacks by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/53377 - -## [v11.30.0](https://github.com/laravel/framework/compare/v11.29.0...v11.30.0) - 2024-10-30 - -* Add `$bind` parameter to `Blade::directive` by [@hossein-zare](https://github.com/hossein-zare) in https://github.com/laravel/framework/pull/53279 -* [11.x] Fix `trans_choice()` when translation replacement include `|` separator by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53331 -* [11.x] Allow the authorize method to accept Backed Enums directly by [@johanvanhelden](https://github.com/johanvanhelden) in https://github.com/laravel/framework/pull/53330 -* [11.x] use `exists()` instead of `count()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53328 -* [11.x] Docblock Improvements by [@mtlukaszczyk](https://github.com/mtlukaszczyk) in https://github.com/laravel/framework/pull/53325 -* Allow for custom Postgres operators to be added by [@boris-glumpler](https://github.com/boris-glumpler) in https://github.com/laravel/framework/pull/53324 -* [11.x] Support Optional Dimensions for `vector` Column Type by [@akr4m](https://github.com/akr4m) in https://github.com/laravel/framework/pull/53316 -* [11.x] Test Improvements by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/53306 -* [11.x] Added `dropColumnsIfExists`, `dropColumnIfExists` and `dropForeignIfExists` by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/53305 -* [11.x] Provide an error message for PostTooLargeException by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/53301 -* [11.x] Fix integrity constraint violation on failed_jobs_uuid_unique by [@bytestream](https://github.com/bytestream) in https://github.com/laravel/framework/pull/53264 -* Revert "[11.x] Added `dropColumnsIfExists`, `dropColumnIfExists` and `dropForeignIfExists`" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53338 -* [11.x] Introduce `HasUniqueStringIds` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53280 -* [11.x] Refactor: check for contextual attribute before getting parameter class name by [@korkoshko](https://github.com/korkoshko) in https://github.com/laravel/framework/pull/53339 -* [11.x] Pick up existing views and markdowns when creating mails by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53308 -* [11.x] Add withoutDefer and withDefer testing helpers by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53340 - -## [v11.29.0](https://github.com/laravel/framework/compare/v11.28.1...v11.29.0) - 2024-10-22 - -* [10.x] Ensure headers are only attached to illuminate responses by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53019 -* [11.x] Component name guessing with prefix by [@royduin](https://github.com/royduin) in https://github.com/laravel/framework/pull/53183 -* [11.x] Allow list of rate limiters without requiring unique keys by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53177 -* Add directive [@bool](https://github.com/bool) to Blade by [@david-valdivia](https://github.com/david-valdivia) in https://github.com/laravel/framework/pull/53179 -* [11.x] Fixes handling `Js::from(collect());` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53206 -* [11.x] fix PHPDoc for \Illuminate\Redis\Connections\Connection::$events by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/53211 -* [11.x] fix PHPDoc for \Illuminate\Database\Connection by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/53212 -* [11.x] Include class-string generics for Validator::$exception by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53210 -* [11.x] Remove a few useless return void statements. by [@lucasmichot](https://github.com/lucasmichot) in https://github.com/laravel/framework/pull/53225 -* [11.x] Fixes phpdoc type of Number::forHumans() by [@toarupg0318](https://github.com/toarupg0318) in https://github.com/laravel/framework/pull/53218 -* [11.x] Fix handling exceptions thrown in eval()'d code by [@jlabedo](https://github.com/jlabedo) in https://github.com/laravel/framework/pull/53204 -* [11.x] Allow using `castAsJson()` on non default db connection during test by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53256 -* Improve query builder tests by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53251 -* [11.x] Fix incorrect bindings in DB::update when using a collection as a value by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53254 -* fix: EloquentCollection find and unique generics by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/53239 -* [11.x] Add getConnection() Method to Factory Class for Retrieving Database Connection by [@jonathanpmartins](https://github.com/jonathanpmartins) in https://github.com/laravel/framework/pull/53237 -* [11.x] Add `waitUntil` method to `Process` by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/53236 -* Allow Vite entry points to be merged by [@JackWH](https://github.com/JackWH) in https://github.com/laravel/framework/pull/53233 -* [11.x] Add helper method to determine stray request prevention state by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/53232 -* [11.x] Fix typo `$previousLCurrency` to `$previousCurrency` for clarity and consistency by [@mdariftiens](https://github.com/mdariftiens) in https://github.com/laravel/framework/pull/53261 - -## [v11.28.1](https://github.com/laravel/framework/compare/v11.28.0...v11.28.1) - 2024-10-16 - -* [11.x] Fix trim getting discarded in `ViewMakeCommand` by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/53174 -* [11.x] Discard `PHP_CLI_SERVER_WORKERS` on Windows environment by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53178 -* [11.x] Improves PHP 8.4 compatibility by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53182 -* [11.x] Fix handling empty values passed to `enum_value()` function instead of only empty string by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53181 - -## [v11.28.0](https://github.com/laravel/framework/compare/v11.27.2...v11.28.0) - 2024-10-15 - -* [11.x] Update Authorizable methods with BackedEnum support by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/53079 -* [11.x] Use `null` as default cursor value for PHP Redis by [@jayan-blutui](https://github.com/jayan-blutui) in https://github.com/laravel/framework/pull/53095 -* [11.x] PHPDoc Improvements by [@schulerj89](https://github.com/schulerj89) in https://github.com/laravel/framework/pull/53097 -* [11.x] Fix resource not escaped correctly in substituteBindingsIntoRawSql() by [@aedart](https://github.com/aedart) in https://github.com/laravel/framework/pull/53100 -* [11.x] feat: add useful defaultLocale and defaultCurrency helpers to Number facade by [@sts-ryan-holton](https://github.com/sts-ryan-holton) in https://github.com/laravel/framework/pull/53101 -* [11.x] Fix determining pivot timestamp column name(s) when parent relation missing one or both of timestamps by [@daniser](https://github.com/daniser) in https://github.com/laravel/framework/pull/53103 -* [11.x] Add phpstan assertions for last in Collection isEmpty and isNotEmpty by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/53107 -* feat: interactive env:encrypt & env:decrypt by [@hhermsen](https://github.com/hhermsen) in https://github.com/laravel/framework/pull/53081 -* [11.x] PHPDoc Improvements by [@schulerj89](https://github.com/schulerj89) in https://github.com/laravel/framework/pull/53109 -* [11.x] Feat: remove HasFactory in model when not required by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/53104 -* [11.x] Add `Illuminate\Support\enum_value` to resolve `BackedEnum` or `UnitEnum` to scalar by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53096 -* [11.x] allow guessing of nested component by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/52669 -* [11.x] Introduce RouteParameter attribute by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/53080 -* [11.x] Refactored to use enum_value() in castBinding() by [@toarupg0318](https://github.com/toarupg0318) in https://github.com/laravel/framework/pull/53131 -* [11.x] Test Improvements remove code duplication by [@toarupg0318](https://github.com/toarupg0318) in https://github.com/laravel/framework/pull/53128 -* Revert "[11.x] Test Improvements remove code duplication" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53132 -* [11.x] Fix HasManyThrough::one() by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/53119 -* [11.x] Console supports Laravel Prompts 0.3+ by [@edjw](https://github.com/edjw) in https://github.com/laravel/framework/pull/53136 -* [11.x] PHPDoc Improvements by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/53139 -* fix: make model command with folder path - factory incorrect import path by [@JeRabix](https://github.com/JeRabix) in https://github.com/laravel/framework/pull/53142 -* [11.x] feat: refine return type for `throw_if` and `throw_unless` to reflect actual behavior with "falsey" values by [@crishoj](https://github.com/crishoj) in https://github.com/laravel/framework/pull/53154 -* [11.x] Ensure `where` with array respects boolean by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53147 -* [11.x] Gracefully handle null passwords when verifying credentials by [@gbradley](https://github.com/gbradley) in https://github.com/laravel/framework/pull/53156 -* [11.x] feat: restore type-narrowing bahavior for `throw_*` helpers by [@crishoj](https://github.com/crishoj) in https://github.com/laravel/framework/pull/53164 -* [11.x] Add CollectedBy attribute by [@alsterholm](https://github.com/alsterholm) in https://github.com/laravel/framework/pull/53122 -* [11.x] Add successful and failed methods to `ProcessPoolResults` by [@Riley19280](https://github.com/Riley19280) in https://github.com/laravel/framework/pull/53160 -* Issue with constrained() method used after foreignIdFor(), instead of table name when $table parameter is not passed uses column name by [@granitibrahimi](https://github.com/granitibrahimi) in https://github.com/laravel/framework/pull/53144 - -## [v11.27.2](https://github.com/laravel/framework/compare/v11.27.1...v11.27.2) - 2024-10-09 - -* [11.x] Fixes regression with `queue:work` Command by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53076 -* [11.x] Fixes parameter declaration for `ServiceProvider::optimizes()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53074 - -## [v11.27.1](https://github.com/laravel/framework/compare/v11.27.0...v11.27.1) - 2024-10-08 - -* [11.x] Fix border overflow on theme switcher when hovering by [@mezotv](https://github.com/mezotv) in https://github.com/laravel/framework/pull/53064 -* [11.x] Optimize commands registry by [@erikgaal](https://github.com/erikgaal) in https://github.com/laravel/framework/pull/52928 -* [11.x] Fix laravel/framework#53071 by [@it-can](https://github.com/it-can) in https://github.com/laravel/framework/pull/53072 - -## [v11.27.0](https://github.com/laravel/framework/compare/v11.26.0...v11.27.0) - 2024-10-08 - -* [11.x] feat: narrow types for throw_if and throw_unless by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/53005 -* [11.x] Prevent calling tries() twice by [@themsaid](https://github.com/themsaid) in https://github.com/laravel/framework/pull/53010 -* [11.x] Improve PHPDoc by [@schulerj89](https://github.com/schulerj89) in https://github.com/laravel/framework/pull/53009 -* [11.x] Utilise `Illuminate\Support\php_binary()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53008 -* [11.x] Set HasAttributes[@casts](https://github.com/casts)() array generics by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53024 -* [11.x] Improve `Schema::hasTable()` performance by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/53006 -* [11.x] Always inherit parent attributes by [@royduin](https://github.com/royduin) in https://github.com/laravel/framework/pull/53011 -* [11.x] feat: introduce option to change default Number currency by [@sts-ryan-holton](https://github.com/sts-ryan-holton) in https://github.com/laravel/framework/pull/53022 -* [11.x] feat: add Str::doesntContain() method and supporting tests by [@sts-ryan-holton](https://github.com/sts-ryan-holton) in https://github.com/laravel/framework/pull/53035 -* [11.x] Str: Add extension support for `Str::inlineMarkdown()` by [@ryangjchandler](https://github.com/ryangjchandler) in https://github.com/laravel/framework/pull/53033 -* Fix: Correct typehint on repository retrieval methods by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/53025 -* [11.x] Test for forgetting non-flexible keys for file driver by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53018 -* Add metadata to mailable view data by [@TobMoeller](https://github.com/TobMoeller) in https://github.com/laravel/framework/pull/53042 -* [11.x] PHPDoc Improvements by [@schulerj89](https://github.com/schulerj89) in https://github.com/laravel/framework/pull/53054 -* [11.x] Test Improvements by [@toarupg0318](https://github.com/toarupg0318) in https://github.com/laravel/framework/pull/53057 -* [11.x] PHPDoc Improvements by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/53053 -* Add Exception Handling for jsonOptions() Method by [@shamimulalam](https://github.com/shamimulalam) in https://github.com/laravel/framework/pull/53056 -* [11.x] Fixes `make:model` for Form Requests by [@joshmanders](https://github.com/joshmanders) in https://github.com/laravel/framework/pull/53052 -* [11.x] Fixes validation using `shouldConvertToBoolean` when parameter uses dot notation by [@bytestream](https://github.com/bytestream) in https://github.com/laravel/framework/pull/53048 -* [11.x] Add methods to the HTTP kernel to append middleware relative to other middleware by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/52897 -* [11.x] Add `--json` flag to `queue:work` command for structured logging by [@josecl](https://github.com/josecl) in https://github.com/laravel/framework/pull/52887 -* [11.x] Improve performance of Redis queue block_for when a worker has multiple queues to service by [@michael-scinocca](https://github.com/michael-scinocca) in https://github.com/laravel/framework/pull/52826 - -## [v11.26.0](https://github.com/laravel/framework/compare/v11.25.0...v11.26.0) - 2024-10-01 - -* [11.x] Fix PHPDoc typo by [@LucaRed](https://github.com/LucaRed) in https://github.com/laravel/framework/pull/52960 -* Add stop() method to Process and Pool by [@MiniCodeMonkey](https://github.com/MiniCodeMonkey) in https://github.com/laravel/framework/pull/52959 -* [11.x] Improve PHPDoc by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/52949 -* [11.x] Fix crash of method PreventsCircularRecursion::withoutRecursion() on mocked models by [@maximetassy](https://github.com/maximetassy) in https://github.com/laravel/framework/pull/52943 -* [11.x] Document callable types for `Enumerable::implode()` by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/52937 -* [11.x] Allows Unit & Backed Enums for registering named `RateLimiter` & `RateLimited` middleware by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/52935 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52933 -* [11.x] Fixes trust proxy `REMOTE_ADDR` not working in Swoole by [@chuoke](https://github.com/chuoke) in https://github.com/laravel/framework/pull/52889 -* [11.x] Fixes function loading conflicts when using `[@include](https://github.com/include)('vendor/autoload.php')` via Laravel Envoy by [@s-damian](https://github.com/s-damian) in https://github.com/laravel/framework/pull/52974 -* [11.x] Support Laravel Prompts 0.3+ by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52993 -* Allow mass assignment with mutators when using model::guarded by [@Apfelfrisch](https://github.com/Apfelfrisch) in https://github.com/laravel/framework/pull/52962 -* [11.x] Add `make:job-middleware` artisan command by [@dshafik](https://github.com/dshafik) in https://github.com/laravel/framework/pull/52965 -* [11.x] Auto discover Events outside app namespace when folder name is in kebab-case by [@xizprodev](https://github.com/xizprodev) in https://github.com/laravel/framework/pull/52976 -* [11.x] Feat: factory generic in make:model command by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/52855 - -## [v11.25.0](https://github.com/laravel/framework/compare/v11.24.1...v11.25.0) - 2024-09-26 - -* [11.x] Fix make:listener command by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/52924 -* [11.x] Fix incorrect PHPDoc for KeyBy and GroupBy by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/52918 -* [11.x] Fix PHPDoc for TestResponse's `Response` Type to \Symfony\Component\HttpFoundation\Response by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/52915 -* [11.x] Docblock Improvements by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/52909 -* [11.x] Add vector column support to migrations by [@Jim-Webfox](https://github.com/Jim-Webfox) in https://github.com/laravel/framework/pull/52884 -* [11.x] Revert auto-discovering `routes/console.php` as this will cause breaking change with the default `withRouting($console)` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52942 -* [11.x] Fixed docblock on typeVector method in Grammer by [@flavio-schoute](https://github.com/flavio-schoute) in https://github.com/laravel/framework/pull/52927 - -## [v11.24.1](https://github.com/laravel/framework/compare/v11.24.0...v11.24.1) - 2024-09-25 - -* [11.x] Fixes `defer()` function return type by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/52910 -* [11.x] Fixes missing `ExecutableFinder` import by [@InfinityXTech](https://github.com/InfinityXTech) in https://github.com/laravel/framework/pull/52912 -* [11.x] Fix for not automatically registering commands in App\Console\Commands by [@SamuelNitsche](https://github.com/SamuelNitsche) in https://github.com/laravel/framework/pull/52903 - -## [v11.24.0](https://github.com/laravel/framework/compare/v11.23.5...v11.24.0) - 2024-09-24 - -* [11.x] Fix issue where `$name` variable in non base config file becomes it's key by [@rojtjo](https://github.com/rojtjo) in https://github.com/laravel/framework/pull/52738 -* [11.x] PHPDoc Improvements by [@amitmerchant1990](https://github.com/amitmerchant1990) in https://github.com/laravel/framework/pull/52797 -* [11.x] Remove a unused import and fix docblock for DeferredCallbackCollection by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52808 -* [11.x] Handle SQLSTATE[HY000] [2002] Operation now in progress in DetectsLostConnection trait #52759 by [@webartisan10](https://github.com/webartisan10) in https://github.com/laravel/framework/pull/52805 -* [11.x] Add prependLocation method to View Factory by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/52806 -* [11.x] add nullOnUpdate() method to ForeignKeyDefinition by [@gisuNasr](https://github.com/gisuNasr) in https://github.com/laravel/framework/pull/52798 -* [11.x] Allow `BackedEnum` to be passed to `Route::can()` by [@Omegadela](https://github.com/Omegadela) in https://github.com/laravel/framework/pull/52792 -* [11.x] Ensure headers are only attached to illuminate responses by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52789 -* [11.x] feat: improve Collection groupBy, keyBy generics by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52787 -* [11.x] Using Correct `Concurrency` Configuration Index Name by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/52788 -* [11.x] Ensure `withoutPretending` method properly resets state after callback execution by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/52794 -* [11.x] delegate `ProcessDriver[@defer](https://github.com/defer)()` to `ProcessDriver[@run](https://github.com/run)()` method by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52807 -* [11.x] Use command string instead of array on `Concurrency\ProcessDriver` by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52813 -* [11.x] Allows Laravel Framework to correctly resolve PHP binary when running via Laravel Herd by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52791 -* [11.x] Move Defer classes to Support component and add `Illuminate\Support\defer` function by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52801 -* [11.x] Suggest `laravel/serializable-closure` on Database component by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52835 -* Bump vite from 5.2.10 to 5.2.14 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/52834 -* [11.x] Update Concurrency component's composer dependencies by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52836 -* Add result shorthands for `Process` fakes by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/52840 -* Update SerializesCastableAttributes to include array generics by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/52841 -* [11.x] CI Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52850 -* [11.x] Supports `laravel/prompts` v0.2 by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52849 -* [11.x] Handle allows null parameter instead of requiring default value by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52866 -* [11.x] Adds `[@throws](https://github.com/throws)` section to Concurrency manager doc block by [@rnambaale](https://github.com/rnambaale) in https://github.com/laravel/framework/pull/52856 -* Update stub to remove unused trait imports by [@lombervid](https://github.com/lombervid) in https://github.com/laravel/framework/pull/52877 -* [11.x] Fix validation rule type hints by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/52870 -* [11.x] Support multiple batch IDs when retrying failed batch jobs by [@skegel13](https://github.com/skegel13) in https://github.com/laravel/framework/pull/52873 -* [11.x] Remove unused namespaces from DatabaseInspectionCommand and LocalFileSystemAdapter by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/52868 -* [11.x] Auto-register commands in `routes/console.php` by [@SamuelNitsche](https://github.com/SamuelNitsche) in https://github.com/laravel/framework/pull/52867 -* [11.x] Prevent infinite recursion on `touchesParents()` for chaperoned models by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/52883 -* Bump rollup from 4.17.1 to 4.22.4 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/52892 -* [11.x] `Cache::flexible` improvements by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52891 -* [11.x] Add `\DateTimeInterface` and `\DateInterval` to type for `Cache::flexible()` by [@bram-pkg](https://github.com/bram-pkg) in https://github.com/laravel/framework/pull/52888 -* [11.x] CI Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52851 -* [11.x] Do not trigger missing translation key handling when checking existence of translation key by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52895 - -## [v11.23.5](https://github.com/laravel/framework/compare/v11.23.4...v11.23.5) - 2024-09-13 - -* allow recursive Model::withoutTimestamps calls by [@m1guelpf](https://github.com/m1guelpf) in https://github.com/laravel/framework/pull/52768 -* [11.x] Fixes out of memory issue running `route:cache` with ServeFile by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52781 - -## [v11.23.4](https://github.com/laravel/framework/compare/v11.23.2...v11.23.4) - 2024-09-12 - -* [10.x] Fixes `whereDate`, `whereDay`, `whereMonth`, `whereTime`, `whereYear` and `whereJsonLength` to ignore invalid `$operator` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52704 -* [11.x] Fixing Concurrency Facade Docblocks by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/52764 -* [11.x] add lazy default to when helper by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52747 -* Fix arguments passed to artisan commands that start with 'env' by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/52748 - -## [v11.23.2](https://github.com/laravel/framework/compare/v11.23.1...v11.23.2) - 2024-09-11 - -## [v11.23.1](https://github.com/laravel/framework/compare/v11.23.0...v11.23.1) - 2024-09-11 - -## [v11.23.0](https://github.com/laravel/framework/compare/v11.22.0...v11.23.0) - 2024-09-11 - -* [11.x] Fix $fail closure type in docblocks for validation rules by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/52644 -* [11.x] Add MSSQL 2017 and PGSQL 10 builds by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/52631 -* Update `everyThirtyMinutes` cron expression by [@SamuelNitsche](https://github.com/SamuelNitsche) in https://github.com/laravel/framework/pull/52662 -* Bump micromatch from 4.0.5 to 4.0.8 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/52664 -* [11.x] apply excludeUnvalidatedArrayKeys to list validation by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/52658 -* [11.x] Adding minRatio & maxRatio rules on Dimension validation ruleset by [@CamKem](https://github.com/CamKem) in https://github.com/laravel/framework/pull/52482 -* [11.x] Add BackedEnum support to Authorize middleware by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52679 -* [11.x] Add BackedEnum support to Gate methods by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52677 -* [11.x] Suggest serializable-closure by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/52673 -* [11.x] Fix alter table expressions on SQLite by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/52678 -* [11.x] Add Exceptions\Handler::mapLogLevel(...) so the logic can be easily overridden by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/52666 -* [11.x] Bugfix for calling pluck() on chaperoned relations. by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/52680 -* [11.x] Fix build failures due to enum collide After adding BackedEnum support to Gate by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52683 -* Fixing Str::trim to remove the default trim/ltrim/rtim characters " \n\r\t\v\0" by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/52684 -* [11.x] Add `Skip` middleware for Queue Jobs by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52645 -* [11.x] Fix etag headers for binary file responses by [@wouterrutgers](https://github.com/wouterrutgers) in https://github.com/laravel/framework/pull/52705 -* [11.x] add `withoutDelay()` to PendingDispatch by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52696 -* [11.x] Refactor `Container::getInstance()` to use null coalescing assignment by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/52693 -* [11.x] Removed unnecessary call to setAccessible(true) by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/52691 -* [11.x] Add `Eloquent\Collection::findOrFail` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/52690 -* [11.x] PHPStan Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52712 -* [11.x] Fix Collection PHPDoc by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/52724 -* [11.x] Add optional parameter for `confirmed` validator rule by [@jwpage](https://github.com/jwpage) in https://github.com/laravel/framework/pull/52722 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52718 -* [11.x] Fix incorrect variable-length argument `$guards` from array to string by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/52719 -* Allow testing of relative signed routes by [@shealavington](https://github.com/shealavington) in https://github.com/laravel/framework/pull/52726 -* [11.x] fix: Builder::with closure types by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52729 -* Laracon 2024 by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/52710 -* Add `Tag` attribute by [@TijmenWierenga](https://github.com/TijmenWierenga) in https://github.com/laravel/framework/pull/52743 -* [11.x] Adds BackedEnum to PendingDispatch's phpDoc for onQueue, allOnQueue, onConnection, allOnConnection methods by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/52739 -* New when() helper. by [@danmatthews](https://github.com/danmatthews) in https://github.com/laravel/framework/pull/52665 -* [11.x] Add `fromUrl()` to Attachment by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52688 - -## [v11.22.0](https://github.com/laravel/framework/compare/v11.21.0...v11.22.0) - 2024-09-03 - -* [11.x] Fix FoundationServiceProvider docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52542 -* [11.x] Fix ReflectionParameter [@param](https://github.com/param) type on Util::getContextualAttributeFromDependency() by [@samsonasik](https://github.com/samsonasik) in https://github.com/laravel/framework/pull/52541 -* [11.x] More specific parameter type in CastsInboundAttributes by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/52536 -* [11.x] Unify prefetch API by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52550 -* [11.x] Add PDO subclass support for PHP 8.4 by [@ju5t](https://github.com/ju5t) in https://github.com/laravel/framework/pull/52538 -* [11.x] Handle circular references in model serialization by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/52461 -* [11.x] Eloquent inverse relations by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/51582 -* [11.x] Feature/whereany closures by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/52555 -* [11.x] Update remaining workflows to run on latest possible ubuntu version by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/52566 -* Correct comments to better represent the updated method functionality by [@dropweb](https://github.com/dropweb) in https://github.com/laravel/framework/pull/52564 -* [11.x] Support CSP nonce by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52558 -* [11.x] Allow enums to be passed to routes by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/52561 -* [11.x] SORT_NATURAL on Collection no longer throws warning for nulls by [@Chaplinski](https://github.com/Chaplinski) in https://github.com/laravel/framework/pull/52557 -* [11.x] Allow prefetch to start on custom event by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52574 -* [11.x] Fix regression in database assertions with custom model connections by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/52581 -* [11] Update DetectsLostConnections.php by [@webartisan10](https://github.com/webartisan10) in https://github.com/laravel/framework/pull/52614 -* Fix docblock for `Model::getEventDispatcher()` by [@inmula](https://github.com/inmula) in https://github.com/laravel/framework/pull/52602 -* [11.x] Restore Request::HEADER_X_FORWARDED_PREFIX in TrustProxies by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/52598 -* [11.x] Accepts BackedEnum for onQueue, onConnection, allOnQueue, and allOnConnection methods in the Queueable trait by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/52604 -* [11.x] Use the same parameter type for 'throwUnless' as used for 'throwIf' by [@pataar](https://github.com/pataar) in https://github.com/laravel/framework/pull/52626 -* [11.x] Pass iterable keys to `withProgressBar` in InteractsWithIO by [@robinmoisson](https://github.com/robinmoisson) in https://github.com/laravel/framework/pull/52623 -* [11.x] Fix docblock for Filesystem::hash() by [@sunaoka](https://github.com/sunaoka) in https://github.com/laravel/framework/pull/52630 -* Fix Apostrophe Handling in SeeInOrder.php and Enhance Test Coverage by [@nomitoor](https://github.com/nomitoor) in https://github.com/laravel/framework/pull/52627 -* [11.x] SQLite Error: "General error: 1 no such table" after adding a foreign key when using a table prefix. by [@incrize](https://github.com/incrize) in https://github.com/laravel/framework/pull/52578 - -## [v11.21.0](https://github.com/laravel/framework/compare/v11.20.0...v11.21.0) - 2024-08-20 - -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52402 -* [11.x] Fix docblock for the event dispatcher by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52411 -* [11.x] fix: Update text email template by [@tranvanhieu01012002](https://github.com/tranvanhieu01012002) in https://github.com/laravel/framework/pull/52417 -* [11.x] Make `expectsChoice` assertion more intuitive with associative arrays. by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/52408 -* [11.x] Add `resource()` method to Illuminate\Http\Client\Response by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52412 -* [10.x] fix: prevent casting empty string to array from triggering json error by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52415 -* [11.x] Add ResponseInterface mixin to `Illuminate\Http\Client\Response` by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52410 -* [11.x] Don't touch BelongsTo relationship when it doesn't exist by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/52407 -* [11.x] Fix `Factory::afterCreating` callable argument type by [@villfa](https://github.com/villfa) in https://github.com/laravel/framework/pull/52424 -* [11.x] Auto-secure cookies by [@fabricecw](https://github.com/fabricecw) in https://github.com/laravel/framework/pull/52422 -* fix: add missing phpdoc types for Model::$table and Model::$dateFormat by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/52425 -* [11.x] Add `withoutHeaders` method by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/52435 -* Checking availability before calling Log::flushSharedContext() method by [@ajaxray](https://github.com/ajaxray) in https://github.com/laravel/framework/pull/52470 -* [11.x] MessageBag errors out when custom rules are created and the class is left out of the message array by [@DanteB918](https://github.com/DanteB918) in https://github.com/laravel/framework/pull/52451 -* Create Notification make command markdown name placeholder from Notif… by [@hosseinakbari-liefermia](https://github.com/hosseinakbari-liefermia) in https://github.com/laravel/framework/pull/52465 -* [11.x] Add `forceDestroy` to `SoftDeletes` by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/52432 -* Make SQLiteProcessor cope with '/' in column names by [@vroomfondle](https://github.com/vroomfondle) in https://github.com/laravel/framework/pull/52490 -* [11.x] Improve Cookie Testing Coverage by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/52472 -* [11.x] Fix for #52436 artisan schema:dump infinite recursion by [@rust17](https://github.com/rust17) in https://github.com/laravel/framework/pull/52492 -* Run prepareNestedBatches on append/prependToChain & chain by [@SabatinoMasala](https://github.com/SabatinoMasala) in https://github.com/laravel/framework/pull/52486 -* [11.x] Enhance DB inspection commands by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/52501 -* [11.x] Constrain key when asserting database has a model by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/52464 -* Add `between` to `AssertableJson` by [@rudashi](https://github.com/rudashi) in https://github.com/laravel/framework/pull/52479 -* [11.x] Eager asset prefetching strategies for Vite by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52462 -* [11.x] Support attributes in `app()->call()` by [@innocenzi](https://github.com/innocenzi) in https://github.com/laravel/framework/pull/52428 -* [11.x] Applying `value` Function into the `$default` value of `transform` helper by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/52510 -* [11.x] Enhanced typing for `HigherOrderCollectionProxy` by [@Voltra](https://github.com/Voltra) in https://github.com/laravel/framework/pull/52484 -* [11.x] Add `expectsSearch()` assertion for testing prompts that use `search()` and `multisearch()` functions by [@JayBizzle](https://github.com/JayBizzle) in https://github.com/laravel/framework/pull/51669 -* [11.x] revert #52510 which added a unneeded function call by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52526 - -## [v11.20.0](https://github.com/laravel/framework/compare/v11.19.0...v11.20.0) - 2024-08-06 - -* Update testcase for `whereNone` method by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52351 -* Improve `Lock->block` method by [@RedmarBakker](https://github.com/RedmarBakker) in https://github.com/laravel/framework/pull/52349 -* [11.x] Use correct pluralization rules in trans_choice for fallback strings by [@stefanvdlugt](https://github.com/stefanvdlugt) in https://github.com/laravel/framework/pull/52343 -* [11.x] Replace dead link in Security Policy by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/52338 -* Add compatible query type to `Model::resolveRouteBindingQuery` by [@sebj54](https://github.com/sebj54) in https://github.com/laravel/framework/pull/52339 -* [10.x] Fix `Factory::afterCreating` callable argument type by [@villfa](https://github.com/villfa) in https://github.com/laravel/framework/pull/52335 -* [11.x] Remove undefined class PreventRequestsDuringMaintenance by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52322 -* [11.x] Add middleware before sending request and dispatching events by [@eduance](https://github.com/eduance) in https://github.com/laravel/framework/pull/52323 -* Add `collapseWithKeys` to `Collection` by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/52347 -* [11.x] Inverse Fake Queue Interactions: `assertNotDeleted`, `assertNotFailed`, and `assertNotReleased` by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52320 -* Add `deduplicate` to strings by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/52350 -* [11.x] feat: make `Facade::isFake()` public by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52357 -* [11.x] Ask about markdown template for notification command with no initial input by [@christophrumpel](https://github.com/christophrumpel) in https://github.com/laravel/framework/pull/52355 -* [11.x] allow custom view path when making components by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/52219 -* [11.x] chore: update to PHPStan Level 1 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51956 -* [11.x] Support passing default as named parameter in whenLoaded, whenAggregated, whenCounted by [@hn-seoai](https://github.com/hn-seoai) in https://github.com/laravel/framework/pull/51342 -* Declare exceptions unreportable using the ShouldntReport interface by [@chrispage1](https://github.com/chrispage1) in https://github.com/laravel/framework/pull/52337 -* [11.x] Enable extension of connection inspection methods by [@GromNaN](https://github.com/GromNaN) in https://github.com/laravel/framework/pull/52231 -* [11.x] Add `whenExistsLoaded` method to conditionally include relationship existence attribute by [@CodeWithKyrian](https://github.com/CodeWithKyrian) in https://github.com/laravel/framework/pull/52295 -* [11.x] Add `in()` and `inHidden()` functions to Context Stacks by [@lessevv](https://github.com/lessevv) in https://github.com/laravel/framework/pull/52346 -* [11.x] Use Command::fail() method for single error messages by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52387 -* [11.x] Rework `Context::stackContains` with Closures. by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52381 -* [11.x] Allow enums to be passed to AssertableJson where methods by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/52360 -* [11.x] Made `list` validation rule as array for "size rules" in validation messages by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/framework/pull/52385 -* [11.x] Add contextual attributes to resolve drivers by [@ziadoz](https://github.com/ziadoz) in https://github.com/laravel/framework/pull/52265 -* [11.x] Fix docblocks for where(All|Any|None) query methods by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52388 -* [10.x] backport #52204 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52389 -* [11.x] Fix Http Client Pool requests that have no response by [@andrewbroberg](https://github.com/andrewbroberg) in https://github.com/laravel/framework/pull/52393 -* [11.x] Introduce MixFileNotFoundException for handling missing Mix files by [@Ex10Dios](https://github.com/Ex10Dios) in https://github.com/laravel/framework/pull/52400 -* [10.x] In MySQL, harvest last insert ID immediately after query is executed by [@piurafunk](https://github.com/piurafunk) in https://github.com/laravel/framework/pull/52390 - -## [v11.19.0](https://github.com/laravel/framework/compare/v11.18.1...v11.19.0) - 2024-07-30 - -* fix [@return](https://github.com/return) typehint in Illuminate\Contracts\Process\InvokedProcess::wait method by [@mdmahbubhelal](https://github.com/mdmahbubhelal) in https://github.com/laravel/framework/pull/52304 -* [11.x] Add php doc for ServiceProvider bindings and singletons properties by [@Anton5360](https://github.com/Anton5360) in https://github.com/laravel/framework/pull/52298 -* [10.x] backport #52188 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52293 -* Update docblock to accept an Expression for whereLike methods by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/52299 -* [11.x] Fix Bcrypt/Argon/Argon2I Hashers not checking database field for nullish value before checking hash compatibility by [@localpath](https://github.com/localpath) in https://github.com/laravel/framework/pull/52297 -* [11.x] Method to trim '0' digits after decimal point of a given number by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52284 -* [11.x] Add `whereNone` method to the query builder by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52260 -* [11.x] Fix flat array parameter for relation upsert by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/52289 -* [11.x] `assertSeeHtml`, `assertDontSeeHtml` and `assertSeeHtmlInOrder` testing methods by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52285 -* [11.x] Fully qualify morph columns when using WHERE clauses by [@maartenpaauw](https://github.com/maartenpaauw) in https://github.com/laravel/framework/pull/52227 -* [10.x] Fix runPaginationCountQuery not working properly for union queries by [@chinleung](https://github.com/chinleung) in https://github.com/laravel/framework/pull/52314 -* [11.x] Add `assertExactJsonStructure` method by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52311 -* [11.x] Add `withoutHeader()` test method by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52309 -* [11.x] Widen typehints in base service provider by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/52308 -* [11.X] "Model::preventAccessingMissingAttributes()" Causes Exception During Pagination with ResourceCollection by [@Katalam](https://github.com/Katalam) in https://github.com/laravel/framework/pull/52305 -* [11.x] Fixes through() relationship by [@leobeal](https://github.com/leobeal) in https://github.com/laravel/framework/pull/52318 -* [11.x] Add new `success` Method to the Docblock of `Illuminate\Console\View\Components\Factory` by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/52310 -* [11.x] Fix tests in wrong file by [@christophrumpel](https://github.com/christophrumpel) in https://github.com/laravel/framework/pull/52329 - -## [v11.18.1](https://github.com/laravel/framework/compare/v11.18.0...v11.18.1) - 2024-07-26 - -* [11.x] Fix variable typo at Terminating Event test by [@chu121su12](https://github.com/chu121su12) in https://github.com/laravel/framework/pull/52282 -* Revert "[11.x] Declare bindings and singletons properties in Service Provider" by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/52288 - -## [v11.18.0](https://github.com/laravel/framework/compare/v11.17.0...v11.18.0) - 2024-07-26 - -* Added completeWords flag to limit str method by [@itsmewes](https://github.com/itsmewes) in https://github.com/laravel/framework/pull/52245 -* [11.x] Fix missing * in phpdoc by [@pb30](https://github.com/pb30) in https://github.com/laravel/framework/pull/52277 -* [11.x] Fix SQLite schema dumps missing most tables by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/52275 -* [11.x] Access dispatchedBatches via BusFake by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52270 -* [11.x] Adds terminating event by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52259 -* [11.x] Declare bindings and singletons properties in Service Provider by [@Anton5360](https://github.com/Anton5360) in https://github.com/laravel/framework/pull/52256 -* [11.x] Fix explicit route binding for broadcast routes by [@ccharz](https://github.com/ccharz) in https://github.com/laravel/framework/pull/52280 -* Revert "[11.x] Allow non-`ContextualAttribute` attributes to have an `after` callback" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/52281 -* [11.x] Apply relation constraitns on upsert by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/52239 - -## [v11.17.0](https://github.com/laravel/framework/compare/v11.16.0...v11.17.0) - 2024-07-23 - -* [10.x] Fix PHP_CLI_SERVER_WORKERS warning by suppressing it by [@pelomedusa](https://github.com/pelomedusa) in https://github.com/laravel/framework/pull/52094 -* [11.x] Use `Command::FAILURE` for `db:wipe` command by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/framework/pull/52152 -* [11.x] Update outdated config files by [@TENIOS](https://github.com/TENIOS) in https://github.com/laravel/framework/pull/52150 -* [11.x] Fix 'pushProcessor method not found on LoggerInterface' error by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/52117 -* [11.x] Use `Command::FAILURE` for `migrate:fresh` command by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/framework/pull/52153 -* Improve accuracy of `Collection::isEmpty` and `isNotEmpty` assertions by [@spawnia](https://github.com/spawnia) in https://github.com/laravel/framework/pull/52184 -* [11.x] Fix return for ApplicationBuilder:: withCommandRouting method by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52181 -* [11.x] Refactor: Replace get_called_class() with static::class for consistency by [@fernandokbs](https://github.com/fernandokbs) in https://github.com/laravel/framework/pull/52173 -* [11.x] Improve readability of SQLite schema dumps by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/52172 -* [11.x] Allow non-`ContextualAttribute` attributes to have an `after` callback by [@innocenzi](https://github.com/innocenzi) in https://github.com/laravel/framework/pull/52167 -* [11.x] Ignoring column definitions when determining if a blueprint has a create command by [@kingsven](https://github.com/kingsven) in https://github.com/laravel/framework/pull/52177 -* Add specify exceptions for exceptions handling the vite manifest file by [@SamuelWei](https://github.com/SamuelWei) in https://github.com/laravel/framework/pull/52169 -* [11.x] fix: Model newCollection generics; feat: add HasCollection trait by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52171 -* Add whereLike clause to query builder by [@einar-hansen](https://github.com/einar-hansen) in https://github.com/laravel/framework/pull/52147 -* [11.x] Implement HasV7Uuids to use with MariaDB native uuid data type by [@Karem-sobhy](https://github.com/Karem-sobhy) in https://github.com/laravel/framework/pull/52029 -* [11.x] Rename `Model::$collection` to `$collectionClass` by [@GromNaN](https://github.com/GromNaN) in https://github.com/laravel/framework/pull/52186 -* [11.x] Allow microsecond travel by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52190 -* [11.x] fix: Model/JsonResource::toJson should not fail with prior json errors by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52188 -* [11.x] Fix SQL Server tests by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/52222 -* [11.x] Inspect exception of assertThrows by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/52224 -* [10.x] Backport #51615 by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/52215 -* [11.x] fix: Request::json() json errors when decoding empty string by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52204 -* [11.x] Reduce the number of queries with `Cache::many` and `Cache::putMany` methods in the database driver by [@tonysm](https://github.com/tonysm) in https://github.com/laravel/framework/pull/52209 -* Add method `QueryExecuted::toRawSql()` by [@spawnia](https://github.com/spawnia) in https://github.com/laravel/framework/pull/52192 -* [11.x] Support lower version of Carbon by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52233 -* [11.x] Prevent bug (🐛) emoji on `Collection`/`Dumpable` `dd` method by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/52234 - -## [v11.16.0](https://github.com/laravel/framework/compare/v11.15.0...v11.16.0) - 2024-07-16 - -* [11.x] Fix expected/actual argument order for test assertion by [@riesjart](https://github.com/riesjart) in https://github.com/laravel/framework/pull/52084 -* [11.x] Fix Moving Files in Sorted Order in vendor:publish by [@lmottasin](https://github.com/lmottasin) in https://github.com/laravel/framework/pull/52078 -* [11.x] Fix docblock for \Illuminate\Validation\ClosureValidationRule::message() by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52077 -* [11.x] Allow passing Enum casts to `Rule::enum()->only()` and `->except()` by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/52073 -* [11.x] Include ConnectionException in ConnectionFailed events by [@alexbowers](https://github.com/alexbowers) in https://github.com/laravel/framework/pull/52069 -* [11.x] Document returned array shape for sync methods by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/52070 -* [11.x] Add ability to configure SQLite `busy_timeout`, `journal_mode`, and `synchronous` pragmas by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/52052 -* [11.x] Allow view content dependent mail callbacks by [@MaxTingle](https://github.com/MaxTingle) in https://github.com/laravel/framework/pull/51990 -* Install Laravel Reverb version 1.0 instead of [@beta](https://github.com/beta) by [@lucasRolff](https://github.com/lucasRolff) in https://github.com/laravel/framework/pull/52096 -* [11.x] fix: dont use web middleware on health endpoint by [@joshmanders](https://github.com/joshmanders) in https://github.com/laravel/framework/pull/52088 -* [11.x] Add an option to replace configs recursively by [@felixbessler](https://github.com/felixbessler) in https://github.com/laravel/framework/pull/52087 -* [11.x] Fixes generator tests by [@buismaarten](https://github.com/buismaarten) in https://github.com/laravel/framework/pull/52118 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52122 -* [11.x] Fix SQLite schema dumps containing internal `sqlite_*` objects by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/52135 -* Fix typo in `` declaration by [@TENIOS](https://github.com/TENIOS) in https://github.com/laravel/framework/pull/52134 -* [11.x] fix: pluck generics by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52109 -* [11.x] Include 'success' console component by [@lewislarsen](https://github.com/lewislarsen) in https://github.com/laravel/framework/pull/52112 -* [11.x] Fix dumping migrations table with schema or prefixed name by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/52098 -* Add `assertSentTo` shorthand by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/52083 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52139 -* [11.x] Update the docblock for the constructor of the FileFailedJobProvider class by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/52149 -* [11.x] Update the docblock for the explode method of the Stringable class. by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/52148 -* Refactor PHPStan configurations by [@TENIOS](https://github.com/TENIOS) in https://github.com/laravel/framework/pull/52145 -* [11.x] Fix docblock for RoutingServiceProvider by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52144 - -## [v11.15.0](https://github.com/laravel/framework/compare/v11.14.0...v11.15.0) - 2024-07-09 - -* [10.x] Set previous exception on `HttpResponseException` by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51986 -* [11.x] feat: add generics to Eloquent Builder and Relations by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51851 -* add phpstan assertions to Collection isEmpty and isNotEmpty by [@johanrosenson](https://github.com/johanrosenson) in https://github.com/laravel/framework/pull/51998 -* [11.x] Add support for mime types in Resend mail transport by [@jayanratna](https://github.com/jayanratna) in https://github.com/laravel/framework/pull/52006 -* [11.x] feat: add virtual methods to SoftDeletes trait by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52001 -* [11.x] Fix service container docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52000 -* [10.x] Fix Http::retry so that throw is respected for call signature Http::retry([1,2], throw: false) by [@paulyoungnb](https://github.com/paulyoungnb) in https://github.com/laravel/framework/pull/52002 -* [10.x] Set application_name and character set as PostgreSQL DSN string by [@sunaoka](https://github.com/sunaoka) in https://github.com/laravel/framework/pull/51985 -* [11.x] Fix GeneratorCommand docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52014 -* [11.x] Enhance database migrations by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51373 -* [11.x] Run MySQL 9 Database Integration Tests nightly by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/52027 -* [11.x] Enhance doc blocks of the Migrator class by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/52033 -* [11.x] Use nullsafe operator for event dispatcher by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52024 -* [11.x] Fix PasswordBroker constructor docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52023 -* [11.x] Add test `testMultiplyIsLazy` to ensure LazyCollection's `multiply` method's lazy behaviour by [@lmottasin](https://github.com/lmottasin) in https://github.com/laravel/framework/pull/52020 -* [11.x] Allow `MultipleInstanceManager` to have studly creators by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/52030 -* [11.x] Adds `$config` property to `MultipleInstanceManager` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/52028 -* [11.x] fix: findOr and firstOr generics by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52037 -* [11.x] Make `Router` `Tappable` by [@mabdullahsari](https://github.com/mabdullahsari) in https://github.com/laravel/framework/pull/52051 -* [11.x] feat: improve Factory generics, add generics to HasFactory by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52005 -* [11.x] Ask About View Next To Name For Create Mail Command by [@christophrumpel](https://github.com/christophrumpel) in https://github.com/laravel/framework/pull/52057 -* [11.x] Added [@throws](https://github.com/throws) docblock for `block` method for `LockTimeoutException` by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/framework/pull/52063 - -## [v11.14.0](https://github.com/laravel/framework/compare/v11.13.0...v11.14.0) - 2024-07-02 - -* Adding Pest stubs to publish command by [@bartdenhoed](https://github.com/bartdenhoed) in https://github.com/laravel/framework/pull/51933 -* [11.x] Added attempts() method to FakeJob by [@JamesFreeman](https://github.com/JamesFreeman) in https://github.com/laravel/framework/pull/51951 -* [11.x] Run all Workflows on Ubuntu 24.04 by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/51946 -* [11.x] Improve PHPDoc for `mapSpread` Method in `Arr` Class & Remove Warning from IDE by [@lmottasin](https://github.com/lmottasin) in https://github.com/laravel/framework/pull/51952 -* Bump braces from 3.0.2 to 3.0.3 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/51955 -* [11.x] Remove unreachable code in AssertableJsonString by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51943 -* [11.x] Fix TestResponseAssert docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51942 -* [11.x] feat: add more specific types and tests for helpers by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51938 -* [11.x] Mark sensitive params with `SensitiveParameter` attribute by [@onlime](https://github.com/onlime) in https://github.com/laravel/framework/pull/51940 -* [11.x] Adds support for Markdown extensions to the `Stringable` class. by [@lukeraymonddowning](https://github.com/lukeraymonddowning) in https://github.com/laravel/framework/pull/51932 -* [11.x] Add secret method declaration to Components\Factory class by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51949 -* [11.x] Run Workflows on Windows 2022 and with bash instead of powershell by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/51958 -* [11.x] Fix duplicated return type PHPDoc by [@chu121su12](https://github.com/chu121su12) in https://github.com/laravel/framework/pull/51965 -* [11.x] Fix test failure message by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/51974 -* [11.x] Update tests to ensure mail Message implements the fluent interface pattern by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51969 -* [11.x] Set previous exception on `HttpResponseException` by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51968 -* [11.x] Fix typo in SupportCollectionTest by [@zbundy](https://github.com/zbundy) in https://github.com/laravel/framework/pull/51966 -* [11.x] Improvements for the ServeCommand (add more loves & elevate DX) by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/51957 -* [11.x] Adds support for using castAsJson with a MariaDb connection by [@haniha](https://github.com/haniha) in https://github.com/laravel/framework/pull/51963 -* [11.x] Add support for acting on attributes through container by [@innocenzi](https://github.com/innocenzi) in https://github.com/laravel/framework/pull/51934 -* [11.x] Fix Component::resolveComponentsUsing test by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51988 -* [11.x] Update composer.json files to provide PSR implementations by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51983 -* [11.x] add queued closure type for soft delete events by [@hpiaia](https://github.com/hpiaia) in https://github.com/laravel/framework/pull/51982 -* [11.x] Fix using container nesting to make the same 'abstract' in different context by [@guiqibusixin](https://github.com/guiqibusixin) in https://github.com/laravel/framework/pull/51989 -* [11.x] Fix sync is running touch query twice by [@Tofandel](https://github.com/Tofandel) in https://github.com/laravel/framework/pull/51984 - -## [v11.13.0](https://github.com/laravel/framework/compare/v11.12.0...v11.13.0) - 2024-06-27 - -* [11.x] Add Support for Extensions in Str::markdown Method by [@tnylea](https://github.com/tnylea) in https://github.com/laravel/framework/pull/51907 -* [11.x] Update config:show command by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51902 -* [11.x] Fix console prompt docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51913 -* [11.x] Fix prohibit docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51916 -* [11.x] Mark `$queue` as nullable by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/51912 -* use `Macroable` trait on TokenGuard by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/51922 -* [11.x] Update Command::fail() dockblock and tests by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51914 -* Revert and add test by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/51924 -* [11.x] Display view creation messages by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/51925 -* [11.x] Introduce `Str::chopStart` and `Str::chopEnd` by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/51910 -* feat: Add Number::pairs by [@hotmeteor](https://github.com/hotmeteor) in https://github.com/laravel/framework/pull/51904 -* [11.x] Fixes escaping path via Process given commands as array by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51926 -* [11.x] Make MultipleInstanceManager driver field customizable by [@princejohnsantillan](https://github.com/princejohnsantillan) in https://github.com/laravel/framework/pull/51905 -* [11.x] Account for long strings on new Laravel error page by [@shengslogar](https://github.com/shengslogar) in https://github.com/laravel/framework/pull/51880 - -## [v11.12.0](https://github.com/laravel/framework/compare/v11.11.1...v11.12.0) - 2024-06-25 - -* [10.x] Fix typo in return comment of createSesTransport method by [@zds-s](https://github.com/zds-s) in https://github.com/laravel/framework/pull/51688 -* [10.x] Fix collection shift less than one item by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51686 -* [10.x] Turn `Enumerable unless()` $callback parameter optional by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51701 -* Revert "[10.x] Turn `Enumerable unless()` $callback parameter optional" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/51707 -* [10.x] Fixes unable to call another command as a initialized instance of `Command` class by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51824 -* [10.x] fix handle `shift()` on an empty collection by [@Treggats](https://github.com/Treggats) in https://github.com/laravel/framework/pull/51841 -* [10.x] Ensure`schema:dump` will dump the migrations table only if it exists by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/51827 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51847 -* [11.x] Test application storage path by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51848 -* [11.x] Fix PHP_MAXPATHLEN check for strings slightly smaller then PHP_MAXPATHLEN by [@joshuaruesweg](https://github.com/joshuaruesweg) in https://github.com/laravel/framework/pull/51850 -* [11.x] Improve Bus::assertNothingDispatched(), Event::assertNothingDispatched(), Mail::assertNothingSent(), Notification::assertNothingSent() error messages by [@macbookandrew](https://github.com/macbookandrew) in https://github.com/laravel/framework/pull/51846 -* [11.x] Update error page to show GET by [@chu121su12](https://github.com/chu121su12) in https://github.com/laravel/framework/pull/51837 -* [11.x] Remove deprecated `type` attributes in the exception renderer by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/51866 -* [11.x] Import classes in the exception templates by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/51863 -* [11.x] Collection before/after optimization by [@bert-w](https://github.com/bert-w) in https://github.com/laravel/framework/pull/51876 -* [11.x] Add multiply to collection by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/51870 -* [11.x] Add addEventDiscoveryPaths to EventServiceProvider by [@ya-cha](https://github.com/ya-cha) in https://github.com/laravel/framework/pull/51896 -* [11.x] Fix validation attributes when translations are empty or missing by [@owenandrews](https://github.com/owenandrews) in https://github.com/laravel/framework/pull/51890 -* [11.x] feat: add generics to tap() helper by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51881 - -## [v11.11.1](https://github.com/laravel/framework/compare/v11.11.0...v11.11.1) - 2024-06-20 - -* [11.x] Remove useless variable assignment by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51838 -* [11.x] Fix event dispatcher typing in cache repository by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/51835 -* Chop PHP extension when passed to `make` commands by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/51842 -* [11.x] Simplify `.php` extension chopping in `getNameInput` by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/51843 -* [11.x] fix: improve performance and robustness of Relation::getMorphAlias() by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51845 -* Revert "[11.x] Change scope for `afterCreating` and `afterMaking` callbacks" by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/51858 - -## [v11.11.0](https://github.com/laravel/framework/compare/v11.10.0...v11.11.0) - 2024-06-18 - -* [11.x] Add `get`, `write` and `forget` cache events by [@stayallive](https://github.com/stayallive) in https://github.com/laravel/framework/pull/51560 -* [11.x] Add test for Arr::sortRecursiveDesc() method. by [@lmottasin](https://github.com/lmottasin) in https://github.com/laravel/framework/pull/51716 -* [11.x] Fix missing table name in `db:table` command by [@benholmen](https://github.com/benholmen) in https://github.com/laravel/framework/pull/51710 -* Ensure files exist for `install:broadcasting` by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/51719 -* [11.x] Restore exceptions/errors to test assertion failure messages by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/51725 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51723 -* [11.x] Add tests for accessible and take method by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51724 -* Increment the `totalJobs` property for the `BatchFake` when add some jobs by [@yankewei](https://github.com/yankewei) in https://github.com/laravel/framework/pull/51742 -* [11.x] Give session ID retrieval the Laravel treatment by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/51732 -* [11.x] Fix the chunk method to an integer type in the splitIn method by [@rookiexxk](https://github.com/rookiexxk) in https://github.com/laravel/framework/pull/51733 -* Update:update name method and doc by [@mehdi-fathi](https://github.com/mehdi-fathi) in https://github.com/laravel/framework/pull/51744 -* [11.x] Fixes `config:publish` with `dontMergeFrameworkConfiguration()` set to `true` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51751 -* Updated phpdoc for Builder::from() by [@boris-glumpler](https://github.com/boris-glumpler) in https://github.com/laravel/framework/pull/51767 -* [11.x] Fixed pop on default Beankstalkd queue when not specifically added by [@rinocs](https://github.com/rinocs) in https://github.com/laravel/framework/pull/51759 -* [11.x] Add `before` and `after` methods to Collection by [@avosalmon](https://github.com/avosalmon) in https://github.com/laravel/framework/pull/51752 -* [11.x] Change scope for `afterCreating` and `afterMaking` callbacks by [@jacob418](https://github.com/jacob418) in https://github.com/laravel/framework/pull/51772 -* Use numeric literal separator in file rule validation by [@AmirKhalifehSoltani](https://github.com/AmirKhalifehSoltani) in https://github.com/laravel/framework/pull/51781 -* [11.x] Import Model class for Renderer\Exception by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51778 -* [11.x] About command improvement by [@AmirKhalifehSoltani](https://github.com/AmirKhalifehSoltani) in https://github.com/laravel/framework/pull/51791 -* [11.x] Test abort behavior by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51800 -* [11.x] Container shares fixed values/initialized instances instead of singleton closure resolutions by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51804 -* [11.x] Fix altering a table that has a column with `default 0` on SQLite by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51803 -* [11.x] Fix typo in `VendorPublishCommand` by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/51812 -* [11.x] Fix some typos in the tests by [@tamiroh](https://github.com/tamiroh) in https://github.com/laravel/framework/pull/51811 -* [11.x] Add unprocessableContent and update unprocessableEntity by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/51815 -* [11.x] Improve Queue::assertNothingPushed() error message by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/51814 -* [11.x] Add `Relation::getMorphAlias()` by [@pxlrbt](https://github.com/pxlrbt) in https://github.com/laravel/framework/pull/51809 -* [11.x] Support third-party relations in `model:show` command by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51807 -* [11.x] Fix nested rules custom attribute names by [@owenandrews](https://github.com/owenandrews) in https://github.com/laravel/framework/pull/51805 -* [11.x] Fix docblock of \Illuminate\Http\Response by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/51823 - -## [v11.10.0](https://github.com/laravel/framework/compare/v11.9.2...v11.10.0) - 2024-06-04 - -* [11.x] Fix typo in filename by [@Henridv](https://github.com/Henridv) in https://github.com/laravel/framework/pull/51643 -* [11.x] Add Vite auto refresh to error page by [@riasvdv](https://github.com/riasvdv) in https://github.com/laravel/framework/pull/51635 -* [11.x] Add test for join_paths by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/51621 -* [11.x] Preload base options for missing config files by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/51619 -* [11.x] Add option to disable merging of base configuration by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/51579 -* [11.x] Allow callback to be passed to `updateOrInsert()` to pass different `$values` if the record already exists by [@Markshall](https://github.com/Markshall) in https://github.com/laravel/framework/pull/51566 -* [11.x] Fix `join_paths` issue with segment '0' by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/51649 -* [11.x] Remove extra double quote in the error page by [@nicolus](https://github.com/nicolus) in https://github.com/laravel/framework/pull/51670 -* [11.x] Add tests to improve test coverage for `HtmlString` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51666 -* [11.x] Add tests to improve test coverage for `Arr::whereNotNull` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51661 -* [11.x] Add tests for FileSystem class by [@imanghafoori1](https://github.com/imanghafoori1) in https://github.com/laravel/framework/pull/51654 -* [11.x] Update OptimizeClearCommand.php by [@nathanpurcell](https://github.com/nathanpurcell) in https://github.com/laravel/framework/pull/51667 -* [11.x] Support soft deleted models when using explicit route model binding by [@gbradley](https://github.com/gbradley) in https://github.com/laravel/framework/pull/51651 -* [11.x] Add tests for `Arr::divide` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51673 -* [11.x] Prune should be a flag option by [@riasvdv](https://github.com/riasvdv) in https://github.com/laravel/framework/pull/51694 -* [11.x] Avoid using Laravel new error page if `app.debug` changes to `true` at runtime by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51705 - -## [v11.9.2](https://github.com/laravel/framework/compare/v11.9.1...v11.9.2) - 2024-05-30 - -* [11.x] Fix new exception renderer compatibility with closure middleware by [@ifox](https://github.com/ifox) in https://github.com/laravel/framework/pull/51614 -* [11.x] Fix double-quoted string literals on SQLite by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51615 -* [11.x] Allow setting Resend api key in mailer specific config by [@riasvdv](https://github.com/riasvdv) in https://github.com/laravel/framework/pull/51618 -* [11.x] Fix only number as session key will result in numbered session keys by [@Katalam](https://github.com/Katalam) in https://github.com/laravel/framework/pull/51611 - -## [v11.9.1](https://github.com/laravel/framework/compare/v11.9.0...v11.9.1) - 2024-05-28 - -* [11.x] Fixes missing route context by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/51602 - -## [v11.9.0](https://github.com/laravel/framework/compare/v11.8.0...v11.9.0) - 2024-05-28 - -* [11.x] Optimize boostrap time by using hashtable to store providers by [@sarven](https://github.com/sarven) in https://github.com/laravel/framework/pull/51343 -* [11.x] Prevent destructive commands from running by [@jasonmccreary](https://github.com/jasonmccreary) in https://github.com/laravel/framework/pull/51376 -* [11.x] renamed left `has` to `contains` by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/51532 -* [10.x] Fix typo by [@Issei0804-ie](https://github.com/Issei0804-ie) in https://github.com/laravel/framework/pull/51535 -* [11.x] Fixes doc block in Timebox.php by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51537 -* [11.x] Rename test function to match prohibit action by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51534 -* [11.x] Fix LazilyRefreshDatabase when using Laravel BrowserKit Testing by [@MaxGiting](https://github.com/MaxGiting) in https://github.com/laravel/framework/pull/51538 -* [10.x] Fix SQL Server detection in database store by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51547 -* [11.x] Display test creation messages by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/framework/pull/51546 -* [11.x] Detect Cockroach DB connection loss by [@saschaglo](https://github.com/saschaglo) in https://github.com/laravel/framework/pull/51559 -* [11.x] Fix type tests by [@stayallive](https://github.com/stayallive) in https://github.com/laravel/framework/pull/51558 -* [11.x] Add `withoutDelay()` to the `Queueable` trait by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/51555 -* [11.x] Add an option to remove the original environment file after encrypting by [@riasvdv](https://github.com/riasvdv) in https://github.com/laravel/framework/pull/51556 -* [10.x] - Fix batch list loading in Horizon when serialization error by [@jeffortegad](https://github.com/jeffortegad) in https://github.com/laravel/framework/pull/51551 -* [10.x] Fixes explicit route binding with `BackedEnum` by [@CAAHS](https://github.com/CAAHS) in https://github.com/laravel/framework/pull/51586 -* [11.x] Add `Macroable` to `PendingCommand` by [@PerryvanderMeer](https://github.com/PerryvanderMeer) in https://github.com/laravel/framework/pull/51572 -* [11.x] Improves errors by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/51261 -* [11.x] Add RELEASE.md to .gitattributes by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/51598 -* [11.x] Fixes exception rendering by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/51587 - -## [v11.8.0](https://github.com/laravel/framework/compare/v11.7.0...v11.8.0) - 2024-05-21 - -* [11.x] Update PendingRequest.php by [@foremtehan](https://github.com/foremtehan) in https://github.com/laravel/framework/pull/51338 -* Add unshift method to Collection by [@timkelty](https://github.com/timkelty) in https://github.com/laravel/framework/pull/51344 -* [11.x] Synchronizing cache configuration file with updated laravel v11.0.7 by [@dvlpr91](https://github.com/dvlpr91) in https://github.com/laravel/framework/pull/51336 -* [11.x] Utilize `null-safe` operator instead of conditional check by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51328 -* [11.x] Add the events to be displayed on the model:show command by [@WendellAdriel](https://github.com/WendellAdriel) in https://github.com/laravel/framework/pull/51324 -* [11.x] fix: remove use of Redis::COMPRESSION_ZSTD_MIN by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51346 -* [10.x] Backport: Fix SesV2Transport to use correct `EmailTags` argument by [@Tietew](https://github.com/Tietew) in https://github.com/laravel/framework/pull/51352 -* [11.x] feat: use phpredis 6 in ci by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/51347 -* [11.x] create new "has" validation rule by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/51348 -* [11.x] Add support for previous apps keys in signed URL verification by [@Krisell](https://github.com/Krisell) in https://github.com/laravel/framework/pull/51222 -* [11.x] Allow setting exit code in migrate:status --pending by [@brecht-vermeersch](https://github.com/brecht-vermeersch) in https://github.com/laravel/framework/pull/51341 -* [11.x] Fix array rule typehint by [@erik-perri](https://github.com/erik-perri) in https://github.com/laravel/framework/pull/51372 -* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51365 -* [10.x] Fix PHPDoc typo by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51390 -* [11.x] Fix return type hint of resolveRouteBindingQuery by [@philbates35](https://github.com/philbates35) in https://github.com/laravel/framework/pull/51392 -* [11.x] Allow adding array or string for web and api routes in bootstrap/app.php by [@mrthito](https://github.com/mrthito) in https://github.com/laravel/framework/pull/51356 -* [ 11.x ] Adds ability to manually fail a command from outside the handle() method by [@ProjektGopher](https://github.com/ProjektGopher) in https://github.com/laravel/framework/pull/51435 -* [10.x] Fix `apa` on non ASCII characters by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51428 -* [11.x] Compare lowercased column names in getColumnType by [@chady](https://github.com/chady) in https://github.com/laravel/framework/pull/51431 -* [11.x] Use contracts instead of concrete type for `resolveRouteBindingQuery()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51425 -* [11.x] Set the value of `$this` in macro closures by [@simonwelsh](https://github.com/simonwelsh) in https://github.com/laravel/framework/pull/51401 -* [11.x] Add missing roundrobin transport driver config by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/framework/pull/51400 -* [11.x] Remove unused namespace by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51436 -* [11.x] Fixes doc block in `Connector.php` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51440 -* [10.x] Fixes view engine resolvers leaking memory by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/51450 -* [11.x] Add some tests to `SupportStrTest` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51437 -* [11.x] Add isCurrentlyOwnedBy function to lock by [@gazben](https://github.com/gazben) in https://github.com/laravel/framework/pull/51393 -* [11.x] Collection average/avg optimization by [@bert-w](https://github.com/bert-w) in https://github.com/laravel/framework/pull/51512 -* [11.x] Introduce `MixManifestNotFoundException` for handling missing Mix manifests by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/51502 -* [11.x] MailMakeCommand: Add new `--view` option by [@ryangjchandler](https://github.com/ryangjchandler) in https://github.com/laravel/framework/pull/51411 -* [11.x] Replace all backed enums with values when building URLs by [@stefanvdlugt](https://github.com/stefanvdlugt) in https://github.com/laravel/framework/pull/51524 -* [10.x] Do not use `app()` Foundation helper on `ViewServiceProvider` by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/51522 -* Fixes explicit route binding with `BackedEnum` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51525 -* [11.x] Add query method to UrlGenerator contract docblock by [@hjanos-bc](https://github.com/hjanos-bc) in https://github.com/laravel/framework/pull/51515 - -## [v11.7.0](https://github.com/laravel/framework/compare/v11.6.0...v11.7.0) - 2024-05-07 - -* [11.x] Fix SesV2Transport to use correct `EmailTags` argument by @Tietew in https://github.com/laravel/framework/pull/51265 -* [11.x] Add Databases nightly workflow by @Jubeki in https://github.com/laravel/framework/pull/51218 -* [11.x] update "min" and "max" rule comments by @browner12 in https://github.com/laravel/framework/pull/51274 -* [11.x] Fix namespace and improvement PSR in `ClassMakeCommandTest.php` by @saMahmoudzadeh in https://github.com/laravel/framework/pull/51280 -* [11.x] improvement test coverage for view components. by @saMahmoudzadeh in https://github.com/laravel/framework/pull/51271 -* [11.x] Introduce method `Rule::array()` by @Jacobs63 in https://github.com/laravel/framework/pull/51250 -* [11.x] Fix docblock for collection pluck methods by @SanderMuller in https://github.com/laravel/framework/pull/51295 -* [11.x] Add tests for handling non-baked enum and empty string requests by @hrant1020 in https://github.com/laravel/framework/pull/51289 -* blank and filled now support stringable by @lava83 in https://github.com/laravel/framework/pull/51300 -* [11.x] Fix ratio validation for high ratio images by @ahmedbally in https://github.com/laravel/framework/pull/51296 -* [11.x] Add int|float support to e method by @trippo in https://github.com/laravel/framework/pull/51314 -* [11.x] Add release notes by @driesvints in https://github.com/laravel/framework/pull/51310 -* [11.x] `Stringable` is also an interface of symfony by @lava83 in https://github.com/laravel/framework/pull/51309 -* [11.x] Add some tests and improvement test coverage for `Str::camel` by @saMahmoudzadeh in https://github.com/laravel/framework/pull/51308 -* [11.x] Using the `??` Operator (Null Coalescing Operator) by @saMahmoudzadeh in https://github.com/laravel/framework/pull/51305 -* [11.x] Add ability to override the default loading cached Routes for application by @ahmedabdel3al in https://github.com/laravel/framework/pull/51292 -* [11.x] Add ->whereJsonOverlaps() for mysql by @parkourben99 in https://github.com/laravel/framework/pull/51288 -* [11.x] Add `InteractsWithInput` methods to `ValidatedInput` by @aydinfatih in https://github.com/laravel/framework/pull/51316 -* [11.x] Adding PasswordResetLinkSent event by @Muffinman in https://github.com/laravel/framework/pull/51253 - -## [v11.6.0](https://github.com/laravel/framework/compare/v11.5.0...v11.6.0) - 2024-04-30 - -* [11.x] github: mariadb database healthcheck+naming by @grooverdan in https://github.com/laravel/framework/pull/51192 -* Add support for PHPUnit 11.1 by @crynobone in https://github.com/laravel/framework/pull/51197 -* Move whitespace in front of verbatim block in Blade templates by @Sjord in https://github.com/laravel/framework/pull/51195 -* [11.x] Trim trailing `?` from generated URL without query params by @onlime in https://github.com/laravel/framework/pull/51191 -* Add some tests on route:list sort command by @fgaroby in https://github.com/laravel/framework/pull/51202 -* [10.x] Improve releases flow by @driesvints in https://github.com/laravel/framework/pull/51213 -* Fix return types of `firstWhere` and `first` of `BelongsToMany` and `HasManyThrough` by @SanderMuller in https://github.com/laravel/framework/pull/51219 -* [10.x] Fix typo in signed URL tampering tests by @Krisell in https://github.com/laravel/framework/pull/51238 -* [10.x] Add "Server has gone away" to DetectsLostConnection by @Jubeki in https://github.com/laravel/framework/pull/51241 -* [11.x] Add some tests in `SupportStrTest` class by @saMahmoudzadeh in https://github.com/laravel/framework/pull/51235 -* [10.x] Fix support for the LARAVEL_STORAGE_PATH env var (#51238) by @dunglas in https://github.com/laravel/framework/pull/51243 -* [11.x] Add replaceable tags to translations by @LegendEffects in https://github.com/laravel/framework/pull/51190 -* [10.x] fix: Factory::createMany creating n^2 records by @calebdw in https://github.com/laravel/framework/pull/51225 - -## [v11.5.0](https://github.com/laravel/framework/compare/v11.4.0...v11.5.0) - 2024-04-23 - -* [11.x] Add namespace for `make:trait` and `make:interface` command by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/51083 -* [11.x] Ability to generate URL's with query params by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/51075 -* [11.x] Adds anonymous broadcasting by [@joedixon](https://github.com/joedixon) in https://github.com/laravel/framework/pull/51082 -* [10.x] Binding order is incorrect when using cursor paginate with multiple unions with a where by [@thijsvdanker](https://github.com/thijsvdanker) in https://github.com/laravel/framework/pull/50884 -* [10.x] Fix cursor paginate with union and column alias by [@thijsvdanker](https://github.com/thijsvdanker) in https://github.com/laravel/framework/pull/50882 -* [11.x] Fix typo in tests by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/51093 -* Fix argument type in `Cache\Store` by [@GromNaN](https://github.com/GromNaN) in https://github.com/laravel/framework/pull/51100 -* Correct comment's grammatical and semantic errors by [@javadihugo](https://github.com/javadihugo) in https://github.com/laravel/framework/pull/51101 -* [11.x] Replace matches typehint fix by [@henzeb](https://github.com/henzeb) in https://github.com/laravel/framework/pull/51095 -* [11.x] Exclude `laravel_through_key` when replicating model, fixes #51097 by [@levu42](https://github.com/levu42) in https://github.com/laravel/framework/pull/51098 -* [11.x] Add enum types to static Rule methods by [@erik-perri](https://github.com/erik-perri) in https://github.com/laravel/framework/pull/51090 -* [11.x] Add decrement method to the rate limiter class by [@AlexJump24](https://github.com/AlexJump24) in https://github.com/laravel/framework/pull/51102 -* [11.x] Remove dead code by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/51106 -* [11.x] Fix support for other hashing implementations when using `hashed` cast by [@j3j5](https://github.com/j3j5) in https://github.com/laravel/framework/pull/51112 -* Revert "[11.x] Adds support for `int` backed enums to implicit `Enum` route binding" by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/51119 -* [11.x] Add support for enums in `whereIn` route constraints by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/51121 -* Clarify that \Illuminate\Http\Request::replace replace all input values by [@treyssatvincent](https://github.com/treyssatvincent) in https://github.com/laravel/framework/pull/51123 -* [11.x] Fix db:show's --counts option by [@xuchunyang](https://github.com/xuchunyang) in https://github.com/laravel/framework/pull/51140 -* Update RuntimeException message when no data has been found by [@mikemeijer](https://github.com/mikemeijer) in https://github.com/laravel/framework/pull/51133 -* [11] Update DetectsLostConnections.php by [@it-can](https://github.com/it-can) in https://github.com/laravel/framework/pull/51127 -* [11.x] Reset connection after migrate for FreshCommand by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/51167 -* [10.x] Address Null Parameter Deprecations in UrlGenerator by [@aldobarr](https://github.com/aldobarr) in https://github.com/laravel/framework/pull/51148 -* [11.x] Provide context for NestedRules by [@imahmood](https://github.com/imahmood) in https://github.com/laravel/framework/pull/51160 -* [11.x] Fix renaming columns with `NULL` as default on legacy MariaDB/MySQL by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51177 -* [11.x] Supercharge Blade by [@assertchris](https://github.com/assertchris) in https://github.com/laravel/framework/pull/51143 -* [11.x] Allow implicit binding to have optional backed enums by [@Neol3108](https://github.com/Neol3108) in https://github.com/laravel/framework/pull/51178 -* [11.x] Blade Component Loop Speed Improvement by [@lonnylot](https://github.com/lonnylot) in https://github.com/laravel/framework/pull/51158 -* [11.x] Fix normalizedNameCache by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/51185 -* [11.x] GenericUser use `getAuthPasswordName` instead of hardcoded column name by [@Daniel-H123](https://github.com/Daniel-H123) in https://github.com/laravel/framework/pull/51186 - -## [v11.4.0](https://github.com/laravel/framework/compare/v11.3.1...v11.4.0) - 2024-04-16 - -* [11.x] Apc Cache - Remove long-time gone apc_* functions by [@serpentblade](https://github.com/serpentblade) in https://github.com/laravel/framework/pull/51010 -* [11.x] Allowing Usage of Livewire Wire Boolean Style Directives by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/51007 -* [11.x] Introduces `Exceptions` facade by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50704 -* [11.x] `afterQuery` hook by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/50587 -* Fix computed columns mapping to wrong tables by [@maddhatter](https://github.com/maddhatter) in https://github.com/laravel/framework/pull/51009 -* [11.x] improvement test for string title by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51015 -* [11.x] Fix failing `afterQuery` method tests when using sql server by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/51016 -* [11.x] Fix: Apply database connection before checking if the repository exist by [@sjspereira](https://github.com/sjspereira) in https://github.com/laravel/framework/pull/51021 -* [10.x] Fix error when using `orderByRaw()` in query before using `cursorPaginate()` by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/51023 -* [11.x] Add RequiredIfDeclined validation rule by [@timmydhooghe](https://github.com/timmydhooghe) in https://github.com/laravel/framework/pull/51030 -* [11.x] Adds support for enums on `mapInto` collection method by [@lukeraymonddowning](https://github.com/lukeraymonddowning) in https://github.com/laravel/framework/pull/51027 -* [11.x] Fix prompt fallback return value when using numeric keys by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/50995 -* [11.x] Adds support for `int` backed enums to implicit `Enum` route binding by [@monurakkaya](https://github.com/monurakkaya) in https://github.com/laravel/framework/pull/51029 -* [11.x] Configuration to disable events on Cache Repository by [@serpentblade](https://github.com/serpentblade) in https://github.com/laravel/framework/pull/51032 -* Revert "[11.x] Name of job set by displayName() must be honoured by S… by [@RobertBoes](https://github.com/RobertBoes) in https://github.com/laravel/framework/pull/51034 -* chore: fix some typos in comments by [@laterlaugh](https://github.com/laterlaugh) in https://github.com/laravel/framework/pull/51037 -* Name of job set by displayName() must be honoured by Schedule by [@SCIF](https://github.com/SCIF) in https://github.com/laravel/framework/pull/51038 -* Fix more typos by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/framework/pull/51039 -* [11.x] Fix some doc blocks by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/51043 -* [11.x] Add [@throws](https://github.com/throws) ConnectionException tag on Http methods for IDE support by [@masoudtajer](https://github.com/masoudtajer) in https://github.com/laravel/framework/pull/51066 -* [11.x] Add Prompts `textarea` fallback for tests and add assertion tests by [@lioneaglesolutions](https://github.com/lioneaglesolutions) in https://github.com/laravel/framework/pull/51055 -* Validate MAC per key by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/51063 -* [11.x] Add `throttle` method to `LazyCollection` by [@JosephSilber](https://github.com/JosephSilber) in https://github.com/laravel/framework/pull/51060 -* [11.x] Pass decay seconds or minutes like hour and day by [@jimmypuckett](https://github.com/jimmypuckett) in https://github.com/laravel/framework/pull/51054 -* [11.x] Consider after_commit config in SyncQueue by [@hansnn](https://github.com/hansnn) in https://github.com/laravel/framework/pull/51071 -* [10.x] Database layer fixes by [@saadsidqui](https://github.com/saadsidqui) in https://github.com/laravel/framework/pull/49787 -* [11.x] Fix context helper always requiring `$key` value by [@nikspyratos](https://github.com/nikspyratos) in https://github.com/laravel/framework/pull/51080 -* [11.x] Fix `expectsChoice` assertion with optional `multiselect` prompts. by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/51078 - -## [v11.3.1](https://github.com/laravel/framework/compare/v11.3.0...v11.3.1) - 2024-04-10 - -* [11.x] Name of job set by displayName() must be honoured by Schedule by [@SCIF](https://github.com/SCIF) in https://github.com/laravel/framework/pull/50973 -* Add Conditionable trait to Testing\PendingCommand.php by [@tobz-nz](https://github.com/tobz-nz) in https://github.com/laravel/framework/pull/50988 -* Allow sorting of route:list by multiple column/factors using a comma by [@fredbradley](https://github.com/fredbradley) in https://github.com/laravel/framework/pull/50998 -* [10.x] Added eachById and chunkByIdDesc to BelongsToMany by [@lonnylot](https://github.com/lonnylot) in https://github.com/laravel/framework/pull/50991 - -## [v11.3.0](https://github.com/laravel/framework/compare/v11.2.0...v11.3.0) - 2024-04-09 - -* [10.x] Prevent Redis connection error report flood on queue worker by [@kasus](https://github.com/kasus) in https://github.com/laravel/framework/pull/50812 -* [11.x] Optimize SetCacheHeaders to ensure error responses aren't cached by [@MinaWilliam](https://github.com/MinaWilliam) in https://github.com/laravel/framework/pull/50903 -* [11.x] Add session `hasAny` method by [@mahmoudmohamedramadan](https://github.com/mahmoudmohamedramadan) in https://github.com/laravel/framework/pull/50897 -* [11.x] Add option to report throttled exception in ThrottlesExceptions middleware by [@JaZo](https://github.com/JaZo) in https://github.com/laravel/framework/pull/50896 -* [11.x] Add DeleteWhenMissingModels attribute by [@Neol3108](https://github.com/Neol3108) in https://github.com/laravel/framework/pull/50890 -* [11.x] Allow customizing TrimStrings::$except by [@grohiro](https://github.com/grohiro) in https://github.com/laravel/framework/pull/50901 -* [11.x] Add pull methods to Context by [@renegeuze](https://github.com/renegeuze) in https://github.com/laravel/framework/pull/50904 -* [11.x] Remove redundant code from MariaDbGrammar by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/50907 -* [11.x] Explicit nullable parameter declarations to fix PHP 8.4 deprecation by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/50922 -* [11.x] Add setters to cache stores by [@stancl](https://github.com/stancl) in https://github.com/laravel/framework/pull/50912 -* [10.x] Laravel 10x optional withSize for hasTable by [@apspan](https://github.com/apspan) in https://github.com/laravel/framework/pull/50888 -* [11.x] Fix prompting for missing array arguments on artisan command by [@macocci7](https://github.com/macocci7) in https://github.com/laravel/framework/pull/50850 -* [11.x] Add strict-mode safe hasAttribute method to Eloquent by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/framework/pull/50909 -* [11.x] add function to get faked events by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/50905 -* [11.x] `retry` func - catch "Throwable" instead of Exception by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/50944 -* chore: remove repetitive words by [@findseat](https://github.com/findseat) in https://github.com/laravel/framework/pull/50943 -* [10.x] Add `serializeAndRestore()` to `NotificationFake` by [@dbpolito](https://github.com/dbpolito) in https://github.com/laravel/framework/pull/50935 -* [11.x] Prevent crash when handling ConnectionException in HttpClient retry logic by [@shinsenter](https://github.com/shinsenter) in https://github.com/laravel/framework/pull/50955 -* [11.x] Remove unknown parameters by [@naopusyu](https://github.com/naopusyu) in https://github.com/laravel/framework/pull/50965 -* [11.x] Fixed typo in PHPDoc `[@param](https://github.com/param)` by [@naopusyu](https://github.com/naopusyu) in https://github.com/laravel/framework/pull/50967 -* [11.x] Fix dockblock by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50979 -* [11.x] Allow time to be faked in database lock by [@JurianArie](https://github.com/JurianArie) in https://github.com/laravel/framework/pull/50981 -* [11.x] Introduce method `Http::createPendingRequest()` by [@Jacobs63](https://github.com/Jacobs63) in https://github.com/laravel/framework/pull/50980 -* [11.x] Add [@throws](https://github.com/throws) to some doc blocks by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50969 -* [11.x] Fix PHP_MAXPATHLEN check for existing check of files for views by [@joshuaruesweg](https://github.com/joshuaruesweg) in https://github.com/laravel/framework/pull/50962 -* [11.x] Allow to remove scopes from BelongsToMany relation by [@plumthedev](https://github.com/plumthedev) in https://github.com/laravel/framework/pull/50953 -* [11.x] Throw exception if named rate limiter and model property do not exist by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/framework/pull/50908 - -## [v11.2.0](https://github.com/laravel/framework/compare/v11.1.1...v11.2.0) - 2024-04-02 - -* [11.x] Fix: update `[@param](https://github.com/param)` in some doc block by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50827 -* [11.x] Fix: update [@return](https://github.com/return) in some doc blocks by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50826 -* [11.x] Fix retrieving generated columns on legacy PostgreSQL by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/50834 -* [11.x] Trim invisible characters by [@dasundev](https://github.com/dasundev) in https://github.com/laravel/framework/pull/50832 -* [11.x] Add default value for `get` and `getHidden` on `Context` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50824 -* [11.x] Improves `serve` Artisan command by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50821 -* [11.x] Rehash user passwords when logging in once by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/50843 -* [11.x] Do not wipe database if it does not exists by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50838 -* [11.x] Better database creation failure handling by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50836 -* [11.x] Use Default Schema Name on SQL Server by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/50855 -* Correct typing for startedAs and virtualAs database column definitions by [@ollieread](https://github.com/ollieread) in https://github.com/laravel/framework/pull/50851 -* Allow passing query Expression as column in Many-to-Many relationship by [@plumthedev](https://github.com/plumthedev) in https://github.com/laravel/framework/pull/50849 -* [11.x] Fix `Middleware::trustHosts(subdomains: true)` by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/50877 -* [11.x] Modify doc blocks for getGateArguments by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50874 -* [11.x] Add `[@throws](https://github.com/throws)` to doc block for resolve method by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50873 -* [11.x] Str trim methods by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/50822 -* [11.x] Add fluent helper by [@PhiloNL](https://github.com/PhiloNL) in https://github.com/laravel/framework/pull/50848 -* [11.x] Add a new helper for context by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50878 -* [11.x] `assertChain` and `assertNoChain` on job instance by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/50858 -* [11.x] Remove redundant `getDefaultNamespace` method in some classes (class, interface and trait commands) by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50880 -* [11.x] Remove redundant implementation of ConnectorInterface in MariaDbConnector by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50881 -* [11.X] Fix: error when using `orderByRaw` in query before using `cursorPaginate` by [@ngunyimacharia](https://github.com/ngunyimacharia) in https://github.com/laravel/framework/pull/50887 - -## [v11.1.1](https://github.com/laravel/framework/compare/v11.1.0...v11.1.1) - 2024-03-28 - -* [11.x] Fix: update `[@param](https://github.com/param)` in doc blocks by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50791 -* [11.x] Fix query builder `whereBetween` with CarbonPeriod and Carbon 3 by [@bakerkretzmar](https://github.com/bakerkretzmar) in https://github.com/laravel/framework/pull/50792 -* [11.x] Allows asserting no output in Artisan commands by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50702 -* fix typo by [@elguitarraverde](https://github.com/elguitarraverde) in https://github.com/laravel/framework/pull/50808 -* [11.x] Make DB::usingConnection() respect read/write type by [@SajtiDH](https://github.com/SajtiDH) in https://github.com/laravel/framework/pull/50806 -* [11.x] Fix deprecation warning caused by Carbon 3.2 by [@JackWH](https://github.com/JackWH) in https://github.com/laravel/framework/pull/50813 - -## [v11.1.0](https://github.com/laravel/framework/compare/v11.0.8...v11.1.0) - 2024-03-26 - -* [11.x] MySQL transaction isolation level fix by [@mwikberg-virta](https://github.com/mwikberg-virta) in https://github.com/laravel/framework/pull/50689 -* [11.x] Add ListManagementOptions in SES mail transport by [@arifszn](https://github.com/arifszn) in https://github.com/laravel/framework/pull/50660 -* [11.x] Accept non-backed enum in database queries by [@gbalduzzi](https://github.com/gbalduzzi) in https://github.com/laravel/framework/pull/50674 -* [11.x] Add `Conditionable` trait to `Context` by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50707 -* [11.x] Adds `[@throws](https://github.com/throws)` section to the Context's doc blocks by [@rnambaale](https://github.com/rnambaale) in https://github.com/laravel/framework/pull/50715 -* [11.x] Test modifying nullable columns by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/50708 -* [11.x] Introduce HASH_VERIFY env var by [@valorin](https://github.com/valorin) in https://github.com/laravel/framework/pull/50718 -* [11.x] Apply default timezone when casting unix timestamps by [@daniser](https://github.com/daniser) in https://github.com/laravel/framework/pull/50751 -* [11.x] Fixes `ApplicationBuilder::withCommandRouting()` usage by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50742 -* [11.x] Register console commands, paths and routes after the app is booted by [@plumthedev](https://github.com/plumthedev) in https://github.com/laravel/framework/pull/50738 -* [11.x] Enhance malformed request handling by [@jnoordsij](https://github.com/jnoordsij) in https://github.com/laravel/framework/pull/50735 -* [11.x] Adds `withSchedule` to `bootstrap/app.php` file by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50755 -* [11.x] Fix dock block for create method in `InvalidArgumentException.php` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50762 -* [11.x] signature typo by [@abrahamgreyson](https://github.com/abrahamgreyson) in https://github.com/laravel/framework/pull/50766 -* [11.x] Simplify `ApplicationBuilder::withSchedule()` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50765 - -## [v11.0.8](https://github.com/laravel/framework/compare/v11.0.7...v11.0.8) - 2024-03-21 - -* [11.x] Change typehint for enum rule from string to class-string by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/50603 -* [11.x] Fixed enum and enum.backed stub paths after publish by [@haroon-mahmood-4276](https://github.com/haroon-mahmood-4276) in https://github.com/laravel/framework/pull/50629 -* [11.x] Fix(ScheduleListCommand): fix doc block for listEvent method by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50638 -* [11.x] Re: Fix issue with missing 'js/' directory in broadcasting installation command by [@alnahian2003](https://github.com/alnahian2003) in https://github.com/laravel/framework/pull/50657 -* [11.x] Remove `$except` property from `ExcludesPaths` trait by [@gdebrauwer](https://github.com/gdebrauwer) in https://github.com/laravel/framework/pull/50644 -* [11.x] Fix command alias registration and usage. by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/50617 -* [11.x] Fixed make:session-table Artisan command cannot be executed if a migration exists by [@naopusyu](https://github.com/naopusyu) in https://github.com/laravel/framework/pull/50615 -* [11.x] Fix(src\illuminate\Queue): update doc block, Simplification of the code in RedisManager by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50635 -* [11.x] Add `--without-reverb` and `--without-node` arguments to `install:broadcasting` command by [@duncanmcclean](https://github.com/duncanmcclean) in https://github.com/laravel/framework/pull/50662 -* [11.x] Fixed `trait` stub paths after publish by [@haroon-mahmood-4276](https://github.com/haroon-mahmood-4276) in https://github.com/laravel/framework/pull/50678 -* [11.x] Fixed `class` and `class.invokable` stub paths after publish by [@haroon-mahmood-4276](https://github.com/haroon-mahmood-4276) in https://github.com/laravel/framework/pull/50676 -* [10.x] Fix `Collection::concat()` return type by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/50669 -* [11.x] Fix adding multiple bootstrap providers with opcache by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/framework/pull/50665 -* [11.x] Allow `BackedEnum` and `UnitEnum` in `Rule::in` and `Rule::notIn` by [@PerryvanderMeer](https://github.com/PerryvanderMeer) in https://github.com/laravel/framework/pull/50680 -* [10.x] Fix command alias registration and usage by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50695 - -## [v11.0.7](https://github.com/laravel/framework/compare/v11.0.6...v11.0.7) - 2024-03-15 - -* [11.x] Re-add translations for ValidationException by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50546 -* [11.x] Removes unused Dumpable trait by [@OussamaMater](https://github.com/OussamaMater) in https://github.com/laravel/framework/pull/50559 -* [11.x] Fix withRouting docblock type by [@santigarcor](https://github.com/santigarcor) in https://github.com/laravel/framework/pull/50563 -* [11.x] Fix docblock in FakeInvokedProcess.php by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50568 -* [11.x] fix: Add missing InvalidArgumentException import to Database/Schema/SqlServerBuilder by [@ayutaya](https://github.com/ayutaya) in https://github.com/laravel/framework/pull/50573 -* [11.x] Improved translation for displaying the count of errors in the validation message by [@andrey-helldar](https://github.com/andrey-helldar) in https://github.com/laravel/framework/pull/50560 -* [11.x] Fix retry_after to be an integer by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50580 -* [11.x] Use available `getPath()` instead of using `app_path()` to detect if base controller exists by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50583 -* [11.x] Fix doc block: `[@return](https://github.com/return) static` has been modified to `[@return](https://github.com/return) void` by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50592 -* accept attributes for channels by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/commit/398f49485e305756409b52af64837c784fd30de9 - -## [v11.0.6](https://github.com/laravel/framework/compare/v11.0.5...v11.0.6) - 2024-03-14 - -* [11.x] Fix version constraints for illuminate/process by [@riesjart](https://github.com/riesjart) in https://github.com/laravel/framework/pull/50524 -* [11.x] Update Broadcasting Install Command With Bun Support by [@HDVinnie](https://github.com/HDVinnie) in https://github.com/laravel/framework/pull/50525 -* [11.x] Allows to comment `web` and `health` routes by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50533 -* [11.x] Add generics for Arr::first() by [@phh](https://github.com/phh) in https://github.com/laravel/framework/pull/50514 -* Change default collation for MySQL by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50555 -* [11.x] Fixes install:broadcasting command by [@joedixon](https://github.com/joedixon) in https://github.com/laravel/framework/pull/50550 -* [11.x] Fix crash when configuration directory is non-existing by [@buismaarten](https://github.com/buismaarten) in https://github.com/laravel/framework/pull/50537 - -## [v11.0.5](https://github.com/laravel/framework/compare/v11.0.4...v11.0.5) - 2024-03-13 - -* [11.x] Improves broadcasting install by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50519 -* [11.x] Improved exception message on 'ensure' method by [@fgaroby](https://github.com/fgaroby) in https://github.com/laravel/framework/pull/50517 -* [11.x] Add hasValidRelativeSignatureWhileIgnoring macro by [@br13an](https://github.com/br13an) in https://github.com/laravel/framework/pull/50511 -* [11.x] Prevents database redis options of being merged by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50523 - -## [v11.0.4](https://github.com/laravel/framework/compare/v11.0.3...v11.0.4) - 2024-03-13 - -* [11.x] Add class_exists check for `Spark`'s `subscribed` default alias Middleware by [@akr4m](https://github.com/akr4m) in https://github.com/laravel/framework/pull/50489 -* [11.x] Fix: Removed TTY mode to resolve Windows compatibility issue by [@yourchocomate](https://github.com/yourchocomate) in https://github.com/laravel/framework/pull/50495 -* [11.x] Check for password before storing hash in session by [@valorin](https://github.com/valorin) in https://github.com/laravel/framework/pull/50507 -* [11.x] Fix an issue with missing controller class by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50505 -* [11.x] Add default empty config when creating repository within CacheManager by [@noefleury](https://github.com/noefleury) in https://github.com/laravel/framework/pull/50510 - -## [v11.0.3](https://github.com/laravel/framework/compare/v11.0.2...v11.0.3) - 2024-03-12 - -* [11.x] Arr helper map spread by [@bilfeldt](https://github.com/bilfeldt) in https://github.com/laravel/framework/pull/50474 -* [11.x] add `list` rule by [@medilies](https://github.com/medilies) in https://github.com/laravel/framework/pull/50454 -* [11.x] Fixes installation of passport by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50488 - -## [v11.0.2](https://github.com/laravel/framework/compare/v11.0.1...v11.0.2) - 2024-03-12 - -* [11.x] Adds `--graceful` to `php artisan migrate` by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/50486 - -## [v11.0.1](https://github.com/laravel/framework/compare/v11.0.0..v11.0.1) - 2024-03-12 - -* [10.x] Update mockery conflict to just disallow the broken version by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/50472 -* [10.x] Conflict with specific release by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50473 -* [10.x] Fix for attributes being escaped on Dynamic Blade Components by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/50471 -* [10.x] Revert PR 50403 by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50482 - -## v11.0.0 - 2024-03-12 - -Check the upgrade guide in the [Official Laravel Upgrade Documentation](https://laravel.com/docs/11.x/upgrade). Also you can see some release notes in the [Official Laravel Release Documentation](https://laravel.com/docs/11.x/releases). +* Add Env::extend to support custom adapters when loading environment variables by [@andrii-androshchuk](https://github.com/andrii-androshchuk) in https://github.com/laravel/framework/pull/54756 +* [12.x] Sync `filesystem.disk.local` configurations by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/54764 diff --git a/bin/release.sh b/bin/release.sh index b629084461d7..7e2aa75df1b9 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -10,7 +10,7 @@ then exit 1 fi -RELEASE_BRANCH="11.x" +RELEASE_BRANCH="12.x" CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) VERSION=$1 diff --git a/bin/split.sh b/bin/split.sh index 46033e130b99..ae4cdf01f8f3 100755 --- a/bin/split.sh +++ b/bin/split.sh @@ -3,7 +3,7 @@ set -e set -x -CURRENT_BRANCH="11.x" +CURRENT_BRANCH="12.x" function split() { diff --git a/composer.json b/composer.json index 282d9ff0aa8d..708b0c12c893 100644 --- a/composer.json +++ b/composer.json @@ -24,38 +24,38 @@ "ext-session": "*", "ext-tokenizer": "*", "composer-runtime-api": "^2.2", - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "brick/math": "^0.11|^0.12", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", "egulias/email-validator": "^3.2.1|^4.0", "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", "league/commonmark": "^2.6", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "nesbot/carbon": "^3.8.4", "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": "^7.0.3", - "symfony/error-handler": "^7.0.3", - "symfony/finder": "^7.0.3", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", "symfony/http-foundation": "^7.2.0", - "symfony/http-kernel": "^7.0.3", - "symfony/mailer": "^7.0.3", - "symfony/mime": "^7.0.3", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.31", - "symfony/process": "^7.0.3", - "symfony/routing": "^7.0.3", - "symfony/uid": "^7.0.3", - "symfony/var-dumper": "^7.0.3", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" @@ -111,17 +111,17 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.11.2", - "pda/pheanstalk": "^5.0.6", + "orchestra/testbench-core": "^10.0.0", + "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0.3", - "symfony/http-client": "^7.0.3", - "symfony/psr-http-message-bridge": "^7.0.3", - "symfony/translation": "^7.0.3" + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" }, "conflict": { "tightenco/collect": "<5.5.33" @@ -161,7 +161,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { @@ -188,17 +188,17 @@ "mockery/mockery": "Required to use mocking (^1.6).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/http-client": "Required to enable support for 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).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." }, "config": { "sort-packages": true, @@ -207,6 +207,6 @@ "php-http/discovery": false } }, - "minimum-stability": "stable", + "minimum-stability": "dev", "prefer-stable": true } diff --git a/config-stubs/app.php b/config-stubs/app.php index f46726731e4a..324b513a2733 100644 --- a/config-stubs/app.php +++ b/config-stubs/app.php @@ -65,7 +65,7 @@ | */ - 'timezone' => env('APP_TIMEZONE', 'UTC'), + 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- diff --git a/config/app.php b/config/app.php index d4e3180fa6fc..16073173f8f8 100644 --- a/config/app.php +++ b/config/app.php @@ -72,7 +72,7 @@ | */ - 'timezone' => env('APP_TIMEZONE', 'UTC'), + 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- diff --git a/config/database.php b/config/database.php index 8910562d6143..3e827c359b04 100644 --- a/config/database.php +++ b/config/database.php @@ -36,6 +36,7 @@ 'url' => env('DB_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', + 'prefix_indexes' => null, 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'busy_timeout' => null, 'journal_mode' => null, diff --git a/config/filesystems.php b/config/filesystems.php index c9bd1d09468f..3d671bd9105e 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -32,7 +32,8 @@ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => storage_path('app/private'), + 'serve' => true, 'throw' => false, 'report' => false, ], diff --git a/config/hashing.php b/config/hashing.php index 9eb408e09eea..356ec108dbe4 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -31,6 +31,7 @@ 'bcrypt' => [ 'rounds' => env('BCRYPT_ROUNDS', 12), 'verify' => env('HASH_VERIFY', true), + 'limit' => env('BCRYPT_LIMIT', null), ], /* diff --git a/config/logging.php b/config/logging.php index 8d94292b29f4..1345f6f66c51 100644 --- a/config/logging.php +++ b/config/logging.php @@ -98,10 +98,10 @@ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, - 'formatter' => env('LOG_STDERR_FORMATTER'), - 'with' => [ + 'handler_with' => [ 'stream' => 'php://stderr', ], + 'formatter' => env('LOG_STDERR_FORMATTER'), 'processors' => [PsrLogMessageProcessor::class], ], diff --git a/config/mail.php b/config/mail.php index 7ed4812780e0..ff140eb439f8 100644 --- a/config/mail.php +++ b/config/mail.php @@ -85,6 +85,7 @@ 'smtp', 'log', ], + 'retry_after' => 60, ], 'roundrobin' => [ @@ -93,6 +94,7 @@ 'ses', 'postmark', ], + 'retry_after' => 60, ], ], diff --git a/src/Illuminate/Auth/Access/AuthorizationException.php b/src/Illuminate/Auth/Access/AuthorizationException.php index 1454bde2a01d..1dd157e34e94 100644 --- a/src/Illuminate/Auth/Access/AuthorizationException.php +++ b/src/Illuminate/Auth/Access/AuthorizationException.php @@ -27,7 +27,6 @@ class AuthorizationException extends Exception * @param string|null $message * @param mixed $code * @param \Throwable|null $previous - * @return void */ public function __construct($message = null, $code = null, ?Throwable $previous = null) { diff --git a/src/Illuminate/Auth/Access/Events/GateEvaluated.php b/src/Illuminate/Auth/Access/Events/GateEvaluated.php index f77a9c84c51b..2e75512d5870 100644 --- a/src/Illuminate/Auth/Access/Events/GateEvaluated.php +++ b/src/Illuminate/Auth/Access/Events/GateEvaluated.php @@ -39,7 +39,6 @@ class GateEvaluated * @param string $ability * @param bool|null $result * @param array $arguments - * @return void */ public function __construct($user, $ability, $result, $arguments) { diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index d8feb383170b..47dea0ddd26e 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -94,7 +94,6 @@ class Gate implements GateContract * @param array $beforeCallbacks * @param array $afterCallbacks * @param callable|null $guessPolicyNamesUsingCallback - * @return void */ public function __construct( Container $container, @@ -180,8 +179,8 @@ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResp if ($condition instanceof Closure) { $response = $this->canBeCalledWithUser($user, $condition) - ? $condition($user) - : new Response(false, $message, $code); + ? $condition($user) + : new Response(false, $message, $code); } else { $response = $condition; } @@ -277,8 +276,8 @@ protected function buildAbilityCallback($ability, $callback) } return isset($method) - ? $policy->{$method}(...func_get_args()) - : $policy(...func_get_args()); + ? $policy->{$method}(...func_get_args()) + : $policy(...func_get_args()); }; } @@ -703,6 +702,9 @@ protected function guessPolicyName($class) $classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index)); return $classDirname.'\\Policies\\'.class_basename($class).'Policy'; + })->when(str_contains($classDirname, '\\Models\\'), function ($collection) use ($class, $classDirname) { + return $collection->concat([str_replace('\\Models\\', '\\Policies\\', $classDirname).'\\'.class_basename($class).'Policy']) + ->concat([str_replace('\\Models\\', '\\Models\\Policies\\', $classDirname).'\\'.class_basename($class).'Policy']); })->reverse()->values()->first(function ($class) { return class_exists($class); }) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']); diff --git a/src/Illuminate/Auth/Access/Response.php b/src/Illuminate/Auth/Access/Response.php index 6461d0fce128..d35b78ad09ff 100644 --- a/src/Illuminate/Auth/Access/Response.php +++ b/src/Illuminate/Auth/Access/Response.php @@ -41,7 +41,6 @@ class Response implements Arrayable, Stringable * @param bool $allowed * @param string|null $message * @param mixed $code - * @return void */ public function __construct($allowed, $message = '', $code = null) { diff --git a/src/Illuminate/Auth/AuthManager.php b/src/Illuminate/Auth/AuthManager.php index a4bc8e8a4b3f..70723558886e 100755 --- a/src/Illuminate/Auth/AuthManager.php +++ b/src/Illuminate/Auth/AuthManager.php @@ -48,7 +48,6 @@ class AuthManager implements FactoryContract * Create a new Auth manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { diff --git a/src/Illuminate/Auth/AuthenticationException.php b/src/Illuminate/Auth/AuthenticationException.php index c4f835c5e6c0..e3da045bc9a0 100644 --- a/src/Illuminate/Auth/AuthenticationException.php +++ b/src/Illuminate/Auth/AuthenticationException.php @@ -34,7 +34,6 @@ class AuthenticationException extends Exception * @param string $message * @param array $guards * @param string|null $redirectTo - * @return void */ public function __construct($message = 'Unauthenticated.', array $guards = [], $redirectTo = null) { diff --git a/src/Illuminate/Auth/DatabaseUserProvider.php b/src/Illuminate/Auth/DatabaseUserProvider.php index def86b346a55..24fae41d4d5f 100755 --- a/src/Illuminate/Auth/DatabaseUserProvider.php +++ b/src/Illuminate/Auth/DatabaseUserProvider.php @@ -38,7 +38,6 @@ class DatabaseUserProvider implements UserProvider * @param \Illuminate\Database\ConnectionInterface $connection * @param \Illuminate\Contracts\Hashing\Hasher $hasher * @param string $table - * @return void */ public function __construct(ConnectionInterface $connection, HasherContract $hasher, $table) { @@ -74,7 +73,8 @@ public function retrieveByToken($identifier, #[\SensitiveParameter] $token) ); return $user && $user->getRememberToken() && hash_equals($user->getRememberToken(), $token) - ? $user : null; + ? $user + : null; } /** diff --git a/src/Illuminate/Auth/EloquentUserProvider.php b/src/Illuminate/Auth/EloquentUserProvider.php index dc7a21ecac5d..e91f1057b553 100755 --- a/src/Illuminate/Auth/EloquentUserProvider.php +++ b/src/Illuminate/Auth/EloquentUserProvider.php @@ -36,7 +36,6 @@ class EloquentUserProvider implements UserProvider * * @param \Illuminate\Contracts\Hashing\Hasher $hasher * @param string $model - * @return void */ public function __construct(HasherContract $hasher, $model) { @@ -189,8 +188,8 @@ public function rehashPasswordIfRequired(UserContract $user, #[\SensitiveParamet protected function newModelQuery($model = null) { $query = is_null($model) - ? $this->createModel()->newQuery() - : $model->newQuery(); + ? $this->createModel()->newQuery() + : $model->newQuery(); with($query, $this->queryCallback); diff --git a/src/Illuminate/Auth/Events/Attempting.php b/src/Illuminate/Auth/Events/Attempting.php index e4500e33b735..567d7e6e9f66 100644 --- a/src/Illuminate/Auth/Events/Attempting.php +++ b/src/Illuminate/Auth/Events/Attempting.php @@ -4,39 +4,17 @@ class Attempting { - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The credentials for the user. - * - * @var array - */ - public $credentials; - - /** - * Indicates if the user should be "remembered". - * - * @var bool - */ - public $remember; - /** * Create a new event instance. * - * @param string $guard - * @param array $credentials - * @param bool $remember - * @return void + * @param string $guard The authentication guard name. + * @param array $credentials The credentials for the user. + * @param bool $remember Indicates if the user should be "remembered". */ - public function __construct($guard, #[\SensitiveParameter] $credentials, $remember) - { - $this->guard = $guard; - $this->remember = $remember; - $this->credentials = $credentials; + public function __construct( + public $guard, + #[\SensitiveParameter] public $credentials, + public $remember, + ) { } } diff --git a/src/Illuminate/Auth/Events/Authenticated.php b/src/Illuminate/Auth/Events/Authenticated.php index faefcbecba3a..33c537a6128c 100644 --- a/src/Illuminate/Auth/Events/Authenticated.php +++ b/src/Illuminate/Auth/Events/Authenticated.php @@ -8,30 +8,15 @@ class Authenticated { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user The authenticated user. */ - public function __construct($guard, $user) - { - $this->user = $user; - $this->guard = $guard; + public function __construct( + public $guard, + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/CurrentDeviceLogout.php b/src/Illuminate/Auth/Events/CurrentDeviceLogout.php index 32d31faf6448..d7614a1e5afe 100644 --- a/src/Illuminate/Auth/Events/CurrentDeviceLogout.php +++ b/src/Illuminate/Auth/Events/CurrentDeviceLogout.php @@ -8,30 +8,15 @@ class CurrentDeviceLogout { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user The authenticated user. */ - public function __construct($guard, $user) - { - $this->user = $user; - $this->guard = $guard; + public function __construct( + public $guard, + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/Failed.php b/src/Illuminate/Auth/Events/Failed.php index b29940e3ae5f..4b8800762f50 100644 --- a/src/Illuminate/Auth/Events/Failed.php +++ b/src/Illuminate/Auth/Events/Failed.php @@ -4,39 +4,17 @@ class Failed { - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The user the attempter was trying to authenticate as. - * - * @var \Illuminate\Contracts\Auth\Authenticatable|null - */ - public $user; - - /** - * The credentials provided by the attempter. - * - * @var array - */ - public $credentials; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable|null $user - * @param array $credentials - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user The user the attempter was trying to authenticate as. + * @param array $credentials The credentials provided by the attempter. */ - public function __construct($guard, $user, #[\SensitiveParameter] $credentials) - { - $this->user = $user; - $this->guard = $guard; - $this->credentials = $credentials; + public function __construct( + public $guard, + public $user, + #[\SensitiveParameter] public $credentials, + ) { } } diff --git a/src/Illuminate/Auth/Events/Lockout.php b/src/Illuminate/Auth/Events/Lockout.php index 347943feb181..d01c274d4de2 100644 --- a/src/Illuminate/Auth/Events/Lockout.php +++ b/src/Illuminate/Auth/Events/Lockout.php @@ -17,7 +17,6 @@ class Lockout * Create a new event instance. * * @param \Illuminate\Http\Request $request - * @return void */ public function __construct(Request $request) { diff --git a/src/Illuminate/Auth/Events/Login.php b/src/Illuminate/Auth/Events/Login.php index 87a399eab38b..a403e1efad32 100644 --- a/src/Illuminate/Auth/Events/Login.php +++ b/src/Illuminate/Auth/Events/Login.php @@ -8,39 +8,17 @@ class Login { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - - /** - * Indicates if the user should be "remembered". - * - * @var bool - */ - public $remember; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @param bool $remember - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user The authenticated user. + * @param bool $remember Indicates if the user should be "remembered". */ - public function __construct($guard, $user, $remember) - { - $this->user = $user; - $this->guard = $guard; - $this->remember = $remember; + public function __construct( + public $guard, + public $user, + public $remember, + ) { } } diff --git a/src/Illuminate/Auth/Events/Logout.php b/src/Illuminate/Auth/Events/Logout.php index c47341dc5601..3b7787ed38d5 100644 --- a/src/Illuminate/Auth/Events/Logout.php +++ b/src/Illuminate/Auth/Events/Logout.php @@ -8,30 +8,15 @@ class Logout { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user The authenticated user. */ - public function __construct($guard, $user) - { - $this->user = $user; - $this->guard = $guard; + public function __construct( + public $guard, + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/OtherDeviceLogout.php b/src/Illuminate/Auth/Events/OtherDeviceLogout.php index ea139a7b496e..fe3bd31f50a3 100644 --- a/src/Illuminate/Auth/Events/OtherDeviceLogout.php +++ b/src/Illuminate/Auth/Events/OtherDeviceLogout.php @@ -8,30 +8,15 @@ class OtherDeviceLogout { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user \Illuminate\Contracts\Auth\Authenticatable */ - public function __construct($guard, $user) - { - $this->user = $user; - $this->guard = $guard; + public function __construct( + public $guard, + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/PasswordReset.php b/src/Illuminate/Auth/Events/PasswordReset.php index f57b3c947660..813a6e95f4a1 100644 --- a/src/Illuminate/Auth/Events/PasswordReset.php +++ b/src/Illuminate/Auth/Events/PasswordReset.php @@ -8,21 +8,13 @@ class PasswordReset { use SerializesModels; - /** - * The user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param \Illuminate\Contracts\Auth\Authenticatable $user The user. */ - public function __construct($user) - { - $this->user = $user; + public function __construct( + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/PasswordResetLinkSent.php b/src/Illuminate/Auth/Events/PasswordResetLinkSent.php index 233e92db34fd..4153ba654b91 100644 --- a/src/Illuminate/Auth/Events/PasswordResetLinkSent.php +++ b/src/Illuminate/Auth/Events/PasswordResetLinkSent.php @@ -8,21 +8,13 @@ class PasswordResetLinkSent { use SerializesModels; - /** - * The user instance. - * - * @var \Illuminate\Contracts\Auth\CanResetPassword - */ - public $user; - /** * Create a new event instance. * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @return void + * @param \Illuminate\Contracts\Auth\CanResetPassword $user The user instance. */ - public function __construct($user) - { - $this->user = $user; + public function __construct( + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/Registered.php b/src/Illuminate/Auth/Events/Registered.php index f84058cf1fed..7bd312088f4c 100644 --- a/src/Illuminate/Auth/Events/Registered.php +++ b/src/Illuminate/Auth/Events/Registered.php @@ -8,21 +8,13 @@ class Registered { use SerializesModels; - /** - * The authenticated user. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param \Illuminate\Contracts\Auth\Authenticatable $user The authenticated user. */ - public function __construct($user) - { - $this->user = $user; + public function __construct( + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/Validated.php b/src/Illuminate/Auth/Events/Validated.php index ebc3b2ce1797..034016161182 100644 --- a/src/Illuminate/Auth/Events/Validated.php +++ b/src/Illuminate/Auth/Events/Validated.php @@ -8,30 +8,15 @@ class Validated { use SerializesModels; - /** - * The authentication guard name. - * - * @var string - */ - public $guard; - - /** - * The user retrieved and validated from the User Provider. - * - * @var \Illuminate\Contracts\Auth\Authenticatable - */ - public $user; - /** * Create a new event instance. * - * @param string $guard - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @param string $guard The authentication guard name. + * @param \Illuminate\Contracts\Auth\Authenticatable $user The user retrieved and validated from the User Provider. */ - public function __construct($guard, $user) - { - $this->user = $user; - $this->guard = $guard; + public function __construct( + public $guard, + public $user, + ) { } } diff --git a/src/Illuminate/Auth/Events/Verified.php b/src/Illuminate/Auth/Events/Verified.php index 1d6e4c0f3448..609fc73cc5ce 100644 --- a/src/Illuminate/Auth/Events/Verified.php +++ b/src/Illuminate/Auth/Events/Verified.php @@ -8,21 +8,13 @@ class Verified { use SerializesModels; - /** - * The verified user. - * - * @var \Illuminate\Contracts\Auth\MustVerifyEmail - */ - public $user; - /** * Create a new event instance. * - * @param \Illuminate\Contracts\Auth\MustVerifyEmail $user - * @return void + * @param \Illuminate\Contracts\Auth\MustVerifyEmail $user The verified user. */ - public function __construct($user) - { - $this->user = $user; + public function __construct( + public $user, + ) { } } diff --git a/src/Illuminate/Auth/GenericUser.php b/src/Illuminate/Auth/GenericUser.php index d015e5b4b617..99b199a56b8e 100755 --- a/src/Illuminate/Auth/GenericUser.php +++ b/src/Illuminate/Auth/GenericUser.php @@ -17,7 +17,6 @@ class GenericUser implements UserContract * Create a new generic User object. * * @param array $attributes - * @return void */ public function __construct(array $attributes) { diff --git a/src/Illuminate/Auth/Middleware/Authenticate.php b/src/Illuminate/Auth/Middleware/Authenticate.php index 81d4ee455ae3..30cf903610bc 100644 --- a/src/Illuminate/Auth/Middleware/Authenticate.php +++ b/src/Illuminate/Auth/Middleware/Authenticate.php @@ -28,7 +28,6 @@ class Authenticate implements AuthenticatesRequests * Create a new middleware instance. * * @param \Illuminate\Contracts\Auth\Factory $auth - * @return void */ public function __construct(Auth $auth) { diff --git a/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php b/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php index 0b4510c0fb66..00230191fc49 100644 --- a/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php +++ b/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php @@ -18,7 +18,6 @@ class AuthenticateWithBasicAuth * Create a new middleware instance. * * @param \Illuminate\Contracts\Auth\Factory $auth - * @return void */ public function __construct(AuthFactory $auth) { diff --git a/src/Illuminate/Auth/Middleware/Authorize.php b/src/Illuminate/Auth/Middleware/Authorize.php index e2ccbaf02ea3..a5a11ec796d5 100644 --- a/src/Illuminate/Auth/Middleware/Authorize.php +++ b/src/Illuminate/Auth/Middleware/Authorize.php @@ -22,7 +22,6 @@ class Authorize * Create a new middleware instance. * * @param \Illuminate\Contracts\Auth\Access\Gate $gate - * @return void */ public function __construct(Gate $gate) { diff --git a/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php b/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php index 10a3f7c65538..227174df2148 100644 --- a/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php +++ b/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php @@ -34,8 +34,8 @@ public function handle($request, Closure $next, $redirectToRoute = null) ($request->user() instanceof MustVerifyEmail && ! $request->user()->hasVerifiedEmail())) { return $request->expectsJson() - ? abort(403, 'Your email address is not verified.') - : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice')); + ? abort(403, 'Your email address is not verified.') + : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice')); } return $next($request); diff --git a/src/Illuminate/Auth/Middleware/RequirePassword.php b/src/Illuminate/Auth/Middleware/RequirePassword.php index fa62b8420c1a..8ac6f8af66d4 100644 --- a/src/Illuminate/Auth/Middleware/RequirePassword.php +++ b/src/Illuminate/Auth/Middleware/RequirePassword.php @@ -35,7 +35,6 @@ class RequirePassword * @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory * @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator * @param int|null $passwordTimeout - * @return void */ public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator, $passwordTimeout = null) { diff --git a/src/Illuminate/Auth/Notifications/ResetPassword.php b/src/Illuminate/Auth/Notifications/ResetPassword.php index d31ae210c943..3689cf027dac 100644 --- a/src/Illuminate/Auth/Notifications/ResetPassword.php +++ b/src/Illuminate/Auth/Notifications/ResetPassword.php @@ -33,7 +33,6 @@ class ResetPassword extends Notification * Create a notification instance. * * @param string $token - * @return void */ public function __construct(#[\SensitiveParameter] $token) { diff --git a/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php b/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php index aae28652fc58..935b93c2f8b6 100755 --- a/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php +++ b/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php @@ -10,73 +10,20 @@ class DatabaseTokenRepository implements TokenRepositoryInterface { - /** - * The database connection instance. - * - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * The Hasher implementation. - * - * @var \Illuminate\Contracts\Hashing\Hasher - */ - protected $hasher; - - /** - * The token database table. - * - * @var string - */ - protected $table; - - /** - * The hashing key. - * - * @var string - */ - protected $hashKey; - - /** - * The number of seconds a token should last. - * - * @var int - */ - protected $expires; - - /** - * Minimum number of seconds before re-redefining the token. - * - * @var int - */ - protected $throttle; - /** * Create a new token repository instance. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Contracts\Hashing\Hasher $hasher - * @param string $table - * @param string $hashKey - * @param int $expires - * @param int $throttle - * @return void + * @param int $expires The number of seconds a token should remain valid. + * @param int $throttle Minimum number of seconds before the user can generate new password reset tokens. */ public function __construct( - ConnectionInterface $connection, - HasherContract $hasher, - $table, - $hashKey, - $expires = 60, - $throttle = 60, + protected ConnectionInterface $connection, + protected HasherContract $hasher, + protected string $table, + protected string $hashKey, + protected int $expires = 3600, + protected int $throttle = 60, ) { - $this->table = $table; - $this->hasher = $hasher; - $this->hashKey = $hashKey; - $this->expires = $expires * 60; - $this->connection = $connection; - $this->throttle = $throttle; } /** diff --git a/src/Illuminate/Auth/Passwords/PasswordBroker.php b/src/Illuminate/Auth/Passwords/PasswordBroker.php index 29ef2f9cbce6..89565eb77b3a 100755 --- a/src/Illuminate/Auth/Passwords/PasswordBroker.php +++ b/src/Illuminate/Auth/Passwords/PasswordBroker.php @@ -30,7 +30,7 @@ class PasswordBroker implements PasswordBrokerContract /** * The event dispatcher instance. * - * @var \Illuminate\Contracts\Events\Dispatcher + * @var \Illuminate\Contracts\Events\Dispatcher|null */ protected $events; @@ -40,7 +40,6 @@ class PasswordBroker implements PasswordBrokerContract * @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens * @param \Illuminate\Contracts\Auth\UserProvider $users * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher - * @return void */ public function __construct(#[\SensitiveParameter] TokenRepositoryInterface $tokens, UserProvider $users, ?Dispatcher $dispatcher = null) { @@ -92,7 +91,7 @@ public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closur * * @param array $credentials * @param \Closure $callback - * @return mixed + * @return string */ public function reset(#[\SensitiveParameter] array $credentials, Closure $callback) { diff --git a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php index c388c693df79..516638b17f5f 100644 --- a/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php @@ -28,7 +28,6 @@ class PasswordBrokerManager implements FactoryContract * Create a new PasswordBroker manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -104,8 +103,8 @@ protected function createTokenRepository(array $config) $this->app['hash'], $config['table'], $key, - $config['expire'], - $config['throttle'] ?? 0 + ($config['expire'] ?? 60) * 60, + $config['throttle'] ?? 0, ); } diff --git a/src/Illuminate/Auth/Recaller.php b/src/Illuminate/Auth/Recaller.php index 4d96c82bc97a..222a98d655db 100644 --- a/src/Illuminate/Auth/Recaller.php +++ b/src/Illuminate/Auth/Recaller.php @@ -15,7 +15,6 @@ class Recaller * Create a new recaller instance. * * @param string $recaller - * @return void */ public function __construct($recaller) { diff --git a/src/Illuminate/Auth/RequestGuard.php b/src/Illuminate/Auth/RequestGuard.php index 9b8fd10a36b6..e9f4bc74a3c8 100644 --- a/src/Illuminate/Auth/RequestGuard.php +++ b/src/Illuminate/Auth/RequestGuard.php @@ -31,7 +31,6 @@ class RequestGuard implements Guard * @param callable $callback * @param \Illuminate\Http\Request $request * @param \Illuminate\Contracts\Auth\UserProvider|null $provider - * @return void */ public function __construct(callable $callback, Request $request, ?UserProvider $provider = null) { diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 4aede1ae6767..13bd15f46c5a 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -126,7 +126,6 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth * @param \Symfony\Component\HttpFoundation\Request|null $request * @param \Illuminate\Support\Timebox|null $timebox * @param bool $rehashOnLogin - * @return void */ public function __construct( $name, @@ -239,8 +238,8 @@ public function id() } return $this->user() - ? $this->user()->getAuthIdentifier() - : $this->session->get($this->getName()); + ? $this->user()->getAuthIdentifier() + : $this->session->get($this->getName()); } /** diff --git a/src/Illuminate/Auth/TokenGuard.php b/src/Illuminate/Auth/TokenGuard.php index 1e002a0a0845..b6e7f187ce04 100644 --- a/src/Illuminate/Auth/TokenGuard.php +++ b/src/Illuminate/Auth/TokenGuard.php @@ -47,7 +47,6 @@ class TokenGuard implements Guard * @param string $inputKey * @param string $storageKey * @param bool $hash - * @return void */ public function __construct( UserProvider $provider, diff --git a/src/Illuminate/Auth/composer.json b/src/Illuminate/Auth/composer.json index 5b74a53b91c5..7073f8b060cc 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -16,12 +16,12 @@ "require": { "php": "^8.2", "ext-hash": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/http": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/queue": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/http": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/queue": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -30,13 +30,13 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "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)." + "illuminate/console": "Required to use the auth:clear-resets command (^12.0).", + "illuminate/queue": "Required to fire login / logout events (^12.0).", + "illuminate/session": "Required to use the session based guard (^12.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Broadcasting/AnonymousEvent.php b/src/Illuminate/Broadcasting/AnonymousEvent.php index 51e47f158531..c53e2f1c2c2e 100644 --- a/src/Illuminate/Broadcasting/AnonymousEvent.php +++ b/src/Illuminate/Broadcasting/AnonymousEvent.php @@ -39,8 +39,6 @@ class AnonymousEvent implements ShouldBroadcast /** * Create a new anonymous broadcastable event instance. - * - * @return void */ public function __construct(protected Channel|array|string $channels) { diff --git a/src/Illuminate/Broadcasting/BroadcastController.php b/src/Illuminate/Broadcasting/BroadcastController.php index f2936ab416e0..01ce074eec88 100644 --- a/src/Illuminate/Broadcasting/BroadcastController.php +++ b/src/Illuminate/Broadcasting/BroadcastController.php @@ -41,6 +41,6 @@ public function authenticateUser(Request $request) } return Broadcast::resolveAuthenticatedUser($request) - ?? throw new AccessDeniedHttpException; + ?? throw new AccessDeniedHttpException; } } diff --git a/src/Illuminate/Broadcasting/BroadcastEvent.php b/src/Illuminate/Broadcasting/BroadcastEvent.php index 1f7d927e7b31..c4da0faab220 100644 --- a/src/Illuminate/Broadcasting/BroadcastEvent.php +++ b/src/Illuminate/Broadcasting/BroadcastEvent.php @@ -54,7 +54,6 @@ class BroadcastEvent implements ShouldQueue * Create a new job handler instance. * * @param mixed $event - * @return void */ public function __construct($event) { @@ -75,7 +74,8 @@ public function __construct($event) public function handle(BroadcastingFactory $manager) { $name = method_exists($this->event, 'broadcastAs') - ? $this->event->broadcastAs() : get_class($this->event); + ? $this->event->broadcastAs() + : get_class($this->event); $channels = Arr::wrap($this->event->broadcastOn()); @@ -84,8 +84,8 @@ public function handle(BroadcastingFactory $manager) } $connections = method_exists($this->event, 'broadcastConnections') - ? $this->event->broadcastConnections() - : [null]; + ? $this->event->broadcastConnections() + : [null]; $payload = $this->getPayloadFromEvent($this->event); diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index 4df00526aa3b..790e096bbaa2 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -51,7 +51,6 @@ class BroadcastManager implements FactoryContract * Create a new manager instance. * * @param \Illuminate\Contracts\Container\Container $app - * @return void */ public function __construct($app) { diff --git a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php index 01c673c22f32..5fc73a2d9902 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php @@ -26,7 +26,6 @@ class AblyBroadcaster extends Broadcaster * Create a new broadcaster instance. * * @param \Ably\AblyRest $ably - * @return void */ public function __construct(AblyRest $ably) { @@ -78,8 +77,8 @@ public function validAuthenticationResponse($request, $result) $user = $this->retrieveUser($request, $channelName); $broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting') - ? $user->getAuthIdentifierForBroadcasting() - : $user->getAuthIdentifier(); + ? $user->getAuthIdentifierForBroadcasting() + : $user->getAuthIdentifier(); $signature = $this->generateAblySignature( $request->channel_name, @@ -175,8 +174,8 @@ public function normalizeChannelName($channel) { if ($this->isGuardedChannel($channel)) { return str_starts_with($channel, 'private-') - ? Str::replaceFirst('private-', '', $channel) - : Str::replaceFirst('presence-', '', $channel); + ? Str::replaceFirst('private-', '', $channel) + : Str::replaceFirst('presence-', '', $channel); } return $channel; diff --git a/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php index eb21f2c0662f..7340f55181df 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php @@ -141,11 +141,11 @@ protected function extractAuthParameters($pattern, $channel, $callback) { $callbackParameters = $this->extractParameters($callback); - return (new Collection($this->extractChannelKeys($pattern, $channel)))->reject(function ($value, $key) { - return is_numeric($key); - })->map(function ($value, $key) use ($callbackParameters) { - return $this->resolveBinding($key, $value, $callbackParameters); - })->values()->all(); + return (new Collection($this->extractChannelKeys($pattern, $channel))) + ->reject(fn ($value, $key) => is_numeric($key)) + ->map(fn ($value, $key) => $this->resolveBinding($key, $value, $callbackParameters)) + ->values() + ->all(); } /** @@ -299,7 +299,8 @@ protected function binder() { if (! $this->bindingRegistrar) { $this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class) - ? Container::getInstance()->make(BindingRegistrar::class) : null; + ? Container::getInstance()->make(BindingRegistrar::class) + : null; } return $this->bindingRegistrar; diff --git a/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php index 50877dc976fe..5479361559a5 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php @@ -17,7 +17,6 @@ class LogBroadcaster extends Broadcaster * Create a new broadcaster instance. * * @param \Psr\Log\LoggerInterface $logger - * @return void */ public function __construct(LoggerInterface $logger) { diff --git a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php index 962d814183a8..e68a73c1f3de 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php @@ -24,7 +24,6 @@ class PusherBroadcaster extends Broadcaster * Create a new broadcaster instance. * * @param \Pusher\Pusher $pusher - * @return void */ public function __construct(Pusher $pusher) { @@ -110,8 +109,8 @@ public function validAuthenticationResponse($request, $result) $user = $this->retrieveUser($request, $channelName); $broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting') - ? $user->getAuthIdentifierForBroadcasting() - : $user->getAuthIdentifier(); + ? $user->getAuthIdentifierForBroadcasting() + : $user->getAuthIdentifier(); return $this->decodePusherResponse( $request, diff --git a/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php index 03245eac6e6f..9cb81c85af1d 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php @@ -40,7 +40,6 @@ class RedisBroadcaster extends Broadcaster * @param \Illuminate\Contracts\Redis\Factory $redis * @param string|null $connection * @param string $prefix - * @return void */ public function __construct(Redis $redis, $connection = null, $prefix = '') { @@ -92,8 +91,8 @@ public function validAuthenticationResponse($request, $result) $user = $this->retrieveUser($request, $channelName); $broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting') - ? $user->getAuthIdentifierForBroadcasting() - : $user->getAuthIdentifier(); + ? $user->getAuthIdentifierForBroadcasting() + : $user->getAuthIdentifier(); return json_encode(['channel_data' => [ 'user_id' => $broadcastIdentifier, diff --git a/src/Illuminate/Broadcasting/Channel.php b/src/Illuminate/Broadcasting/Channel.php index 53094227f559..ebbfa9b24564 100644 --- a/src/Illuminate/Broadcasting/Channel.php +++ b/src/Illuminate/Broadcasting/Channel.php @@ -18,7 +18,6 @@ class Channel implements Stringable * Create a new channel instance. * * @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name - * @return void */ public function __construct($name) { diff --git a/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php b/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php index 76977c158e49..e6a9597167d5 100644 --- a/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php +++ b/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php @@ -8,7 +8,6 @@ class EncryptedPrivateChannel extends Channel * Create a new channel instance. * * @param string $name - * @return void */ public function __construct($name) { diff --git a/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php b/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php index fd27a8cabb67..48fcba6bf802 100644 --- a/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php +++ b/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php @@ -22,8 +22,8 @@ trait InteractsWithBroadcasting public function broadcastVia($connection = null) { $this->broadcastConnection = is_null($connection) - ? [null] - : Arr::wrap($connection); + ? [null] + : Arr::wrap($connection); return $this; } diff --git a/src/Illuminate/Broadcasting/PendingBroadcast.php b/src/Illuminate/Broadcasting/PendingBroadcast.php index 191b905f5938..0d1298e07111 100644 --- a/src/Illuminate/Broadcasting/PendingBroadcast.php +++ b/src/Illuminate/Broadcasting/PendingBroadcast.php @@ -25,7 +25,6 @@ class PendingBroadcast * * @param \Illuminate\Contracts\Events\Dispatcher $events * @param mixed $event - * @return void */ public function __construct(Dispatcher $events, $event) { diff --git a/src/Illuminate/Broadcasting/PresenceChannel.php b/src/Illuminate/Broadcasting/PresenceChannel.php index 22de12d37f16..50c1ced8f8ae 100644 --- a/src/Illuminate/Broadcasting/PresenceChannel.php +++ b/src/Illuminate/Broadcasting/PresenceChannel.php @@ -8,7 +8,6 @@ class PresenceChannel extends Channel * Create a new channel instance. * * @param string $name - * @return void */ public function __construct($name) { diff --git a/src/Illuminate/Broadcasting/PrivateChannel.php b/src/Illuminate/Broadcasting/PrivateChannel.php index e53094b25c3f..c02e6ac9e5b5 100644 --- a/src/Illuminate/Broadcasting/PrivateChannel.php +++ b/src/Illuminate/Broadcasting/PrivateChannel.php @@ -10,7 +10,6 @@ class PrivateChannel extends Channel * Create a new channel instance. * * @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name - * @return void */ public function __construct($name) { diff --git a/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php b/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php index 83c752df08fb..b99af6f843d5 100644 --- a/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php +++ b/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php @@ -26,7 +26,6 @@ class UniqueBroadcastEvent extends BroadcastEvent implements ShouldBeUnique * Create a new event instance. * * @param mixed $event - * @return void */ public function __construct($event) { @@ -55,7 +54,7 @@ public function __construct($event) public function uniqueVia() { return method_exists($this->event, 'uniqueVia') - ? $this->event->uniqueVia() - : Container::getInstance()->make(Repository::class); + ? $this->event->uniqueVia() + : Container::getInstance()->make(Repository::class); } } diff --git a/src/Illuminate/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index e9e6bbb4a211..103b02ac2733 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -16,12 +16,12 @@ "require": { "php": "^8.2", "psr/log": "^1.0|^2.0|^3.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" + "illuminate/bus": "^12.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/queue": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { diff --git a/src/Illuminate/Bus/Batch.php b/src/Illuminate/Bus/Batch.php index bcfb898cb940..717d1c4ab11d 100644 --- a/src/Illuminate/Bus/Batch.php +++ b/src/Illuminate/Bus/Batch.php @@ -113,7 +113,6 @@ class Batch implements Arrayable, JsonSerializable * @param \Carbon\CarbonImmutable $createdAt * @param \Carbon\CarbonImmutable|null $cancelledAt * @param \Carbon\CarbonImmutable|null $finishedAt - * @return void */ public function __construct( QueueFactory $queue, @@ -454,7 +453,7 @@ public function delete() protected function invokeHandlerCallback($handler, Batch $batch, ?Throwable $e = null) { try { - return $handler($batch, $e); + $handler($batch, $e); } catch (Throwable $e) { if (function_exists('report')) { report($e); diff --git a/src/Illuminate/Bus/BatchFactory.php b/src/Illuminate/Bus/BatchFactory.php index 2c3a4e96ce57..9a3ed600aff6 100644 --- a/src/Illuminate/Bus/BatchFactory.php +++ b/src/Illuminate/Bus/BatchFactory.php @@ -18,7 +18,6 @@ class BatchFactory * Create a new batch factory instance. * * @param \Illuminate\Contracts\Queue\Factory $queue - * @return void */ public function __construct(QueueFactory $queue) { diff --git a/src/Illuminate/Bus/ChainedBatch.php b/src/Illuminate/Bus/ChainedBatch.php index 4a02601a2375..d88aa0e7377e 100644 --- a/src/Illuminate/Bus/ChainedBatch.php +++ b/src/Illuminate/Bus/ChainedBatch.php @@ -39,7 +39,6 @@ class ChainedBatch implements ShouldQueue * Create a new chained batch instance. * * @param \Illuminate\Bus\PendingBatch $batch - * @return void */ public function __construct(PendingBatch $batch) { diff --git a/src/Illuminate/Bus/Dispatcher.php b/src/Illuminate/Bus/Dispatcher.php index 5427c3ffb1b5..0107b9e5acd4 100644 --- a/src/Illuminate/Bus/Dispatcher.php +++ b/src/Illuminate/Bus/Dispatcher.php @@ -51,12 +51,18 @@ class Dispatcher implements QueueingDispatcher */ protected $queueResolver; + /** + * Indicates if dispatching after response is disabled. + * + * @var bool + */ + protected $allowsDispatchingAfterResponses = true; + /** * Create a new command dispatcher instance. * * @param \Illuminate\Contracts\Container\Container $container * @param \Closure|null $queueResolver - * @return void */ public function __construct(Container $container, ?Closure $queueResolver = null) { @@ -74,8 +80,8 @@ public function __construct(Container $container, ?Closure $queueResolver = null public function dispatch($command) { return $this->queueResolver && $this->commandShouldBeQueued($command) - ? $this->dispatchToQueue($command) - : $this->dispatchNow($command); + ? $this->dispatchToQueue($command) + : $this->dispatchNow($command); } /** @@ -253,6 +259,12 @@ protected function pushCommandToQueue($queue, $command) */ public function dispatchAfterResponse($command, $handler = null) { + if (! $this->allowsDispatchingAfterResponses) { + $this->dispatchSync($command); + + return; + } + $this->container->terminating(function () use ($command, $handler) { $this->dispatchSync($command, $handler); }); @@ -283,4 +295,28 @@ public function map(array $map) return $this; } + + /** + * Allow dispatching after responses. + * + * @return $this + */ + public function withDispatchingAfterResponses() + { + $this->allowsDispatchingAfterResponses = true; + + return $this; + } + + /** + * Disable dispatching after responses. + * + * @return $this + */ + public function withoutDispatchingAfterResponses() + { + $this->allowsDispatchingAfterResponses = false; + + return $this; + } } diff --git a/src/Illuminate/Bus/Events/BatchDispatched.php b/src/Illuminate/Bus/Events/BatchDispatched.php index b9a161adb48c..57acae64cc5c 100644 --- a/src/Illuminate/Bus/Events/BatchDispatched.php +++ b/src/Illuminate/Bus/Events/BatchDispatched.php @@ -6,21 +6,13 @@ class BatchDispatched { - /** - * The batch instance. - * - * @var \Illuminate\Bus\Batch - */ - public $batch; - /** * Create a new event instance. * - * @param \Illuminate\Bus\Batch $batch - * @return void + * @param \Illuminate\Bus\Batch $batch The batch instance. */ - public function __construct(Batch $batch) - { - $this->batch = $batch; + public function __construct( + public Batch $batch, + ) { } } diff --git a/src/Illuminate/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php index 3a3074dfe186..9538074d7be4 100644 --- a/src/Illuminate/Bus/PendingBatch.php +++ b/src/Illuminate/Bus/PendingBatch.php @@ -59,7 +59,6 @@ class PendingBatch * * @param \Illuminate\Contracts\Container\Container $container * @param \Illuminate\Support\Collection $jobs - * @return void */ public function __construct(Container $container, Collection $jobs) { @@ -171,8 +170,8 @@ public function progressCallbacks() public function then($callback) { $this->options['then'][] = $callback instanceof Closure - ? new SerializableClosure($callback) - : $callback; + ? new SerializableClosure($callback) + : $callback; return $this; } @@ -196,8 +195,8 @@ public function thenCallbacks() public function catch($callback) { $this->options['catch'][] = $callback instanceof Closure - ? new SerializableClosure($callback) - : $callback; + ? new SerializableClosure($callback) + : $callback; return $this; } @@ -221,8 +220,8 @@ public function catchCallbacks() public function finally($callback) { $this->options['finally'][] = $callback instanceof Closure - ? new SerializableClosure($callback) - : $callback; + ? new SerializableClosure($callback) + : $callback; return $this; } diff --git a/src/Illuminate/Bus/UniqueLock.php b/src/Illuminate/Bus/UniqueLock.php index 9a2726e9d8a5..c1d74c636f1e 100644 --- a/src/Illuminate/Bus/UniqueLock.php +++ b/src/Illuminate/Bus/UniqueLock.php @@ -17,7 +17,6 @@ class UniqueLock * Create a new unique lock manager instance. * * @param \Illuminate\Contracts\Cache\Repository $cache - * @return void */ public function __construct(Cache $cache) { @@ -33,12 +32,12 @@ public function __construct(Cache $cache) public function acquire($job) { $uniqueFor = method_exists($job, 'uniqueFor') - ? $job->uniqueFor() - : ($job->uniqueFor ?? 0); + ? $job->uniqueFor() + : ($job->uniqueFor ?? 0); $cache = method_exists($job, 'uniqueVia') - ? $job->uniqueVia() - : $this->cache; + ? $job->uniqueVia() + : $this->cache; return (bool) $cache->lock($this->getKey($job), $uniqueFor)->get(); } @@ -52,8 +51,8 @@ public function acquire($job) public function release($job) { $cache = method_exists($job, 'uniqueVia') - ? $job->uniqueVia() - : $this->cache; + ? $job->uniqueVia() + : $this->cache; $cache->lock($this->getKey($job))->forceRelease(); } @@ -67,8 +66,8 @@ public function release($job) public static function getKey($job) { $uniqueId = method_exists($job, 'uniqueId') - ? $job->uniqueId() - : ($job->uniqueId ?? ''); + ? $job->uniqueId() + : ($job->uniqueId ?? ''); return 'laravel_unique_job:'.get_class($job).':'.$uniqueId; } diff --git a/src/Illuminate/Bus/UpdatedBatchJobCounts.php b/src/Illuminate/Bus/UpdatedBatchJobCounts.php index 83d33a44f2f7..f68de3bba614 100644 --- a/src/Illuminate/Bus/UpdatedBatchJobCounts.php +++ b/src/Illuminate/Bus/UpdatedBatchJobCounts.php @@ -23,7 +23,6 @@ class UpdatedBatchJobCounts * * @param int $pendingJobs * @param int $failedJobs - * @return void */ public function __construct(int $pendingJobs = 0, int $failedJobs = 0) { diff --git a/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index 3acfed639cf9..dc3385da5120 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/pipeline": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/pipeline": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -27,11 +27,11 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + "illuminate/queue": "Required to use closures when chaining jobs (^12.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Cache/ApcStore.php b/src/Illuminate/Cache/ApcStore.php index 8bba88b50708..89c31a3f7f0c 100755 --- a/src/Illuminate/Cache/ApcStore.php +++ b/src/Illuminate/Cache/ApcStore.php @@ -25,7 +25,6 @@ class ApcStore extends TaggableStore * * @param \Illuminate\Cache\ApcWrapper $apc * @param string $prefix - * @return void */ public function __construct(ApcWrapper $apc, $prefix = '') { diff --git a/src/Illuminate/Cache/ArrayLock.php b/src/Illuminate/Cache/ArrayLock.php index 8e1ebe203eea..2eb5054dd544 100644 --- a/src/Illuminate/Cache/ArrayLock.php +++ b/src/Illuminate/Cache/ArrayLock.php @@ -20,7 +20,6 @@ class ArrayLock extends Lock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct($store, $name, $seconds, $owner = null) { diff --git a/src/Illuminate/Cache/ArrayStore.php b/src/Illuminate/Cache/ArrayStore.php index 353552777462..112501831822 100644 --- a/src/Illuminate/Cache/ArrayStore.php +++ b/src/Illuminate/Cache/ArrayStore.php @@ -35,7 +35,6 @@ class ArrayStore extends TaggableStore implements LockProvider * Create a new Array store. * * @param bool $serializesValues - * @return void */ public function __construct($serializesValues = false) { diff --git a/src/Illuminate/Cache/CacheLock.php b/src/Illuminate/Cache/CacheLock.php index 5cc4eeaf4863..e043b9373b55 100644 --- a/src/Illuminate/Cache/CacheLock.php +++ b/src/Illuminate/Cache/CacheLock.php @@ -18,7 +18,6 @@ class CacheLock extends Lock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct($store, $name, $seconds, $owner = null) { @@ -45,8 +44,8 @@ public function acquire() } return ($this->seconds > 0) - ? $this->store->put($this->name, $this->owner, $this->seconds) - : $this->store->forever($this->name, $this->owner); + ? $this->store->put($this->name, $this->owner, $this->seconds) + : $this->store->forever($this->name, $this->owner); } /** diff --git a/src/Illuminate/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index 5ae5b6a3358b..0a0c2de5e171 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -41,7 +41,6 @@ class CacheManager implements FactoryContract * Create a new Cache manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -72,6 +71,25 @@ public function driver($driver = null) return $this->store($driver); } + /** + * Get a memoized cache driver instance. + * + * @param string|null $driver + * @return \Illuminate\Contracts\Cache\Repository + */ + public function memo($driver = null) + { + $driver = $driver ?: $this->getDefaultDriver(); + + if (! $this->app->bound($bindingKey = "cache.__memoized:{$driver}")) { + $this->app->scoped($bindingKey, fn () => $this->repository( + new MemoizedStore($driver, $this->store($driver)), ['events' => false] + )); + } + + return $this->app->make($bindingKey); + } + /** * Resolve the given store. * @@ -332,7 +350,7 @@ protected function setEventDispatcher(Repository $repository) */ public function refreshEventDispatcher() { - array_map([$this, 'setEventDispatcher'], $this->stores); + array_map($this->setEventDispatcher(...), $this->stores); } /** @@ -419,6 +437,9 @@ public function purge($name = null) * * @param string $driver * @param \Closure $callback + * + * @param-closure-this $this $callback + * * @return $this */ public function extend($driver, Closure $callback) diff --git a/src/Illuminate/Cache/Console/ClearCommand.php b/src/Illuminate/Cache/Console/ClearCommand.php index 23870d4db6be..e84cefae8d6c 100755 --- a/src/Illuminate/Cache/Console/ClearCommand.php +++ b/src/Illuminate/Cache/Console/ClearCommand.php @@ -45,7 +45,6 @@ class ClearCommand extends Command * * @param \Illuminate\Cache\CacheManager $cache * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(CacheManager $cache, Filesystem $files) { diff --git a/src/Illuminate/Cache/Console/ForgetCommand.php b/src/Illuminate/Cache/Console/ForgetCommand.php index 7f418afbfaac..bb34e039eb85 100755 --- a/src/Illuminate/Cache/Console/ForgetCommand.php +++ b/src/Illuminate/Cache/Console/ForgetCommand.php @@ -34,7 +34,6 @@ class ForgetCommand extends Command * Create a new cache clear command instance. * * @param \Illuminate\Cache\CacheManager $cache - * @return void */ public function __construct(CacheManager $cache) { diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php index 506696fdbd16..8e63374cb988 100644 --- a/src/Illuminate/Cache/DatabaseLock.php +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -44,7 +44,6 @@ class DatabaseLock extends Lock * @param int $seconds * @param string|null $owner * @param array $lottery - * @return void */ public function __construct(Connection $connection, $table, $name, $seconds, $owner = null, $lottery = [2, 100], $defaultTimeoutInSeconds = 86400) { diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index 56564c2988e3..04c52e45922d 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -76,7 +76,6 @@ class DatabaseStore implements LockProvider, Store * @param string $prefix * @param string $lockTable * @param array $lockLottery - * @return void */ public function __construct( ConnectionInterface $connection, diff --git a/src/Illuminate/Cache/DynamoDbLock.php b/src/Illuminate/Cache/DynamoDbLock.php index 284b77a5bf77..b60e382c00aa 100644 --- a/src/Illuminate/Cache/DynamoDbLock.php +++ b/src/Illuminate/Cache/DynamoDbLock.php @@ -18,7 +18,6 @@ class DynamoDbLock extends Lock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct(DynamoDbStore $dynamo, $name, $seconds, $owner = null) { diff --git a/src/Illuminate/Cache/DynamoDbStore.php b/src/Illuminate/Cache/DynamoDbStore.php index 3b74683c925c..1bc7aa879865 100644 --- a/src/Illuminate/Cache/DynamoDbStore.php +++ b/src/Illuminate/Cache/DynamoDbStore.php @@ -67,7 +67,6 @@ class DynamoDbStore implements LockProvider, Store * @param string $valueAttribute * @param string $expirationAttribute * @param string $prefix - * @return void */ public function __construct( DynamoDbClient $dynamo, @@ -414,7 +413,7 @@ public function forever($key, $value) */ public function lock($name, $seconds = 0, $owner = null) { - return new DynamoDbLock($this, $this->prefix.$name, $seconds, $owner); + return new DynamoDbLock($this, $name, $seconds, $owner); } /** @@ -470,8 +469,8 @@ public function flush() protected function toTimestamp($seconds) { return $seconds > 0 - ? $this->availableAt($seconds) - : $this->currentTime(); + ? $this->availableAt($seconds) + : $this->currentTime(); } /** diff --git a/src/Illuminate/Cache/Events/CacheEvent.php b/src/Illuminate/Cache/Events/CacheEvent.php index b6bc49b15c96..6325a4494d9a 100644 --- a/src/Illuminate/Cache/Events/CacheEvent.php +++ b/src/Illuminate/Cache/Events/CacheEvent.php @@ -31,7 +31,6 @@ abstract class CacheEvent * @param string|null $storeName * @param string $key * @param array $tags - * @return void */ public function __construct($storeName, $key, array $tags = []) { diff --git a/src/Illuminate/Cache/Events/CacheFlushFailed.php b/src/Illuminate/Cache/Events/CacheFlushFailed.php new file mode 100644 index 000000000000..7d987e9de82c --- /dev/null +++ b/src/Illuminate/Cache/Events/CacheFlushFailed.php @@ -0,0 +1,45 @@ +storeName = $storeName; + $this->tags = $tags; + } + + /** + * Set the tags for the cache event. + * + * @param array $tags + * @return $this + */ + public function setTags($tags) + { + $this->tags = $tags; + + return $this; + } +} diff --git a/src/Illuminate/Cache/Events/CacheFlushed.php b/src/Illuminate/Cache/Events/CacheFlushed.php new file mode 100644 index 000000000000..5f942afdd1af --- /dev/null +++ b/src/Illuminate/Cache/Events/CacheFlushed.php @@ -0,0 +1,45 @@ +storeName = $storeName; + $this->tags = $tags; + } + + /** + * Set the tags for the cache event. + * + * @param array $tags + * @return $this + */ + public function setTags($tags) + { + $this->tags = $tags; + + return $this; + } +} diff --git a/src/Illuminate/Cache/Events/CacheFlushing.php b/src/Illuminate/Cache/Events/CacheFlushing.php new file mode 100644 index 000000000000..905f016143d7 --- /dev/null +++ b/src/Illuminate/Cache/Events/CacheFlushing.php @@ -0,0 +1,45 @@ +storeName = $storeName; + $this->tags = $tags; + } + + /** + * Set the tags for the cache event. + * + * @param array $tags + * @return $this + */ + public function setTags($tags) + { + $this->tags = $tags; + + return $this; + } +} diff --git a/src/Illuminate/Cache/Events/CacheHit.php b/src/Illuminate/Cache/Events/CacheHit.php index 9802980e3cbe..57a5c53472cd 100644 --- a/src/Illuminate/Cache/Events/CacheHit.php +++ b/src/Illuminate/Cache/Events/CacheHit.php @@ -18,7 +18,6 @@ class CacheHit extends CacheEvent * @param string $key * @param mixed $value * @param array $tags - * @return void */ public function __construct($storeName, $key, $value, array $tags = []) { diff --git a/src/Illuminate/Cache/Events/KeyWriteFailed.php b/src/Illuminate/Cache/Events/KeyWriteFailed.php index e74284d512b2..ecefbfe06dd7 100644 --- a/src/Illuminate/Cache/Events/KeyWriteFailed.php +++ b/src/Illuminate/Cache/Events/KeyWriteFailed.php @@ -26,7 +26,6 @@ class KeyWriteFailed extends CacheEvent * @param mixed $value * @param int|null $seconds * @param array $tags - * @return void */ public function __construct($storeName, $key, $value, $seconds = null, $tags = []) { diff --git a/src/Illuminate/Cache/Events/KeyWritten.php b/src/Illuminate/Cache/Events/KeyWritten.php index 49334882cb10..cfb42532c233 100644 --- a/src/Illuminate/Cache/Events/KeyWritten.php +++ b/src/Illuminate/Cache/Events/KeyWritten.php @@ -26,7 +26,6 @@ class KeyWritten extends CacheEvent * @param mixed $value * @param int|null $seconds * @param array $tags - * @return void */ public function __construct($storeName, $key, $value, $seconds = null, $tags = []) { diff --git a/src/Illuminate/Cache/Events/RetrievingManyKeys.php b/src/Illuminate/Cache/Events/RetrievingManyKeys.php index 9647c686aa8b..3722ad352beb 100644 --- a/src/Illuminate/Cache/Events/RetrievingManyKeys.php +++ b/src/Illuminate/Cache/Events/RetrievingManyKeys.php @@ -17,7 +17,6 @@ class RetrievingManyKeys extends CacheEvent * @param string|null $storeName * @param array $keys * @param array $tags - * @return void */ public function __construct($storeName, $keys, array $tags = []) { diff --git a/src/Illuminate/Cache/Events/WritingKey.php b/src/Illuminate/Cache/Events/WritingKey.php index ac874eb13a82..27dc8a87437c 100644 --- a/src/Illuminate/Cache/Events/WritingKey.php +++ b/src/Illuminate/Cache/Events/WritingKey.php @@ -26,7 +26,6 @@ class WritingKey extends CacheEvent * @param mixed $value * @param int|null $seconds * @param array $tags - * @return void */ public function __construct($storeName, $key, $value, $seconds = null, $tags = []) { diff --git a/src/Illuminate/Cache/Events/WritingManyKeys.php b/src/Illuminate/Cache/Events/WritingManyKeys.php index e180e6884d11..a4d077187d3a 100644 --- a/src/Illuminate/Cache/Events/WritingManyKeys.php +++ b/src/Illuminate/Cache/Events/WritingManyKeys.php @@ -33,7 +33,6 @@ class WritingManyKeys extends CacheEvent * @param array $values * @param int|null $seconds * @param array $tags - * @return void */ public function __construct($storeName, $keys, $values, $seconds = null, $tags = []) { diff --git a/src/Illuminate/Cache/FileStore.php b/src/Illuminate/Cache/FileStore.php index 4105582d44c6..d445f5fc7c23 100755 --- a/src/Illuminate/Cache/FileStore.php +++ b/src/Illuminate/Cache/FileStore.php @@ -48,7 +48,6 @@ class FileStore implements Store, LockProvider * @param \Illuminate\Filesystem\Filesystem $files * @param string $directory * @param int|null $filePermission - * @return void */ public function __construct(Filesystem $files, $directory, $filePermission = null) { diff --git a/src/Illuminate/Cache/Lock.php b/src/Illuminate/Cache/Lock.php index 18cd86a5690e..7913f1628a22 100644 --- a/src/Illuminate/Cache/Lock.php +++ b/src/Illuminate/Cache/Lock.php @@ -46,7 +46,6 @@ abstract class Lock implements LockContract * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct($name, $seconds, $owner = null) { diff --git a/src/Illuminate/Cache/MemcachedLock.php b/src/Illuminate/Cache/MemcachedLock.php index 0078a09e6974..8fdb2fc1427b 100644 --- a/src/Illuminate/Cache/MemcachedLock.php +++ b/src/Illuminate/Cache/MemcachedLock.php @@ -18,7 +18,6 @@ class MemcachedLock extends Lock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct($memcached, $name, $seconds, $owner = null) { diff --git a/src/Illuminate/Cache/MemcachedStore.php b/src/Illuminate/Cache/MemcachedStore.php index 5197c2df71f5..b05560e1a986 100755 --- a/src/Illuminate/Cache/MemcachedStore.php +++ b/src/Illuminate/Cache/MemcachedStore.php @@ -37,7 +37,6 @@ class MemcachedStore extends TaggableStore implements LockProvider * * @param \Memcached $memcached * @param string $prefix - * @return void */ public function __construct($memcached, $prefix = '') { diff --git a/src/Illuminate/Cache/MemoizedStore.php b/src/Illuminate/Cache/MemoizedStore.php new file mode 100644 index 000000000000..d899ef09d609 --- /dev/null +++ b/src/Illuminate/Cache/MemoizedStore.php @@ -0,0 +1,208 @@ + + */ + protected $cache = []; + + /** + * Create a new memoized cache instance. + * + * @param string $name + * @param \Illuminate\Cache\Repository $repository + */ + public function __construct( + protected $name, + protected $repository, + ) { + // + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $prefixedKey = $this->prefix($key); + + if (array_key_exists($prefixedKey, $this->cache)) { + return $this->cache[$prefixedKey]; + } + + return $this->cache[$prefixedKey] = $this->repository->get($key); + } + + /** + * Retrieve multiple items from the cache by key. + * + * Items not found in the cache will have a null value. + * + * @return array + */ + public function many(array $keys) + { + [$memoized, $retrieved, $missing] = [[], [], []]; + + foreach ($keys as $key) { + $prefixedKey = $this->prefix($key); + + if (array_key_exists($prefixedKey, $this->cache)) { + $memoized[$key] = $this->cache[$prefixedKey]; + } else { + $missing[] = $key; + } + } + + if (count($missing) > 0) { + $retrieved = tap($this->repository->many($missing), function ($values) { + $this->cache = [ + ...$this->cache, + ...collect($values)->mapWithKeys(fn ($value, $key) => [ + $this->prefix($key) => $value, + ]), + ]; + }); + } + + $result = []; + + foreach ($keys as $key) { + if (array_key_exists($key, $memoized)) { + $result[$key] = $memoized[$key]; + } else { + $result[$key] = $retrieved[$key]; + } + } + + return $result; + } + + /** + * Store an item in the cache for a given number of seconds. + * + * @param string $key + * @param mixed $value + * @param int $seconds + * @return bool + */ + public function put($key, $value, $seconds) + { + unset($this->cache[$this->prefix($key)]); + + return $this->repository->put($key, $value, $seconds); + } + + /** + * Store multiple items in the cache for a given number of seconds. + * + * @param int $seconds + * @return bool + */ + public function putMany(array $values, $seconds) + { + foreach ($values as $key => $value) { + unset($this->cache[$this->prefix($key)]); + } + + return $this->repository->putMany($values, $seconds); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + unset($this->cache[$this->prefix($key)]); + + return $this->repository->increment($key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + unset($this->cache[$this->prefix($key)]); + + return $this->repository->decrement($key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return bool + */ + public function forever($key, $value) + { + unset($this->cache[$this->prefix($key)]); + + return $this->repository->forever($key, $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + unset($this->cache[$this->prefix($key)]); + + return $this->repository->forget($key); + } + + /** + * Remove all items from the cache. + * + * @return bool + */ + public function flush() + { + $this->cache = []; + + return $this->repository->flush(); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->repository->getPrefix(); + } + + /** + * Prefix the given key. + * + * @param string $key + * @return string + */ + protected function prefix($key) + { + return $this->getPrefix().$key; + } +} diff --git a/src/Illuminate/Cache/PhpRedisLock.php b/src/Illuminate/Cache/PhpRedisLock.php index 6cfce7938a37..2cc29710c172 100644 --- a/src/Illuminate/Cache/PhpRedisLock.php +++ b/src/Illuminate/Cache/PhpRedisLock.php @@ -13,7 +13,6 @@ class PhpRedisLock extends RedisLock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct(PhpRedisConnection $redis, string $name, int $seconds, ?string $owner = null) { diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 12f76fee6e2a..67588ffbb1e1 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -32,7 +32,6 @@ class RateLimiter * Create a new rate limiter instance. * * @param \Illuminate\Contracts\Cache\Repository $cache - * @return void */ public function __construct(Cache $cache) { @@ -100,7 +99,7 @@ public function limiter($name) * @param string $key * @param int $maxAttempts * @param \Closure $callback - * @param int $decaySeconds + * @param \DateTimeInterface|\DateInterval|int $decaySeconds * @return mixed */ public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) @@ -142,7 +141,7 @@ public function tooManyAttempts($key, $maxAttempts) * Increment (by 1) the counter for a given key for a given decay time. * * @param string $key - * @param int $decaySeconds + * @param \DateTimeInterface|\DateInterval|int $decaySeconds * @return int */ public function hit($key, $decaySeconds = 60) @@ -154,7 +153,7 @@ public function hit($key, $decaySeconds = 60) * Increment the counter for a given key for a given decay time by a given amount. * * @param string $key - * @param int $decaySeconds + * @param \DateTimeInterface|\DateInterval|int $decaySeconds * @param int $amount * @return int */ @@ -185,7 +184,7 @@ public function increment($key, $decaySeconds = 60, $amount = 1) * Decrement the counter for a given key for a given decay time by a given amount. * * @param string $key - * @param int $decaySeconds + * @param \DateTimeInterface|\DateInterval|int $decaySeconds * @param int $amount * @return int */ diff --git a/src/Illuminate/Cache/RateLimiting/GlobalLimit.php b/src/Illuminate/Cache/RateLimiting/GlobalLimit.php index 965352ba78d9..e068ce5da12f 100644 --- a/src/Illuminate/Cache/RateLimiting/GlobalLimit.php +++ b/src/Illuminate/Cache/RateLimiting/GlobalLimit.php @@ -9,7 +9,6 @@ class GlobalLimit extends Limit * * @param int $maxAttempts * @param int $decaySeconds - * @return void */ public function __construct(int $maxAttempts, int $decaySeconds = 60) { diff --git a/src/Illuminate/Cache/RateLimiting/Limit.php b/src/Illuminate/Cache/RateLimiting/Limit.php index ed4c20258fe2..1a14009640e8 100644 --- a/src/Illuminate/Cache/RateLimiting/Limit.php +++ b/src/Illuminate/Cache/RateLimiting/Limit.php @@ -38,7 +38,6 @@ class Limit * @param mixed $key * @param int $maxAttempts * @param int $decaySeconds - * @return void */ public function __construct($key = '', int $maxAttempts = 60, int $decaySeconds = 60) { diff --git a/src/Illuminate/Cache/RateLimiting/Unlimited.php b/src/Illuminate/Cache/RateLimiting/Unlimited.php index fcfaa3178f0c..7597570fc6d9 100644 --- a/src/Illuminate/Cache/RateLimiting/Unlimited.php +++ b/src/Illuminate/Cache/RateLimiting/Unlimited.php @@ -6,8 +6,6 @@ class Unlimited extends GlobalLimit { /** * Create a new limit instance. - * - * @return void */ public function __construct() { diff --git a/src/Illuminate/Cache/RedisLock.php b/src/Illuminate/Cache/RedisLock.php index 67e3e0ac03aa..d28490fac737 100644 --- a/src/Illuminate/Cache/RedisLock.php +++ b/src/Illuminate/Cache/RedisLock.php @@ -18,7 +18,6 @@ class RedisLock extends Lock * @param string $name * @param int $seconds * @param string|null $owner - * @return void */ public function __construct($redis, $name, $seconds, $owner = null) { diff --git a/src/Illuminate/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index 39f1a0777ea0..33cdf87307c7 100755 --- a/src/Illuminate/Cache/RedisStore.php +++ b/src/Illuminate/Cache/RedisStore.php @@ -51,7 +51,6 @@ class RedisStore extends TaggableStore implements LockProvider * @param \Illuminate\Contracts\Redis\Factory $redis * @param string $prefix * @param string $connection - * @return void */ public function __construct(Redis $redis, $prefix = '', $connection = 'default') { diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 8846844b413d..69053266d33d 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -2,6 +2,9 @@ namespace Illuminate\Cache; +use Illuminate\Cache\Events\CacheFlushed; +use Illuminate\Cache\Events\CacheFlushing; + class RedisTaggedCache extends TaggedCache { /** @@ -105,9 +108,13 @@ public function forever($key, $value) */ public function flush() { + $this->event(new CacheFlushing($this->getName())); + $this->flushValues(); $this->tags->flush(); + $this->event(new CacheFlushed($this->getName())); + return true; } diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index a5f1df9db137..3eb6f700ed01 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -6,6 +6,9 @@ use BadMethodCallException; use Closure; use DateTimeInterface; +use Illuminate\Cache\Events\CacheFlushed; +use Illuminate\Cache\Events\CacheFlushFailed; +use Illuminate\Cache\Events\CacheFlushing; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; use Illuminate\Cache\Events\ForgettingKey; @@ -69,7 +72,6 @@ class Repository implements ArrayAccess, CacheContract * * @param \Illuminate\Contracts\Cache\Store $store * @param array $config - * @return void */ public function __construct(Store $store, array $config = []) { @@ -142,9 +144,11 @@ public function many(array $keys) { $this->event(new RetrievingManyKeys($this->getName(), $keys)); - $values = $this->store->many((new Collection($keys))->map(function ($value, $key) { - return is_string($key) ? $key : $value; - })->values()->all()); + $values = $this->store->many((new Collection($keys)) + ->map(fn ($value, $key) => is_string($key) ? $key : $value) + ->values() + ->all() + ); return (new Collection($values)) ->map(fn ($value, $key) => $this->handleManyResult($keys, $key, $value)) @@ -575,7 +579,17 @@ public function deleteMultiple($keys): bool */ public function clear(): bool { - return $this->store->flush(); + $this->event(new CacheFlushing($this->getName())); + + $result = $this->store->flush(); + + if ($result) { + $this->event(new CacheFlushed($this->getName())); + } else { + $this->event(new CacheFlushFailed($this->getName())); + } + + return $result; } /** diff --git a/src/Illuminate/Cache/TagSet.php b/src/Illuminate/Cache/TagSet.php index 471dc679ce5f..9dc4d7720be4 100644 --- a/src/Illuminate/Cache/TagSet.php +++ b/src/Illuminate/Cache/TagSet.php @@ -25,7 +25,6 @@ class TagSet * * @param \Illuminate\Contracts\Cache\Store $store * @param array $names - * @return void */ public function __construct(Store $store, array $names = []) { @@ -40,7 +39,7 @@ public function __construct(Store $store, array $names = []) */ public function reset() { - array_walk($this->names, [$this, 'resetTag']); + array_walk($this->names, $this->resetTag(...)); } /** @@ -63,7 +62,7 @@ public function resetTag($name) */ public function flush() { - array_walk($this->names, [$this, 'flushTag']); + array_walk($this->names, $this->flushTag(...)); } /** @@ -93,7 +92,7 @@ public function getNamespace() */ protected function tagIds() { - return array_map([$this, 'tagId'], $this->names); + return array_map($this->tagId(...), $this->names); } /** diff --git a/src/Illuminate/Cache/TaggedCache.php b/src/Illuminate/Cache/TaggedCache.php index 7cd12303882c..5504cdcc2ffd 100644 --- a/src/Illuminate/Cache/TaggedCache.php +++ b/src/Illuminate/Cache/TaggedCache.php @@ -2,6 +2,8 @@ namespace Illuminate\Cache; +use Illuminate\Cache\Events\CacheFlushed; +use Illuminate\Cache\Events\CacheFlushing; use Illuminate\Contracts\Cache\Store; class TaggedCache extends Repository @@ -22,7 +24,6 @@ class TaggedCache extends Repository * * @param \Illuminate\Contracts\Cache\Store $store * @param \Illuminate\Cache\TagSet $tags - * @return void */ public function __construct(Store $store, TagSet $tags) { @@ -78,8 +79,12 @@ public function decrement($key, $value = 1) */ public function flush() { + $this->event(new CacheFlushing($this->getName())); + $this->tags->reset(); + $this->event(new CacheFlushed($this->getName())); + return true; } @@ -105,12 +110,16 @@ public function taggedItemKey($key) /** * Fire an event for this cache instance. * - * @param \Illuminate\Cache\Events\CacheEvent $event + * @param object $event * @return void */ protected function event($event) { - parent::event($event->setTags($this->tags->getNames())); + if (method_exists($event, 'setTags')) { + $event->setTags($this->tags->getNames()); + } + + parent::event($event); } /** diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index ec0d26e18469..b1a44ef6d451 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0" }, "provide": { "psr/simple-cache-implementation": "1.0|2.0|3.0" @@ -30,17 +30,17 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.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 (^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)." + "illuminate/database": "Required to use the database cache driver (^12.0).", + "illuminate/filesystem": "Required to use the file cache driver (^12.0).", + "illuminate/redis": "Required to use the redis cache driver (^12.0).", + "symfony/cache": "Required to use PSR-6 cache bridge (^7.2)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 70fbb36924f5..d9b7561db2cf 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -40,6 +40,38 @@ public static function add($array, $key, $value) return $array; } + /** + * Get an array item from an array using "dot" notation. + */ + public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = null): array + { + $value = Arr::get($array, $key, $default); + + if (! is_array($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be an array, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get a boolean item from an array using "dot" notation. + */ + public static function boolean(ArrayAccess|array $array, string|int|null $key, ?bool $default = null): bool + { + $value = Arr::get($array, $key, $default); + + if (! is_bool($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a boolean, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Collapse an array of arrays into a single array. * @@ -112,13 +144,19 @@ public static function dot($array, $prepend = '') { $results = []; - foreach ($array as $key => $value) { - if (is_array($value) && ! empty($value)) { - $results = array_merge($results, static::dot($value, $prepend.$key.'.')); - } else { - $results[$prepend.$key] = $value; + $flatten = function ($data, $prefix) use (&$results, &$flatten): void { + foreach ($data as $key => $value) { + $newKey = $prefix.$key; + + if (is_array($value) && ! empty($value)) { + $flatten($value, $newKey.'.'); + } else { + $results[$newKey] = $value; + } } - } + }; + + $flatten($array, $prepend); return $results; } @@ -280,6 +318,22 @@ public static function flatten($array, $depth = INF) return $result; } + /** + * Get a float item from an array using "dot" notation. + */ + public static function float(ArrayAccess|array $array, string|int|null $key, ?float $default = null): float + { + $value = Arr::get($array, $key, $default); + + if (! is_float($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a float, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Remove one or many array items from a given array using "dot" notation. * @@ -427,6 +481,22 @@ public static function hasAny($array, $keys) return false; } + /** + * Get an integer item from an array using "dot" notation. + */ + public static function integer(ArrayAccess|array $array, string|int|null $key, ?int $default = null): int + { + $value = Arr::get($array, $key, $default); + + if (! is_integer($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be an integer, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Determines if an array is associative. * @@ -807,6 +877,34 @@ public static function shuffle($array) return (new Randomizer)->shuffleArray($array); } + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param array $array + * @param callable $callback + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public static function sole($array, ?callable $callback = null) + { + if ($callback) { + $array = static::where($array, $callback); + } + + $count = count($array); + + if ($count === 0) { + throw new ItemNotFoundException; + } + + if ($count > 1) { + throw new MultipleItemsFoundException($count); + } + + return static::first($array); + } + /** * Sort the array using the given callback or "dot" notation. * @@ -849,12 +947,12 @@ public static function sortRecursive($array, $options = SORT_REGULAR, $descendin if (! array_is_list($array)) { $descending - ? krsort($array, $options) - : ksort($array, $options); + ? krsort($array, $options) + : ksort($array, $options); } else { $descending - ? rsort($array, $options) - : sort($array, $options); + ? rsort($array, $options) + : sort($array, $options); } return $array; @@ -872,6 +970,22 @@ public static function sortRecursiveDesc($array, $options = SORT_REGULAR) return static::sortRecursive($array, $options, true); } + /** + * Get a string item from an array using "dot" notation. + */ + public static function string(ArrayAccess|array $array, string|int|null $key, ?string $default = null): string + { + $value = Arr::get($array, $key, $default); + + if (! is_string($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a string, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Conditionally compile classes from an array into a CSS class list. * @@ -942,6 +1056,32 @@ public static function reject($array, callable $callback) return static::where($array, fn ($value, $key) => ! $callback($value, $key)); } + /** + * Partition the array into two arrays using the given callback. + * + * @template TKey of array-key + * @template TValue of mixed + * + * @param iterable $array + * @param callable(TValue, TKey): bool $callback + * @return array, array> + */ + public static function partition($array, callable $callback) + { + $passed = []; + $failed = []; + + foreach ($array as $key => $item) { + if ($callback($item, $key)) { + $passed[$key] = $item; + } else { + $failed[$key] = $item; + } + } + + return [$passed, $failed]; + } + /** * Filter items where the value is not null. * diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 01e1114ed84b..23e1af7bbfee 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\TransformsToResourceCollection; use InvalidArgumentException; use stdClass; use Traversable; @@ -24,7 +25,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * @use \Illuminate\Support\Traits\EnumeratesValues */ - use EnumeratesValues, Macroable; + use EnumeratesValues, Macroable, TransformsToResourceCollection; /** * The items contained in the collection. @@ -37,7 +38,6 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Create a new collection. * * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items - * @return void */ public function __construct($items = []) { @@ -49,11 +49,12 @@ public function __construct($items = []) * * @param int $from * @param int $to + * @param int $step * @return static */ - public static function range($from, $to) + public static function range($from, $to, $step = 1) { - return new static(range($from, $to)); + return new static(range($from, $to, $step)); } /** @@ -85,7 +86,7 @@ public function lazy() public function median($key = null) { $values = (isset($key) ? $this->pluck($key) : $this) - ->filter(fn ($item) => ! is_null($item)) + ->reject(fn ($item) => is_null($item)) ->sort()->values(); $count = $values->count(); @@ -235,7 +236,7 @@ public function doesntContain($key, $operator = null, $value = null) public function crossJoin(...$lists) { return new static(Arr::crossJoin( - $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + $this->items, ...array_map($this->getArrayableItems(...), $lists) )); } @@ -1440,9 +1441,10 @@ public function firstOrFail($key = null, $operator = null, $value = null) * Chunk the collection into chunks of the given size. * * @param int $size - * @return static + * @param bool $preserveKeys + * @return ($preserveKeys is true ? static : static>) */ - public function chunk($size) + public function chunk($size, $preserveKeys = true) { if ($size <= 0) { return new static; @@ -1450,7 +1452,7 @@ public function chunk($size) $chunks = []; - foreach (array_chunk($this->items, $size, true) as $chunk) { + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { $chunks[] = new static($chunk); } @@ -1460,8 +1462,8 @@ public function chunk($size) /** * Chunk the collection into chunks with a callback. * - * @param callable(TValue, TKey, static): bool $callback - * @return static> + * @param callable(TValue, TKey, static): bool $callback + * @return static> */ public function chunkWhile(callable $callback) { diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 2d1214a35a19..78187b785697 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -45,9 +45,10 @@ public static function times($number, ?callable $callback = null); * * @param int $from * @param int $to + * @param int $step * @return static */ - public static function range($from, $to); + public static function range($from, $to, $step = 1); /** * Wrap the given value in a collection if applicable. diff --git a/src/Illuminate/Collections/HigherOrderCollectionProxy.php b/src/Illuminate/Collections/HigherOrderCollectionProxy.php index c5a723dd2134..7edfd4fa2c3b 100644 --- a/src/Illuminate/Collections/HigherOrderCollectionProxy.php +++ b/src/Illuminate/Collections/HigherOrderCollectionProxy.php @@ -31,7 +31,6 @@ class HigherOrderCollectionProxy * * @param \Illuminate\Support\Enumerable $collection * @param string $method - * @return void */ public function __construct(Enumerable $collection, $method) { diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index f4e61b457e03..daf811bfcadd 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -39,7 +39,6 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Create a new lazy collection instance. * * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $source - * @return void */ public function __construct($source = null) { @@ -75,17 +74,22 @@ public static function make($items = []) * * @param int $from * @param int $to + * @param int $step * @return static */ - public static function range($from, $to) + public static function range($from, $to, $step = 1) { - return new static(function () use ($from, $to) { + if ($step == 0) { + throw new InvalidArgumentException('Step value cannot be zero.'); + } + + return new static(function () use ($from, $to, $step) { if ($from <= $to) { - for (; $from <= $to; $from++) { + for (; $from <= $to; $from += abs($step)) { yield $from; } } else { - for (; $from >= $to; $from--) { + for (; $from >= $to; $from -= abs($step)) { yield $from; } } @@ -109,7 +113,7 @@ public function all() /** * Eager load all items into a new lazy collection backed by an array. * - * @return static + * @return static */ public function eager() { @@ -119,7 +123,7 @@ public function eager() /** * Cache values as they're enumerated. * - * @return static + * @return static */ public function remember() { @@ -330,7 +334,7 @@ public function countBy($countBy = null) * Get the items that are not present in the given items. * * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * @return static */ public function diff($items) { @@ -1087,7 +1091,7 @@ public function replaceRecursive($items) /** * Reverse items order. * - * @return static + * @return static */ public function reverse() { @@ -1182,7 +1186,7 @@ public function after($value, $strict = false) /** * Shuffle the items in the collection. * - * @return static + * @return static */ public function shuffle() { @@ -1371,22 +1375,28 @@ public function firstOrFail($key = null, $operator = null, $value = null) * Chunk the collection into chunks of the given size. * * @param int $size - * @return static + * @param bool $preserveKeys + * @return ($preserveKeys is true ? static : static>) */ - public function chunk($size) + public function chunk($size, $preserveKeys = true) { if ($size <= 0) { return static::empty(); } - return new static(function () use ($size) { + $add = match ($preserveKeys) { + true => fn (array &$chunk, Traversable $iterator) => $chunk[$iterator->key()] = $iterator->current(), + false => fn (array &$chunk, Traversable $iterator) => $chunk[] = $iterator->current(), + }; + + return new static(function () use ($size, $add) { $iterator = $this->getIterator(); while ($iterator->valid()) { $chunk = []; while (true) { - $chunk[$iterator->key()] = $iterator->current(); + $add($chunk, $iterator); if (count($chunk) < $size) { $iterator->next(); @@ -1421,7 +1431,7 @@ public function splitIn($numberOfGroups) * Chunk the collection into chunks with a callback. * * @param callable(TValue, TKey, Collection): bool $callback - * @return static> + * @return static> */ public function chunkWhile(callable $callback) { @@ -1539,7 +1549,7 @@ public function sortKeysUsing(callable $callback) * Take the first or last {$limit} items. * * @param int $limit - * @return static + * @return static */ public function take($limit) { @@ -1582,7 +1592,7 @@ public function take($limit) * Take items in the collection until the given condition is met. * * @param TValue|callable(TValue,TKey): bool $value - * @return static + * @return static */ public function takeUntil($value) { @@ -1604,7 +1614,7 @@ public function takeUntil($value) * Take items in the collection until a given point in time. * * @param \DateTimeInterface $timeout - * @return static + * @return static */ public function takeUntilTimeout(DateTimeInterface $timeout) { @@ -1629,7 +1639,7 @@ public function takeUntilTimeout(DateTimeInterface $timeout) * Take items in the collection while the given condition is met. * * @param TValue|callable(TValue,TKey): bool $value - * @return static + * @return static */ public function takeWhile($value) { @@ -1643,7 +1653,7 @@ public function takeWhile($value) * Pass each item in the collection to the given callback, lazily. * * @param callable(TValue, TKey): mixed $callback - * @return static + * @return static */ public function tapEach(callable $callback) { @@ -1703,7 +1713,7 @@ public function undot() * * @param (callable(TValue, TKey): mixed)|string|null $key * @param bool $strict - * @return static + * @return static */ public function unique($key = null, $strict = false) { diff --git a/src/Illuminate/Collections/MultipleItemsFoundException.php b/src/Illuminate/Collections/MultipleItemsFoundException.php index d90d835b4159..9c5c7c560ccb 100644 --- a/src/Illuminate/Collections/MultipleItemsFoundException.php +++ b/src/Illuminate/Collections/MultipleItemsFoundException.php @@ -19,7 +19,6 @@ class MultipleItemsFoundException extends RuntimeException * @param int $count * @param int $code * @param \Throwable|null $previous - * @return void */ public function __construct($count, $code = 0, $previous = null) { diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 7d47b1f1d02a..d2894529ed6e 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -179,6 +179,19 @@ public static function times($number, ?callable $callback = null) ->map($callback); } + /** + * Create a new collection by decoding a JSON string. + * + * @param string $json + * @param int $depth + * @param int $flags + * @return static + */ + public static function fromJson($json, $depth = 512, $flags = 0) + { + return new static(json_decode($json, true, $depth, $flags)); + } + /** * Get the average value of a given key. * @@ -342,7 +355,7 @@ public function value($key, $default = null) * * @template TEnsureOfType * - * @param class-string|array>|scalar|'array'|'null' $type + * @param class-string|array>|'string'|'int'|'float'|'bool'|'array'|'null' $type * @return static * * @throws \UnexpectedValueException @@ -414,7 +427,7 @@ public function mapToGroups(callable $callback) { $groups = $this->mapToDictionary($callback); - return $groups->map([$this, 'make']); + return $groups->map($this->make(...)); } /** @@ -459,7 +472,7 @@ public function min($callback = null) $callback = $this->valueRetriever($callback); return $this->map(fn ($value) => $callback($value)) - ->filter(fn ($value) => ! is_null($value)) + ->reject(fn ($value) => is_null($value)) ->reduce(fn ($result, $value) => is_null($result) || $value < $result ? $value : $result); } @@ -473,7 +486,7 @@ public function max($callback = null) { $callback = $this->valueRetriever($callback); - return $this->filter(fn ($value) => ! is_null($value))->reduce(function ($result, $item) use ($callback) { + return $this->reject(fn ($value) => is_null($value))->reduce(function ($result, $item) use ($callback) { $value = $callback($item); return is_null($result) || $value > $result ? $value : $result; @@ -504,20 +517,11 @@ public function forPage($page, $perPage) */ public function partition($key, $operator = null, $value = null) { - $passed = []; - $failed = []; - $callback = func_num_args() === 1 - ? $this->valueRetriever($key) - : $this->operatorForWhere(...func_get_args()); + ? $this->valueRetriever($key) + : $this->operatorForWhere(...func_get_args()); - foreach ($this as $key => $item) { - if ($callback($item, $key)) { - $passed[$key] = $item; - } else { - $failed[$key] = $item; - } - } + [$passed, $failed] = Arr::partition($this->getIterator(), $callback); return new static([new static($passed), new static($failed)]); } @@ -544,8 +548,10 @@ public function percentage(callable $callback, int $precision = 2) /** * Get the sum of the given values. * - * @param (callable(TValue): mixed)|string|null $callback - * @return mixed + * @template TReturnType + * + * @param (callable(TValue): TReturnType)|string|null $callback + * @return ($callback is callable ? TReturnType : mixed) */ public function sum($callback = null) { @@ -1000,8 +1006,8 @@ public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) public function __toString() { return $this->escapeWhenCastingToString - ? e($this->toJson()) - : $this->toJson(); + ? e($this->toJson()) + : $this->toJson(); } /** @@ -1053,11 +1059,8 @@ public function __get($key) */ protected function getArrayableItems($items) { - if (is_array($items)) { - return $items; - } - return match (true) { + is_array($items) => $items, $items instanceof WeakMap => throw new InvalidArgumentException('Collections can not be created using instances of WeakMap.'), $items instanceof Enumerable => $items->all(), $items instanceof Arrayable => $items->toArray(), diff --git a/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php new file mode 100644 index 000000000000..22143b356c48 --- /dev/null +++ b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php @@ -0,0 +1,68 @@ +|null $resourceClass + * @return \Illuminate\Http\Resources\Json\ResourceCollection + * + * @throws \Throwable + */ + public function toResourceCollection(?string $resourceClass = null): ResourceCollection + { + if ($resourceClass === null) { + return $this->guessResourceCollection(); + } + + return $resourceClass::collection($this); + } + + /** + * Guess the resource collection for the items. + * + * @return \Illuminate\Http\Resources\Json\ResourceCollection + * + * @throws \Throwable + */ + protected function guessResourceCollection(): ResourceCollection + { + if ($this->isEmpty()) { + return new ResourceCollection($this); + } + + $model = $this->items[0] ?? null; + + throw_unless(is_object($model), LogicException::class, 'Resource collection guesser expects the collection to contain objects.'); + + /** @var class-string $className */ + $className = get_class($model); + + throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className)); + + $resourceClasses = $className::guessResourceName(); + + foreach ($resourceClasses as $resourceClass) { + $resourceCollection = $resourceClass.'Collection'; + + if (is_string($resourceCollection) && class_exists($resourceCollection)) { + return new $resourceCollection($this); + } + } + + foreach ($resourceClasses as $resourceClass) { + if (is_string($resourceClass) && class_exists($resourceClass)) { + return $resourceClass::collection($this); + } + } + + throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className)); + } +} diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index b537407909ad..8d9c96125a47 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -15,9 +15,9 @@ ], "require": { "php": "^8.2", - "illuminate/conditionable": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0" + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0" }, "autoload": { "psr-4": { @@ -30,11 +30,12 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^7.0)." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 6d691f5e3091..55844559e711 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -248,6 +248,8 @@ function value($value, ...$args) */ function when($condition, $value, $default = null) { + $condition = $condition instanceof Closure ? $condition() : $condition; + if ($condition) { return value($value, $condition); } diff --git a/src/Illuminate/Concurrency/ForkDriver.php b/src/Illuminate/Concurrency/ForkDriver.php index b385e4fbf7e6..732873a72ee9 100644 --- a/src/Illuminate/Concurrency/ForkDriver.php +++ b/src/Illuminate/Concurrency/ForkDriver.php @@ -17,8 +17,17 @@ class ForkDriver implements Driver */ public function run(Closure|array $tasks): array { + $tasks = Arr::wrap($tasks); + + $keys = array_keys($tasks); + $values = array_values($tasks); + /** @phpstan-ignore class.notFound */ - return Fork::new()->run(...Arr::wrap($tasks)); + $results = Fork::new()->run(...$values); + + ksort($results); + + return array_combine($keys, $results); } /** diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index f4042da6caa2..bab43e61f309 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -32,14 +32,14 @@ public function run(Closure|array $tasks): array $command = Application::formatCommandString('invoke-serialized-closure'); $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $command) { - foreach (Arr::wrap($tasks) as $task) { - $pool->path(base_path())->env([ + foreach (Arr::wrap($tasks) as $key => $task) { + $pool->as($key)->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), ])->command($command); } })->start()->wait(); - return $results->collect()->map(function ($result) { + return $results->collect()->mapWithKeys(function ($result, $key) { if ($result->failed()) { throw new Exception('Concurrent process failed with exit code ['.$result->exitCode().']. Message: '.$result->errorOutput()); } @@ -54,7 +54,7 @@ public function run(Closure|array $tasks): array ); } - return unserialize($result['result']); + return [$key => unserialize($result['result'])]; })->all(); } diff --git a/src/Illuminate/Concurrency/composer.json b/src/Illuminate/Concurrency/composer.json index 8d542d699155..eb4e9467f66e 100644 --- a/src/Illuminate/Concurrency/composer.json +++ b/src/Illuminate/Concurrency/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.2", - "illuminate/console": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/process": "^11.0", - "illuminate/support": "^11.0", + "illuminate/console": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/process": "^12.0", + "illuminate/support": "^12.0", "laravel/serializable-closure": "^1.3|^2.0" }, "autoload": { diff --git a/src/Illuminate/Conditionable/HigherOrderWhenProxy.php b/src/Illuminate/Conditionable/HigherOrderWhenProxy.php index 579114cf1989..0a694c24fcd2 100644 --- a/src/Illuminate/Conditionable/HigherOrderWhenProxy.php +++ b/src/Illuminate/Conditionable/HigherOrderWhenProxy.php @@ -36,7 +36,6 @@ class HigherOrderWhenProxy * Create a new proxy instance. * * @param mixed $target - * @return void */ public function __construct($target) { diff --git a/src/Illuminate/Conditionable/composer.json b/src/Illuminate/Conditionable/composer.json index eb1d71eb18ab..9e0ddfbbdcf1 100644 --- a/src/Illuminate/Conditionable/composer.json +++ b/src/Illuminate/Conditionable/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.0.2" + "php": "^8.2" }, "autoload": { "psr-4": { @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Config/Repository.php b/src/Illuminate/Config/Repository.php index bf4db4ea3945..19240b42ac93 100644 --- a/src/Illuminate/Config/Repository.php +++ b/src/Illuminate/Config/Repository.php @@ -23,7 +23,6 @@ class Repository implements ArrayAccess, ConfigContract * Create a new configuration repository. * * @param array $items - * @return void */ public function __construct(array $items = []) { diff --git a/src/Illuminate/Config/composer.json b/src/Illuminate/Config/composer.json index ccccc4303e09..48db6c1db52a 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 63e364e2d57d..07073aab309c 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -63,7 +63,6 @@ class Application extends SymfonyApplication implements ApplicationContract * @param \Illuminate\Contracts\Container\Container $laravel * @param \Illuminate\Contracts\Events\Dispatcher $events * @param string $version - * @return void */ public function __construct(Container $laravel, Dispatcher $events, $version) { @@ -200,8 +199,8 @@ protected function parseCommand($command, $parameters) public function output() { return $this->lastOutput && method_exists($this->lastOutput, 'fetch') - ? $this->lastOutput->fetch() - : ''; + ? $this->lastOutput->fetch() + : ''; } /** diff --git a/src/Illuminate/Console/Command.php b/src/Illuminate/Console/Command.php index 3d5167ef82ba..e4be18364599 100755 --- a/src/Illuminate/Console/Command.php +++ b/src/Illuminate/Console/Command.php @@ -86,8 +86,6 @@ class Command extends SymfonyCommand /** * Create a new console command instance. - * - * @return void */ public function __construct() { @@ -203,8 +201,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); return (int) (is_numeric($this->option('isolated')) - ? $this->option('isolated') - : $this->isolatedExitCode); + ? $this->option('isolated') + : $this->isolatedExitCode); } $method = method_exists($this, 'handle') ? 'handle' : '__invoke'; diff --git a/src/Illuminate/Console/Concerns/InteractsWithIO.php b/src/Illuminate/Console/Concerns/InteractsWithIO.php index f839ce463499..33a4b726377b 100644 --- a/src/Illuminate/Console/Concerns/InteractsWithIO.php +++ b/src/Illuminate/Console/Concerns/InteractsWithIO.php @@ -19,8 +19,6 @@ trait InteractsWithIO * The console components factory. * * @var \Illuminate\Console\View\Components\Factory - * - * @internal This property is not meant to be used or overwritten outside the framework. */ protected $components; diff --git a/src/Illuminate/Console/ContainerCommandLoader.php b/src/Illuminate/Console/ContainerCommandLoader.php index f770f6c7101f..08af8f4cadc8 100644 --- a/src/Illuminate/Console/ContainerCommandLoader.php +++ b/src/Illuminate/Console/ContainerCommandLoader.php @@ -28,7 +28,6 @@ class ContainerCommandLoader implements CommandLoaderInterface * * @param \Psr\Container\ContainerInterface $container * @param array $commandMap - * @return void */ public function __construct(ContainerInterface $container, array $commandMap) { diff --git a/src/Illuminate/Console/Events/ArtisanStarting.php b/src/Illuminate/Console/Events/ArtisanStarting.php index f228ac529635..30538804a044 100644 --- a/src/Illuminate/Console/Events/ArtisanStarting.php +++ b/src/Illuminate/Console/Events/ArtisanStarting.php @@ -2,23 +2,17 @@ namespace Illuminate\Console\Events; +use Illuminate\Console\Application; + class ArtisanStarting { - /** - * The Artisan application instance. - * - * @var \Illuminate\Console\Application - */ - public $artisan; - /** * Create a new event instance. * - * @param \Illuminate\Console\Application $artisan - * @return void + * @param \Illuminate\Console\Application $artisan The Artisan application instance. */ - public function __construct($artisan) - { - $this->artisan = $artisan; + public function __construct( + public Application $artisan, + ) { } } diff --git a/src/Illuminate/Console/Events/CommandFinished.php b/src/Illuminate/Console/Events/CommandFinished.php index ef066af31935..850baea333bc 100644 --- a/src/Illuminate/Console/Events/CommandFinished.php +++ b/src/Illuminate/Console/Events/CommandFinished.php @@ -7,48 +7,19 @@ class CommandFinished { - /** - * The command name. - * - * @var string - */ - public $command; - - /** - * The console input implementation. - * - * @var \Symfony\Component\Console\Input\InputInterface|null - */ - public $input; - - /** - * The command output implementation. - * - * @var \Symfony\Component\Console\Output\OutputInterface|null - */ - public $output; - - /** - * The command exit code. - * - * @var int - */ - public $exitCode; - /** * Create a new event instance. * - * @param string $command - * @param \Symfony\Component\Console\Input\InputInterface $input - * @param \Symfony\Component\Console\Output\OutputInterface $output - * @param int $exitCode - * @return void + * @param string $command The command name. + * @param \Symfony\Component\Console\Input\InputInterface $input The console input implementation. + * @param \Symfony\Component\Console\Output\OutputInterface $output The command output implementation. + * @param int $exitCode The command exit code. */ - public function __construct($command, InputInterface $input, OutputInterface $output, $exitCode) - { - $this->input = $input; - $this->output = $output; - $this->command = $command; - $this->exitCode = $exitCode; + public function __construct( + public string $command, + public InputInterface $input, + public OutputInterface $output, + public int $exitCode, + ) { } } diff --git a/src/Illuminate/Console/Events/CommandStarting.php b/src/Illuminate/Console/Events/CommandStarting.php index c6238b5dc3d9..658c70ffc57e 100644 --- a/src/Illuminate/Console/Events/CommandStarting.php +++ b/src/Illuminate/Console/Events/CommandStarting.php @@ -7,39 +7,17 @@ class CommandStarting { - /** - * The command name. - * - * @var string - */ - public $command; - - /** - * The console input implementation. - * - * @var \Symfony\Component\Console\Input\InputInterface|null - */ - public $input; - - /** - * The command output implementation. - * - * @var \Symfony\Component\Console\Output\OutputInterface|null - */ - public $output; - /** * Create a new event instance. * - * @param string $command - * @param \Symfony\Component\Console\Input\InputInterface $input - * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return void + * @param string $command The command name. + * @param \Symfony\Component\Console\Input\InputInterface $input The console input implementation. + * @param \Symfony\Component\Console\Output\OutputInterface $output The command output implementation. */ - public function __construct($command, InputInterface $input, OutputInterface $output) - { - $this->input = $input; - $this->output = $output; - $this->command = $command; + public function __construct( + public string $command, + public InputInterface $input, + public OutputInterface $output, + ) { } } diff --git a/src/Illuminate/Console/Events/ScheduledBackgroundTaskFinished.php b/src/Illuminate/Console/Events/ScheduledBackgroundTaskFinished.php index d9e63c2e58d5..b277a6fced65 100644 --- a/src/Illuminate/Console/Events/ScheduledBackgroundTaskFinished.php +++ b/src/Illuminate/Console/Events/ScheduledBackgroundTaskFinished.php @@ -6,21 +6,13 @@ class ScheduledBackgroundTaskFinished { - /** - * The scheduled event that ran. - * - * @var \Illuminate\Console\Scheduling\Event - */ - public $task; - /** * Create a new event instance. * - * @param \Illuminate\Console\Scheduling\Event $task - * @return void + * @param \Illuminate\Console\Scheduling\Event $task The scheduled event that ran. */ - public function __construct(Event $task) - { - $this->task = $task; + public function __construct( + public Event $task, + ) { } } diff --git a/src/Illuminate/Console/Events/ScheduledTaskFailed.php b/src/Illuminate/Console/Events/ScheduledTaskFailed.php index 46857ad849a7..ba884bb657d6 100644 --- a/src/Illuminate/Console/Events/ScheduledTaskFailed.php +++ b/src/Illuminate/Console/Events/ScheduledTaskFailed.php @@ -7,30 +7,15 @@ class ScheduledTaskFailed { - /** - * The scheduled event that failed. - * - * @var \Illuminate\Console\Scheduling\Event - */ - public $task; - - /** - * The exception that was thrown. - * - * @var \Throwable - */ - public $exception; - /** * Create a new event instance. * - * @param \Illuminate\Console\Scheduling\Event $task - * @param \Throwable $exception - * @return void + * @param \Illuminate\Console\Scheduling\Event $task The scheduled event that failed. + * @param \Throwable $exception The exception that was thrown. */ - public function __construct(Event $task, Throwable $exception) - { - $this->task = $task; - $this->exception = $exception; + public function __construct( + public Event $task, + public Throwable $exception, + ) { } } diff --git a/src/Illuminate/Console/Events/ScheduledTaskFinished.php b/src/Illuminate/Console/Events/ScheduledTaskFinished.php index 6146966229e9..0a56f382fafd 100644 --- a/src/Illuminate/Console/Events/ScheduledTaskFinished.php +++ b/src/Illuminate/Console/Events/ScheduledTaskFinished.php @@ -6,30 +6,15 @@ class ScheduledTaskFinished { - /** - * The scheduled event that ran. - * - * @var \Illuminate\Console\Scheduling\Event - */ - public $task; - - /** - * The runtime of the scheduled event. - * - * @var float - */ - public $runtime; - /** * Create a new event instance. * - * @param \Illuminate\Console\Scheduling\Event $task - * @param float $runtime - * @return void + * @param \Illuminate\Console\Scheduling\Event $task The scheduled event that ran. + * @param float $runtime The runtime of the scheduled event. */ - public function __construct(Event $task, $runtime) - { - $this->task = $task; - $this->runtime = $runtime; + public function __construct( + public Event $task, + public float $runtime, + ) { } } diff --git a/src/Illuminate/Console/Events/ScheduledTaskSkipped.php b/src/Illuminate/Console/Events/ScheduledTaskSkipped.php index cfa7141da9b7..347c26593cff 100644 --- a/src/Illuminate/Console/Events/ScheduledTaskSkipped.php +++ b/src/Illuminate/Console/Events/ScheduledTaskSkipped.php @@ -6,21 +6,13 @@ class ScheduledTaskSkipped { - /** - * The scheduled event being run. - * - * @var \Illuminate\Console\Scheduling\Event - */ - public $task; - /** * Create a new event instance. * - * @param \Illuminate\Console\Scheduling\Event $task - * @return void + * @param \Illuminate\Console\Scheduling\Event $task The scheduled event being run. */ - public function __construct(Event $task) - { - $this->task = $task; + public function __construct( + public Event $task, + ) { } } diff --git a/src/Illuminate/Console/Events/ScheduledTaskStarting.php b/src/Illuminate/Console/Events/ScheduledTaskStarting.php index 66aaaa4c0de7..52a34c7746b4 100644 --- a/src/Illuminate/Console/Events/ScheduledTaskStarting.php +++ b/src/Illuminate/Console/Events/ScheduledTaskStarting.php @@ -6,21 +6,13 @@ class ScheduledTaskStarting { - /** - * The scheduled event being run. - * - * @var \Illuminate\Console\Scheduling\Event - */ - public $task; - /** * Create a new event instance. * - * @param \Illuminate\Console\Scheduling\Event $task - * @return void + * @param \Illuminate\Console\Scheduling\Event $task The scheduled event being run. */ - public function __construct(Event $task) - { - $this->task = $task; + public function __construct( + public Event $task, + ) { } } diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index af0049bbd021..5b6af51a576b 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -121,7 +121,6 @@ abstract class GeneratorCommand extends Command implements PromptsForMissingInpu * Create a new generator command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { @@ -236,8 +235,8 @@ protected function qualifyModel(string $model) } return is_dir(app_path('Models')) - ? $rootNamespace.'Models\\'.$model - : $rootNamespace.$model; + ? $rootNamespace.'Models\\'.$model + : $rootNamespace.$model; } /** diff --git a/src/Illuminate/Console/MigrationGeneratorCommand.php b/src/Illuminate/Console/MigrationGeneratorCommand.php index c741c03358fe..21198c03052c 100644 --- a/src/Illuminate/Console/MigrationGeneratorCommand.php +++ b/src/Illuminate/Console/MigrationGeneratorCommand.php @@ -19,7 +19,6 @@ abstract class MigrationGeneratorCommand extends Command * Create a new migration generator command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Console/OutputStyle.php b/src/Illuminate/Console/OutputStyle.php index 193e93da4350..5bfd6675bfaf 100644 --- a/src/Illuminate/Console/OutputStyle.php +++ b/src/Illuminate/Console/OutputStyle.php @@ -40,7 +40,6 @@ class OutputStyle extends SymfonyStyle implements NewLineAware * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return void */ public function __construct(InputInterface $input, OutputInterface $output) { diff --git a/src/Illuminate/Console/Scheduling/CacheEventMutex.php b/src/Illuminate/Console/Scheduling/CacheEventMutex.php index 3d1ad9247a1b..b2ca43e92a74 100644 --- a/src/Illuminate/Console/Scheduling/CacheEventMutex.php +++ b/src/Illuminate/Console/Scheduling/CacheEventMutex.php @@ -26,7 +26,6 @@ class CacheEventMutex implements EventMutex, CacheAware * Create a new overlapping strategy. * * @param \Illuminate\Contracts\Cache\Factory $cache - * @return void */ public function __construct(Cache $cache) { diff --git a/src/Illuminate/Console/Scheduling/CacheSchedulingMutex.php b/src/Illuminate/Console/Scheduling/CacheSchedulingMutex.php index ca8e2cb881f7..439e5bea3790 100644 --- a/src/Illuminate/Console/Scheduling/CacheSchedulingMutex.php +++ b/src/Illuminate/Console/Scheduling/CacheSchedulingMutex.php @@ -25,7 +25,6 @@ class CacheSchedulingMutex implements SchedulingMutex, CacheAware * Create a new scheduling strategy. * * @param \Illuminate\Contracts\Cache\Factory $cache - * @return void */ public function __construct(Cache $cache) { diff --git a/src/Illuminate/Console/Scheduling/CallbackEvent.php b/src/Illuminate/Console/Scheduling/CallbackEvent.php index 0ef6fddce633..9ee9a6e46e38 100644 --- a/src/Illuminate/Console/Scheduling/CallbackEvent.php +++ b/src/Illuminate/Console/Scheduling/CallbackEvent.php @@ -46,7 +46,6 @@ class CallbackEvent extends Event * @param string|callable $callback * @param array $parameters * @param \DateTimeZone|string|null $timezone - * @return void * * @throws \InvalidArgumentException */ diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index b3ee800663bb..944728361a7f 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -96,7 +96,6 @@ class Event * @param \Illuminate\Console\Scheduling\EventMutex $mutex * @param string $command * @param \DateTimeZone|string|null $timezone - * @return void */ public function __construct(EventMutex $mutex, $command, $timezone = null) { @@ -387,7 +386,7 @@ public function appendOutputTo($location) * * @throws \LogicException */ - public function emailOutputTo($addresses, $onlyIfOutputExists = false) + public function emailOutputTo($addresses, $onlyIfOutputExists = true) { $this->ensureOutputIsBeingCaptured(); @@ -448,7 +447,7 @@ protected function ensureOutputIsBeingCaptured() * @param bool $onlyIfOutputExists * @return void */ - protected function emailOutput(Mailer $mailer, $addresses, $onlyIfOutputExists = false) + protected function emailOutput(Mailer $mailer, $addresses, $onlyIfOutputExists = true) { $text = is_file($this->output) ? file_get_contents($this->output) : ''; @@ -743,8 +742,8 @@ protected function withOutputCallback(Closure $callback, $onlyIfOutputExists = f $output = $this->output && is_file($this->output) ? file_get_contents($this->output) : ''; return $onlyIfOutputExists && empty($output) - ? null - : $container->call($callback, ['output' => new Stringable($output)]); + ? null + : $container->call($callback, ['output' => new Stringable($output)]); }; } diff --git a/src/Illuminate/Console/Scheduling/Schedule.php b/src/Illuminate/Console/Scheduling/Schedule.php index 839d36272122..17de97bad8cb 100644 --- a/src/Illuminate/Console/Scheduling/Schedule.php +++ b/src/Illuminate/Console/Scheduling/Schedule.php @@ -102,7 +102,6 @@ class Schedule * Create a new schedule instance. * * @param \DateTimeZone|string|null $timezone - * @return void * * @throws \RuntimeException */ @@ -119,12 +118,12 @@ public function __construct($timezone = null) $container = Container::getInstance(); $this->eventMutex = $container->bound(EventMutex::class) - ? $container->make(EventMutex::class) - : $container->make(CacheEventMutex::class); + ? $container->make(EventMutex::class) + : $container->make(CacheEventMutex::class); $this->schedulingMutex = $container->bound(SchedulingMutex::class) - ? $container->make(SchedulingMutex::class) - : $container->make(CacheSchedulingMutex::class); + ? $container->make(SchedulingMutex::class) + : $container->make(CacheSchedulingMutex::class); } /** diff --git a/src/Illuminate/Console/Scheduling/ScheduleInterruptCommand.php b/src/Illuminate/Console/Scheduling/ScheduleInterruptCommand.php index 662606a2aee3..4477da56a4e1 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleInterruptCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleInterruptCommand.php @@ -35,7 +35,6 @@ class ScheduleInterruptCommand extends Command * Create a new schedule interrupt command. * * @param \Illuminate\Contracts\Cache\Repository $cache - * @return void */ public function __construct(Cache $cache) { diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 97845ad8bebc..0bb8f11ab498 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -85,7 +85,7 @@ public function handle(Schedule $schedule) */ private function getCronExpressionSpacing($events) { - $rows = $events->map(fn ($event) => array_map('mb_strlen', preg_split("/\s+/", $event->expression))); + $rows = $events->map(fn ($event) => array_map(mb_strlen(...), preg_split("/\s+/", $event->expression))); return (new Collection($rows[0] ?? []))->keys()->map(fn ($key) => $rows->max($key))->all(); } @@ -190,8 +190,8 @@ private function getRepeatExpression($event) private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone $timezone) { return $this->option('next') - ? $events->sortBy(fn ($event) => $this->getNextDueDateForEvent($event, $timezone)) - : $events; + ? $events->sortBy(fn ($event) => $this->getNextDueDateForEvent($event, $timezone)) + : $events; } /** diff --git a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php index b914229af51b..75cb579925cf 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -85,8 +85,6 @@ class ScheduleRunCommand extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/src/Illuminate/Console/Signals.php b/src/Illuminate/Console/Signals.php index 33192dc1c1d2..425352594c88 100644 --- a/src/Illuminate/Console/Signals.php +++ b/src/Illuminate/Console/Signals.php @@ -32,7 +32,6 @@ class Signals * Create a new signal registrar instance. * * @param \Symfony\Component\Console\SignalRegistry\SignalRegistry $registry - * @return void */ public function __construct($registry) { diff --git a/src/Illuminate/Console/View/Components/Component.php b/src/Illuminate/Console/View/Components/Component.php index 913c8b9bbedb..f515f916ff62 100644 --- a/src/Illuminate/Console/View/Components/Component.php +++ b/src/Illuminate/Console/View/Components/Component.php @@ -30,7 +30,6 @@ abstract class Component * Creates a new component instance. * * @param \Illuminate\Console\OutputStyle $output - * @return void */ public function __construct($output) { @@ -57,7 +56,7 @@ protected function renderView($view, $data, $verbosity) * * @param string $view * @param array $data - * @return void + * @return string */ protected function compile($view, $data) { diff --git a/src/Illuminate/Console/View/Components/Factory.php b/src/Illuminate/Console/View/Components/Factory.php index e226d79ae7e3..2929279057ee 100644 --- a/src/Illuminate/Console/View/Components/Factory.php +++ b/src/Illuminate/Console/View/Components/Factory.php @@ -33,7 +33,6 @@ class Factory * Creates a new factory instance. * * @param \Illuminate\Console\OutputStyle $output - * @return void */ public function __construct($output) { diff --git a/src/Illuminate/Console/View/Components/Task.php b/src/Illuminate/Console/View/Components/Task.php index ee743eaed028..fb4ab8d3a517 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\Console\View\TaskResult; use Illuminate\Support\InteractsWithTime; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -34,10 +35,10 @@ public function render($description, $task = null, $verbosity = OutputInterface: $startTime = microtime(true); - $result = false; + $result = TaskResult::Failure->value; try { - $result = ($task ?: fn () => true)(); + $result = ($task ?: fn () => TaskResult::Success->value)(); } catch (Throwable $e) { throw $e; } finally { @@ -53,7 +54,11 @@ public function render($description, $task = null, $verbosity = OutputInterface: $this->output->write("$runTime", false, $verbosity); $this->output->writeln( - $result !== false ? ' DONE' : ' FAIL', + match ($result) { + TaskResult::Failure->value => ' FAIL', + TaskResult::Skipped->value => ' SKIPPED', + default => ' DONE' + }, $verbosity, ); } diff --git a/src/Illuminate/Console/View/TaskResult.php b/src/Illuminate/Console/View/TaskResult.php new file mode 100644 index 000000000000..13d2afba018d --- /dev/null +++ b/src/Illuminate/Console/View/TaskResult.php @@ -0,0 +1,10 @@ +make($className); $pendingDependencies = array_merge($pendingDependencies, is_array($variadicDependencies) - ? $variadicDependencies - : [$variadicDependencies]); + ? $variadicDependencies + : [$variadicDependencies]); } else { $pendingDependencies[] = $container->make($className); } diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 27d835c3f383..32ecaeabe998 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -9,12 +9,15 @@ use Illuminate\Contracts\Container\CircularDependencyException; use Illuminate\Contracts\Container\Container as ContainerContract; use Illuminate\Contracts\Container\ContextualAttribute; +use Illuminate\Support\Collection; use LogicException; use ReflectionAttribute; use ReflectionClass; use ReflectionException; use ReflectionFunction; +use ReflectionIntersectionType; use ReflectionParameter; +use ReflectionUnionType; use TypeError; class Container implements ArrayAccess, ContainerContract @@ -268,15 +271,22 @@ public function isAlias($name) /** * Register a binding with the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void * * @throws \TypeError + * @throws ReflectionException */ public function bind($abstract, $concrete = null, $shared = false) { + if ($abstract instanceof Closure) { + return $this->bindBasedOnClosureReturnTypes( + $abstract, $concrete, $shared + ); + } + $this->dropStaleInstances($abstract); // If no concrete type was given, we will simply set the concrete type to the @@ -381,7 +391,7 @@ public function callMethodBinding($method, $instance) * Add a contextual binding to the container. * * @param string $concrete - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string $implementation * @return void */ @@ -393,7 +403,7 @@ public function addContextualBinding($concrete, $abstract, $implementation) /** * Register a binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void @@ -408,7 +418,7 @@ public function bindIf($abstract, $concrete = null, $shared = false) /** * Register a shared binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -420,7 +430,7 @@ public function singleton($abstract, $concrete = null) /** * Register a shared binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -434,7 +444,7 @@ public function singletonIf($abstract, $concrete = null) /** * Register a scoped binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -448,7 +458,7 @@ public function scoped($abstract, $concrete = null) /** * Register a scoped binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -459,6 +469,54 @@ public function scopedIf($abstract, $concrete = null) } } + /** + * Register a binding with the container based on the given Closure's return types. + * + * @param \Closure|string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + protected function bindBasedOnClosureReturnTypes($abstract, $concrete = null, $shared = false) + { + $abstracts = $this->closureReturnTypes($abstract); + + $concrete = $abstract; + + foreach ($abstracts as $abstract) { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Get the class names / types of the return type of the given Closure. + * + * @param \Closure $closure + * @return list + * + * @throws \ReflectionException + */ + protected function closureReturnTypes(Closure $closure) + { + $reflection = new ReflectionFunction($closure); + + if ($reflection->getReturnType() === null || + $reflection->getReturnType() instanceof ReflectionIntersectionType) { + return []; + } + + $types = $reflection->getReturnType() instanceof ReflectionUnionType + ? $reflection->getReturnType()->getTypes() + : [$reflection->getReturnType()]; + + return (new Collection($types)) + ->reject(fn ($type) => $type->isBuiltin()) + ->reject(fn ($type) => in_array($type->getName(), ['static', 'self'])) + ->map(fn ($type) => $type->getName()) + ->values() + ->all(); + } + /** * "Extend" an abstract type in the container. * @@ -1039,8 +1097,8 @@ protected function resolveDependencies(array $dependencies) // primitive type which we can not resolve since it is not a class and // we will just bomb out with an error since we have no-where to go. $result ??= is_null(Util::getParameterClassName($dependency)) - ? $this->resolvePrimitive($dependency) - : $this->resolveClass($dependency); + ? $this->resolvePrimitive($dependency) + : $this->resolveClass($dependency); $this->fireAfterResolvingAttributeCallbacks($dependency->getAttributes(), $result); @@ -1127,22 +1185,27 @@ protected function resolvePrimitive(ReflectionParameter $parameter) */ protected function resolveClass(ReflectionParameter $parameter) { + $className = Util::getParameterClassName($parameter); + + // First we will check if a default value has been defined for the parameter. + // If it has, and no explicit binding exists, we should return it to avoid + // overriding any of the developer specified defaults for the parameters. + if ($parameter->isDefaultValueAvailable() && + ! $this->bound($className) && + $this->findInContextualBindings($className) === null) { + return $parameter->getDefaultValue(); + } + try { return $parameter->isVariadic() - ? $this->resolveVariadicClass($parameter) - : $this->make(Util::getParameterClassName($parameter)); + ? $this->resolveVariadicClass($parameter) + : $this->make($className); } // If we can not resolve the class instance, we will check to see if the value - // is optional, and if it is we will return the optional parameter value as - // the value of the dependency, similarly to how we do this with scalars. + // is variadic. If it is, we will return an empty array as the value of the + // dependency similarly to how we handle scalar values in this situation. catch (BindingResolutionException $e) { - if ($parameter->isDefaultValueAvailable()) { - array_pop($this->with); - - return $parameter->getDefaultValue(); - } - if ($parameter->isVariadic()) { array_pop($this->with); @@ -1452,8 +1515,8 @@ public function getBindings() public function getAlias($abstract) { return isset($this->aliases[$abstract]) - ? $this->getAlias($this->aliases[$abstract]) - : $abstract; + ? $this->getAlias($this->aliases[$abstract]) + : $abstract; } /** diff --git a/src/Illuminate/Container/ContextualBindingBuilder.php b/src/Illuminate/Container/ContextualBindingBuilder.php index 707b74c74beb..0f3163f9403a 100644 --- a/src/Illuminate/Container/ContextualBindingBuilder.php +++ b/src/Illuminate/Container/ContextualBindingBuilder.php @@ -33,7 +33,6 @@ class ContextualBindingBuilder implements ContextualBindingBuilderContract * * @param \Illuminate\Contracts\Container\Container $container * @param string|array $concrete - * @return void */ public function __construct(Container $container, $concrete) { diff --git a/src/Illuminate/Container/RewindableGenerator.php b/src/Illuminate/Container/RewindableGenerator.php index 14c0bd01789b..53013f8fa952 100644 --- a/src/Illuminate/Container/RewindableGenerator.php +++ b/src/Illuminate/Container/RewindableGenerator.php @@ -27,7 +27,6 @@ class RewindableGenerator implements Countable, IteratorAggregate * * @param callable $generator * @param callable|int $count - * @return void */ public function __construct(callable $generator, $count) { diff --git a/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index ab9f51c8f545..16d737f2a216 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -15,7 +15,7 @@ ], "require": { "php": "^8.2", - "illuminate/contracts": "^11.0", + "illuminate/contracts": "^12.0", "psr/container": "^1.1.1|^2.0.1" }, "provide": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Contracts/Container/Container.php b/src/Illuminate/Contracts/Container/Container.php index acd5dcf5eb9b..c00ddf5cafdb 100644 --- a/src/Illuminate/Contracts/Container/Container.php +++ b/src/Illuminate/Contracts/Container/Container.php @@ -56,7 +56,7 @@ public function tagged($tag); /** * Register a binding with the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void @@ -75,7 +75,7 @@ public function bindMethod($method, $callback); /** * Register a binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void @@ -85,7 +85,7 @@ public function bindIf($abstract, $concrete = null, $shared = false); /** * Register a shared binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -94,7 +94,7 @@ public function singleton($abstract, $concrete = null); /** * Register a shared binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -103,7 +103,7 @@ public function singletonIf($abstract, $concrete = null); /** * Register a scoped binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -112,7 +112,7 @@ public function scoped($abstract, $concrete = null); /** * Register a scoped binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -121,7 +121,7 @@ public function scopedIf($abstract, $concrete = null); /** * "Extend" an abstract type in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure $closure * @return void * @@ -134,7 +134,7 @@ public function extend($abstract, Closure $closure); * * @template TInstance of mixed * - * @param string $abstract + * @param \Closure|string $abstract * @param TInstance $instance * @return TInstance */ @@ -144,7 +144,7 @@ public function instance($abstract, $instance); * Add a contextual binding to the container. * * @param string $concrete - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string $implementation * @return void */ diff --git a/src/Illuminate/Contracts/Database/ModelIdentifier.php b/src/Illuminate/Contracts/Database/ModelIdentifier.php index 5742e8243ddf..95db3f4a74f8 100644 --- a/src/Illuminate/Contracts/Database/ModelIdentifier.php +++ b/src/Illuminate/Contracts/Database/ModelIdentifier.php @@ -48,7 +48,6 @@ class ModelIdentifier * @param mixed $id * @param array $relations * @param mixed $connection - * @return void */ public function __construct($class, $id, array $relations, $connection) { diff --git a/src/Illuminate/Contracts/Filesystem/Filesystem.php b/src/Illuminate/Contracts/Filesystem/Filesystem.php index 43cdaf81cda5..00488a2c2367 100644 --- a/src/Illuminate/Contracts/Filesystem/Filesystem.php +++ b/src/Illuminate/Contracts/Filesystem/Filesystem.php @@ -173,7 +173,7 @@ public function lastModified($path); * * @param string|null $directory * @param bool $recursive - * @return array + * @return array */ public function files($directory = null, $recursive = false); @@ -181,7 +181,7 @@ public function files($directory = null, $recursive = false); * Get all of the files from the given directory (recursive). * * @param string|null $directory - * @return array + * @return array */ public function allFiles($directory = null); @@ -190,7 +190,7 @@ public function allFiles($directory = null); * * @param string|null $directory * @param bool $recursive - * @return array + * @return array */ public function directories($directory = null, $recursive = false); @@ -198,7 +198,7 @@ public function directories($directory = null, $recursive = false); * Get all (recursive) of the directories within a given directory. * * @param string|null $directory - * @return array + * @return array */ public function allDirectories($directory = null); diff --git a/src/Illuminate/Contracts/Log/ContextLogProcessor.php b/src/Illuminate/Contracts/Log/ContextLogProcessor.php new file mode 100644 index 000000000000..ca6900cfbc27 --- /dev/null +++ b/src/Illuminate/Contracts/Log/ContextLogProcessor.php @@ -0,0 +1,9 @@ +validateArray($key, $value) - : CookieValuePrefix::validate($key, $value, $this->encrypter->getAllKeys()); + ? $this->validateArray($key, $value) + : CookieValuePrefix::validate($key, $value, $this->encrypter->getAllKeys()); } /** @@ -142,8 +141,8 @@ protected function validateArray(string $key, array $value) protected function decryptCookie($name, $cookie) { return is_array($cookie) - ? $this->decryptArray($cookie) - : $this->encrypter->decrypt($cookie, static::serialized($name)); + ? $this->decryptArray($cookie) + : $this->encrypter->decrypt($cookie, static::serialized($name)); } /** diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 79c80672d0b2..7184c3d2e351 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -16,12 +16,12 @@ "require": { "php": "^8.2", "ext-hash": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", - "symfony/http-foundation": "^7.0.3", - "symfony/http-kernel": "^7.0.3" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0" }, "autoload": { "psr-4": { @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Database/Capsule/Manager.php b/src/Illuminate/Database/Capsule/Manager.php index cfc47eb5abcf..ddcc85dcf7a0 100755 --- a/src/Illuminate/Database/Capsule/Manager.php +++ b/src/Illuminate/Database/Capsule/Manager.php @@ -25,7 +25,6 @@ class Manager * Create a new database capsule manager. * * @param \Illuminate\Container\Container|null $container - * @return void */ public function __construct(?Container $container = null) { diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index 688d36281f09..f78e13b2d500 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -39,13 +39,21 @@ public function chunk($count, callable $callback) { $this->enforceOrderBy(); + $skip = $this->getOffset(); + $remaining = $this->getLimit(); + $page = 1; do { - // We'll execute the query for the given page and get the results. If there are - // no results we can just break and return from here. When there are results - // we will call the callback with the current chunk of these results here. - $results = $this->forPage($page, $count)->get(); + $offset = (($page - 1) * $count) + intval($skip); + + $limit = is_null($remaining) ? $count : min($count, $remaining); + + if ($limit == 0) { + break; + } + + $results = $this->offset($offset)->limit($limit)->get(); $countResults = $results->count(); @@ -53,9 +61,10 @@ public function chunk($count, callable $callback) break; } - // On each chunk result set, we will pass them to the callback and then let the - // developer take care of everything within the callback, which allows us to - // keep the memory low for spinning through large result sets for working. + if (! is_null($remaining)) { + $remaining = max($remaining - $countResults, 0); + } + if ($callback($results, $page) === false) { return false; } @@ -153,23 +162,33 @@ public function chunkByIdDesc($count, callable $callback, $column = null, $alias public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false) { $column ??= $this->defaultKeyName(); - $alias ??= $column; - $lastId = null; + $skip = $this->getOffset(); + $remaining = $this->getLimit(); $page = 1; do { $clone = clone $this; + if ($skip && $page > 1) { + $clone->offset(0); + } + + $limit = is_null($remaining) ? $count : min($count, $remaining); + + if ($limit == 0) { + break; + } + // We'll execute the query for the given page and get the results. If there are // no results we can just break and return from here. When there are results // we will call the callback with the current chunk of these results here. if ($descending) { - $results = $clone->forPageBeforeId($count, $lastId, $column)->get(); + $results = $clone->forPageBeforeId($limit, $lastId, $column)->get(); } else { - $results = $clone->forPageAfterId($count, $lastId, $column)->get(); + $results = $clone->forPageAfterId($limit, $lastId, $column)->get(); } $countResults = $results->count(); @@ -178,6 +197,10 @@ public function orderedChunkById($count, callable $callback, $column = null, $al break; } + if (! is_null($remaining)) { + $remaining = max($remaining - $countResults, 0); + } + // On each chunk result set, we will pass them to the callback and then let the // developer take care of everything within the callback, which allows us to // keep the memory low for spinning through large result sets for working. @@ -564,7 +587,7 @@ protected function cursorPaginator($items, $perPage, $cursor, $options) } /** - * Pass the query to a given callback. + * Pass the query to a given callback and then return it. * * @param callable($this): mixed $callback * @return $this @@ -575,4 +598,17 @@ public function tap($callback) return $this; } + + /** + * Pass the query to a given callback and return the result. + * + * @template TReturn + * + * @param (callable($this): TReturn) $callback + * @return (TReturn is null|void ? $this : TReturn) + */ + public function pipe($callback) + { + return $callback($this) ?? $this; + } } diff --git a/src/Illuminate/Database/Concerns/BuildsWhereDateClauses.php b/src/Illuminate/Database/Concerns/BuildsWhereDateClauses.php index ea6048975525..06da84427365 100644 --- a/src/Illuminate/Database/Concerns/BuildsWhereDateClauses.php +++ b/src/Illuminate/Database/Concerns/BuildsWhereDateClauses.php @@ -99,6 +99,8 @@ public function orWhereNowOrFuture($columns) * Add an "where" clause to determine if a "date" column is in the past or future. * * @param array|string $columns + * @param string $operator + * @param string $boolean * @return $this */ protected function wherePastOrFuture($columns, $operator, $boolean) @@ -197,7 +199,6 @@ public function orWhereBeforeToday($columns) * Add an "or where date" clause to determine if a "date" column is today or before to the query. * * @param array|string $columns - * @param string $boolean * @return $this */ public function orWhereTodayOrBefore($columns) @@ -209,7 +210,6 @@ public function orWhereTodayOrBefore($columns) * Add an "or where date" clause to determine if a "date" column is after today. * * @param array|string $columns - * @param string $boolean * @return $this */ public function orWhereAfterToday($columns) @@ -221,7 +221,6 @@ public function orWhereAfterToday($columns) * Add an "or where date" clause to determine if a "date" column is today or after to the query. * * @param array|string $columns - * @param string $boolean * @return $this */ public function orWhereTodayOrAfter($columns) diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index e7ac09423d16..23bc60434e49 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -255,8 +255,8 @@ public function rollBack($toLevel = null) // that this given transaction level is valid before attempting to rollback to // that level. If it's not we will just return out and not attempt anything. $toLevel = is_null($toLevel) - ? $this->transactions - 1 - : $toLevel; + ? $this->transactions - 1 + : $toLevel; if ($toLevel < 0 || $toLevel >= $this->transactions) { return; diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 64f2eb25bf2b..a883e3edb22e 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -208,7 +208,6 @@ class Connection implements ConnectionInterface * @param string $database * @param string $tablePrefix * @param array $config - * @return void */ public function __construct($pdo, $database = '', $tablePrefix = '', array $config = []) { @@ -248,9 +247,7 @@ public function useDefaultQueryGrammar() */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $grammar; + return new QueryGrammar($this); } /** @@ -1626,41 +1623,26 @@ public function setTablePrefix($prefix) { $this->tablePrefix = $prefix; - $this->getQueryGrammar()->setTablePrefix($prefix); - return $this; } - /** - * Set the table prefix and return the grammar. - * - * @template TGrammar of \Illuminate\Database\Grammar - * - * @param TGrammar $grammar - * @return TGrammar - */ - public function withTablePrefix(Grammar $grammar) - { - $grammar->setTablePrefix($this->tablePrefix); - - return $grammar; - } - /** * Execute the given callback without table prefix. * * @param \Closure $callback - * @return void + * @return mixed */ - public function withoutTablePrefix(Closure $callback): void + public function withoutTablePrefix(Closure $callback): mixed { $tablePrefix = $this->getTablePrefix(); $this->setTablePrefix(''); - $callback($this); - - $this->setTablePrefix($tablePrefix); + try { + return $callback($this); + } finally { + $this->setTablePrefix($tablePrefix); + } } /** diff --git a/src/Illuminate/Database/ConnectionResolver.php b/src/Illuminate/Database/ConnectionResolver.php index dd16ffd65755..b7b6279e1fc5 100755 --- a/src/Illuminate/Database/ConnectionResolver.php +++ b/src/Illuminate/Database/ConnectionResolver.php @@ -22,7 +22,6 @@ class ConnectionResolver implements ConnectionResolverInterface * Create a new connection resolver instance. * * @param array $connections - * @return void */ public function __construct(array $connections = []) { diff --git a/src/Illuminate/Database/Connectors/ConnectionFactory.php b/src/Illuminate/Database/Connectors/ConnectionFactory.php index e8e187565f0e..8660921633a0 100755 --- a/src/Illuminate/Database/Connectors/ConnectionFactory.php +++ b/src/Illuminate/Database/Connectors/ConnectionFactory.php @@ -26,7 +26,6 @@ class ConnectionFactory * Create a new connection factory instance. * * @param \Illuminate\Contracts\Container\Container $container - * @return void */ public function __construct(Container $container) { @@ -138,8 +137,8 @@ protected function getWriteConfig(array $config) protected function getReadWriteConfig(array $config, $type) { return isset($config[$type][0]) - ? Arr::random($config[$type]) - : $config[$type]; + ? Arr::random($config[$type]) + : $config[$type]; } /** @@ -163,8 +162,8 @@ protected function mergeReadWriteConfig(array $config, array $merge) protected function createPdoResolver(array $config) { return array_key_exists('host', $config) - ? $this->createPdoResolverWithHosts($config) - : $this->createPdoResolverWithoutHosts($config); + ? $this->createPdoResolverWithHosts($config) + : $this->createPdoResolverWithoutHosts($config); } /** diff --git a/src/Illuminate/Database/Connectors/MySqlConnector.php b/src/Illuminate/Database/Connectors/MySqlConnector.php index 14c520ed2495..fc55b801407f 100755 --- a/src/Illuminate/Database/Connectors/MySqlConnector.php +++ b/src/Illuminate/Database/Connectors/MySqlConnector.php @@ -45,8 +45,8 @@ public function connect(array $config) protected function getDsn(array $config) { return $this->hasSocket($config) - ? $this->getSocketDsn($config) - : $this->getHostDsn($config); + ? $this->getSocketDsn($config) + : $this->getHostDsn($config); } /** @@ -80,8 +80,8 @@ protected function getSocketDsn(array $config) protected function getHostDsn(array $config) { return isset($config['port']) - ? "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}" - : "mysql:host={$config['host']};dbname={$config['database']}"; + ? "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}" + : "mysql:host={$config['host']};dbname={$config['database']}"; } /** diff --git a/src/Illuminate/Database/Connectors/SQLiteConnector.php b/src/Illuminate/Database/Connectors/SQLiteConnector.php index 8ffdd81aa1c3..2e2ed8758919 100755 --- a/src/Illuminate/Database/Connectors/SQLiteConnector.php +++ b/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -11,32 +11,120 @@ class SQLiteConnector extends Connector implements ConnectorInterface * * @param array $config * @return \PDO - * - * @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException */ public function connect(array $config) { $options = $this->getOptions($config); + $path = $this->parseDatabasePath($config['database']); + + $connection = $this->createConnection("sqlite:{$path}", $config, $options); + + $this->configureForeignKeyConstraints($connection, $config); + $this->configureBusyTimeout($connection, $config); + $this->configureJournalMode($connection, $config); + $this->configureSynchronous($connection, $config); + + return $connection; + } + + /** + * Get the absolute database path. + * + * @param string $path + * @return string + * + * @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException + */ + protected function parseDatabasePath(string $path): string + { + $database = $path; + // SQLite supports "in-memory" databases that only last as long as the owning // connection does. These are useful for tests or for short lifetime store // querying. In-memory databases shall be anonymous (:memory:) or named. - if ($config['database'] === ':memory:' || - str_contains($config['database'], '?mode=memory') || - str_contains($config['database'], '&mode=memory') + if ($path === ':memory:' || + str_contains($path, '?mode=memory') || + str_contains($path, '&mode=memory') ) { - return $this->createConnection('sqlite:'.$config['database'], $config, $options); + return $path; } - $path = realpath($config['database']) ?: realpath(base_path($config['database'])); + $path = realpath($path) ?: realpath(base_path($path)); // Here we'll verify that the SQLite database exists before going any further // as the developer probably wants to know if the database exists and this // SQLite driver will not throw any exception if it does not by default. if ($path === false) { - throw new SQLiteDatabaseDoesNotExistException($config['database']); + throw new SQLiteDatabaseDoesNotExistException($database); + } + + return $path; + } + + /** + * Enable or disable foreign key constraints if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureForeignKeyConstraints($connection, array $config): void + { + if (! isset($config['foreign_key_constraints'])) { + return; + } + + $foreignKeys = $config['foreign_key_constraints'] ? 1 : 0; + + $connection->prepare("pragma foreign_keys = {$foreignKeys}")->execute(); + } + + /** + * Set the busy timeout if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureBusyTimeout($connection, array $config): void + { + if (! isset($config['busy_timeout'])) { + return; + } + + $connection->prepare("pragma busy_timeout = {$config['busy_timeout']}")->execute(); + } + + /** + * Set the journal mode if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureJournalMode($connection, array $config): void + { + if (! isset($config['journal_mode'])) { + return; + } + + $connection->prepare("pragma journal_mode = {$config['journal_mode']}")->execute(); + } + + /** + * Set the synchronous mode if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureSynchronous($connection, array $config): void + { + if (! isset($config['synchronous'])) { + return; } - return $this->createConnection("sqlite:{$path}", $config, $options); + $connection->prepare("pragma synchronous = {$config['synchronous']}")->execute(); } } diff --git a/src/Illuminate/Database/Connectors/SqlServerConnector.php b/src/Illuminate/Database/Connectors/SqlServerConnector.php index b6ed47d196ac..14cb72dbbf41 100755 --- a/src/Illuminate/Database/Connectors/SqlServerConnector.php +++ b/src/Illuminate/Database/Connectors/SqlServerConnector.php @@ -113,7 +113,8 @@ protected function getDblibDsn(array $config) protected function getOdbcDsn(array $config) { return isset($config['odbc_datasource_name']) - ? 'odbc:'.$config['odbc_datasource_name'] : ''; + ? 'odbc:'.$config['odbc_datasource_name'] + : ''; } /** diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php index 00fc9257690e..8faab04147ab 100644 --- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php +++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php @@ -47,20 +47,4 @@ protected function getConfigFromDatabase($database) return Arr::except(config('database.connections.'.$database), ['password']); } - - /** - * Remove the table prefix from a table name, if it exists. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return string - */ - protected function withoutTablePrefix(ConnectionInterface $connection, string $table) - { - $prefix = $connection->getTablePrefix(); - - return str_starts_with($table, $prefix) - ? substr($table, strlen($prefix)) - : $table; - } } diff --git a/src/Illuminate/Database/Console/DbCommand.php b/src/Illuminate/Database/Console/DbCommand.php index 36b6db3028fc..9737bcab18ea 100644 --- a/src/Illuminate/Database/Console/DbCommand.php +++ b/src/Illuminate/Database/Console/DbCommand.php @@ -142,7 +142,7 @@ public function getCommand(array $connection) { return [ 'mysql' => 'mysql', - 'mariadb' => 'mysql', + 'mariadb' => 'mariadb', 'pgsql' => 'psql', 'sqlite' => 'sqlite3', 'sqlsrv' => 'sqlcmd', diff --git a/src/Illuminate/Database/Console/DumpCommand.php b/src/Illuminate/Database/Console/DumpCommand.php index b27d6c66a93c..0c038939f0de 100644 --- a/src/Illuminate/Database/Console/DumpCommand.php +++ b/src/Illuminate/Database/Console/DumpCommand.php @@ -53,7 +53,7 @@ public function handle(ConnectionResolverInterface $connections, Dispatcher $dis if ($this->option('prune')) { (new Filesystem)->deleteDirectory( - $path = database_path('migrations'), $preserve = false + $path = database_path('migrations'), preserve: false ); $info .= ' and pruned'; diff --git a/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php b/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php index 495a33ccd001..6d080a143923 100644 --- a/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php +++ b/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php @@ -66,8 +66,8 @@ protected function buildClass($name) $factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name))); $namespaceModel = $this->option('model') - ? $this->qualifyModel($this->option('model')) - : $this->qualifyModel($this->guessModelName($name)); + ? $this->qualifyModel($this->option('model')) + : $this->qualifyModel($this->guessModelName($name)); $model = class_basename($namespaceModel); diff --git a/src/Illuminate/Database/Console/Migrations/BaseCommand.php b/src/Illuminate/Database/Console/Migrations/BaseCommand.php index d2a8aee0d9a5..a250d2945fde 100755 --- a/src/Illuminate/Database/Console/Migrations/BaseCommand.php +++ b/src/Illuminate/Database/Console/Migrations/BaseCommand.php @@ -20,8 +20,8 @@ protected function getMigrationPaths() if ($this->input->hasOption('path') && $this->option('path')) { return (new Collection($this->option('path')))->map(function ($path) { return ! $this->usingRealPath() - ? $this->laravel->basePath().'/'.$path - : $path; + ? $this->laravel->basePath().'/'.$path + : $path; })->all(); } diff --git a/src/Illuminate/Database/Console/Migrations/FreshCommand.php b/src/Illuminate/Database/Console/Migrations/FreshCommand.php index 45900ffe7a45..723d3c2298a4 100644 --- a/src/Illuminate/Database/Console/Migrations/FreshCommand.php +++ b/src/Illuminate/Database/Console/Migrations/FreshCommand.php @@ -41,7 +41,6 @@ class FreshCommand extends Command * Create a new fresh command instance. * * @param \Illuminate\Database\Migrations\Migrator $migrator - * @return void */ public function __construct(Migrator $migrator) { diff --git a/src/Illuminate/Database/Console/Migrations/InstallCommand.php b/src/Illuminate/Database/Console/Migrations/InstallCommand.php index 144ff512671b..b89cd4b4e86f 100755 --- a/src/Illuminate/Database/Console/Migrations/InstallCommand.php +++ b/src/Illuminate/Database/Console/Migrations/InstallCommand.php @@ -35,7 +35,6 @@ class InstallCommand extends Command * Create a new migration install command instance. * * @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository - * @return void */ public function __construct(MigrationRepositoryInterface $repository) { @@ -53,7 +52,9 @@ public function handle() { $this->repository->setSource($this->input->getOption('database')); - $this->repository->createRepository(); + if (! $this->repository->repositoryExists()) { + $this->repository->createRepository(); + } $this->components->info('Migration table created successfully.'); } diff --git a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php index 6345985bf06f..497836c65a49 100755 --- a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php +++ b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php @@ -64,7 +64,6 @@ class MigrateCommand extends BaseCommand implements Isolatable * * @param \Illuminate\Database\Migrations\Migrator $migrator * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher - * @return void */ public function __construct(Migrator $migrator, Dispatcher $dispatcher) { diff --git a/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php b/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php index 367f14839e64..c9494c5d5c44 100644 --- a/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php +++ b/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php @@ -51,7 +51,6 @@ class MigrateMakeCommand extends BaseCommand implements PromptsForMissingInput * * @param \Illuminate\Database\Migrations\MigrationCreator $creator * @param \Illuminate\Support\Composer $composer - * @return void */ public function __construct(MigrationCreator $creator, Composer $composer) { @@ -125,8 +124,8 @@ protected function getMigrationPath() { if (! is_null($targetPath = $this->input->getOption('path'))) { return ! $this->usingRealPath() - ? $this->laravel->basePath().'/'.$targetPath - : $targetPath; + ? $this->laravel->basePath().'/'.$targetPath + : $targetPath; } return parent::getMigrationPath(); diff --git a/src/Illuminate/Database/Console/Migrations/ResetCommand.php b/src/Illuminate/Database/Console/Migrations/ResetCommand.php index 85ccae9734e0..787801bab258 100755 --- a/src/Illuminate/Database/Console/Migrations/ResetCommand.php +++ b/src/Illuminate/Database/Console/Migrations/ResetCommand.php @@ -39,7 +39,6 @@ class ResetCommand extends BaseCommand * Create a new migration rollback command instance. * * @param \Illuminate\Database\Migrations\Migrator $migrator - * @return void */ public function __construct(Migrator $migrator) { diff --git a/src/Illuminate/Database/Console/Migrations/RollbackCommand.php b/src/Illuminate/Database/Console/Migrations/RollbackCommand.php index 8846a5e376cf..9c3543ec5bfe 100755 --- a/src/Illuminate/Database/Console/Migrations/RollbackCommand.php +++ b/src/Illuminate/Database/Console/Migrations/RollbackCommand.php @@ -39,7 +39,6 @@ class RollbackCommand extends BaseCommand * Create a new migration rollback command instance. * * @param \Illuminate\Database\Migrations\Migrator $migrator - * @return void */ public function __construct(Migrator $migrator) { diff --git a/src/Illuminate/Database/Console/Migrations/StatusCommand.php b/src/Illuminate/Database/Console/Migrations/StatusCommand.php index 378c4a720d26..cbb16a133c73 100644 --- a/src/Illuminate/Database/Console/Migrations/StatusCommand.php +++ b/src/Illuminate/Database/Console/Migrations/StatusCommand.php @@ -36,7 +36,6 @@ class StatusCommand extends BaseCommand * Create a new migration rollback command instance. * * @param \Illuminate\Database\Migrations\Migrator $migrator - * @return void */ public function __construct(Migrator $migrator) { diff --git a/src/Illuminate/Database/Console/PruneCommand.php b/src/Illuminate/Database/Console/PruneCommand.php index db8dead36342..a7b58e560189 100644 --- a/src/Illuminate/Database/Console/PruneCommand.php +++ b/src/Illuminate/Database/Console/PruneCommand.php @@ -138,15 +138,11 @@ protected function models() ['\\', ''], Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR) ); - })->when(! empty($except), function ($models) use ($except) { - return $models->reject(function ($model) use ($except) { - return in_array($model, $except); - }); - })->filter(function ($model) { - return class_exists($model); - })->filter(function ($model) { - return $this->isPrunable($model); - })->values(); + }) + ->when(! empty($except), fn ($models) => $models->reject(fn ($model) => in_array($model, $except))) + ->filter(fn ($model) => class_exists($model)) + ->filter(fn ($model) => $this->isPrunable($model)) + ->values(); } /** diff --git a/src/Illuminate/Database/Console/Seeds/SeedCommand.php b/src/Illuminate/Database/Console/Seeds/SeedCommand.php index 4ce2b0213129..515ff410b30c 100644 --- a/src/Illuminate/Database/Console/Seeds/SeedCommand.php +++ b/src/Illuminate/Database/Console/Seeds/SeedCommand.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; +use Illuminate\Console\Prohibitable; use Illuminate\Database\ConnectionResolverInterface as Resolver; use Illuminate\Database\Eloquent\Model; use Symfony\Component\Console\Attribute\AsCommand; @@ -13,7 +14,7 @@ #[AsCommand(name: 'db:seed')] class SeedCommand extends Command { - use ConfirmableTrait; + use ConfirmableTrait, Prohibitable; /** * The console command name. @@ -40,7 +41,6 @@ class SeedCommand extends Command * Create a new database seed command instance. * * @param \Illuminate\Database\ConnectionResolverInterface $resolver - * @return void */ public function __construct(Resolver $resolver) { @@ -56,8 +56,9 @@ public function __construct(Resolver $resolver) */ public function handle() { - if (! $this->confirmToProceed()) { - return 1; + if ($this->isProhibited() || + ! $this->confirmToProceed()) { + return Command::FAILURE; } $this->components->info('Seeding database.'); diff --git a/src/Illuminate/Database/Console/ShowCommand.php b/src/Illuminate/Database/Console/ShowCommand.php index 3abc69bbe83e..64c80572b927 100644 --- a/src/Illuminate/Database/Console/ShowCommand.php +++ b/src/Illuminate/Database/Console/ShowCommand.php @@ -8,7 +8,6 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Number; -use Illuminate\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:show')] @@ -80,9 +79,10 @@ protected function tables(ConnectionInterface $connection, Builder $schema) return (new Collection($schema->getTables()))->map(fn ($table) => [ 'table' => $table['name'], 'schema' => $table['schema'], + 'schema_qualified_name' => $table['schema_qualified_name'], 'size' => $table['size'], 'rows' => $this->option('counts') - ? ($connection->table($table['schema'] ? $table['schema'].'.'.$table['name'] : $table['name'])->count()) + ? $connection->withoutTablePrefix(fn ($connection) => $connection->table($table['schema_qualified_name'])->count()) : null, 'engine' => $table['engine'], 'collation' => $table['collation'], @@ -100,11 +100,10 @@ protected function tables(ConnectionInterface $connection, Builder $schema) protected function views(ConnectionInterface $connection, Builder $schema) { return (new Collection($schema->getViews())) - ->reject(fn ($view) => (new Stringable($view['name']))->startsWith(['pg_catalog', 'information_schema', 'spt_'])) ->map(fn ($view) => [ 'view' => $view['name'], 'schema' => $view['schema'], - 'rows' => $connection->table($view['schema'] ? $view['schema'].'.'.$view['name'] : $view['name'])->count(), + 'rows' => $connection->withoutTablePrefix(fn ($connection) => $connection->table($view['schema_qualified_name'])->count()), ]); } diff --git a/src/Illuminate/Database/Console/TableCommand.php b/src/Illuminate/Database/Console/TableCommand.php index ccde5c29680c..fde40a78f8a3 100644 --- a/src/Illuminate/Database/Console/TableCommand.php +++ b/src/Illuminate/Database/Console/TableCommand.php @@ -39,10 +39,8 @@ class TableCommand extends DatabaseInspectionCommand public function handle(ConnectionResolverInterface $connections) { $connection = $connections->connection($this->input->getOption('database')); - $schema = $connection->getSchemaBuilder(); - $tables = (new Collection($schema->getTables())) - ->keyBy(fn ($table) => $table['schema'] ? $table['schema'].'.'.$table['name'] : $table['name']) - ->all(); + $tables = (new Collection($connection->getSchemaBuilder()->getTables())) + ->keyBy('schema_qualified_name')->all(); $tableName = $this->argument('table') ?: select( 'Which table would you like to inspect?', @@ -57,16 +55,22 @@ public function handle(ConnectionResolverInterface $connections) return 1; } - $tableName = ($table['schema'] ? $table['schema'].'.' : '').$this->withoutTablePrefix($connection, $table['name']); + [$columns, $indexes, $foreignKeys] = $connection->withoutTablePrefix(function ($connection) use ($table) { + $schema = $connection->getSchemaBuilder(); + $tableName = $table['schema_qualified_name']; - $columns = $this->columns($schema, $tableName); - $indexes = $this->indexes($schema, $tableName); - $foreignKeys = $this->foreignKeys($schema, $tableName); + return [ + $this->columns($schema, $tableName), + $this->indexes($schema, $tableName), + $this->foreignKeys($schema, $tableName), + ]; + }); $data = [ 'table' => [ 'schema' => $table['schema'], 'name' => $table['name'], + 'schema_qualified_name' => $table['schema_qualified_name'], 'columns' => count($columns), 'size' => $table['size'], 'comment' => $table['comment'], @@ -205,7 +209,7 @@ protected function displayForCli(array $data) $this->newLine(); - $this->components->twoColumnDetail(''.($table['schema'] ? $table['schema'].'.'.$table['name'] : $table['name']).'', $table['comment'] ? ''.$table['comment'].'' : null); + $this->components->twoColumnDetail(''.$table['schema_qualified_name'].'', $table['comment'] ? ''.$table['comment'].'' : null); $this->components->twoColumnDetail('Columns', $table['columns']); if (! is_null($table['size'])) { diff --git a/src/Illuminate/Database/DatabaseManager.php b/src/Illuminate/Database/DatabaseManager.php index 34ba2cc01bd9..4d3aafc83fe3 100755 --- a/src/Illuminate/Database/DatabaseManager.php +++ b/src/Illuminate/Database/DatabaseManager.php @@ -69,7 +69,6 @@ class DatabaseManager implements ConnectionResolverInterface * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Database\Connectors\ConnectionFactory $factory - * @return void */ public function __construct($app, ConnectionFactory $factory) { @@ -177,7 +176,8 @@ protected function parseConnectionName($name) $name = $name ?: $this->getDefaultConnection(); return Str::endsWith($name, ['::read', '::write']) - ? explode('::', $name, 2) : [$name, null]; + ? explode('::', $name, 2) + : [$name, null]; } /** diff --git a/src/Illuminate/Database/DatabaseTransactionRecord.php b/src/Illuminate/Database/DatabaseTransactionRecord.php index c35acb184aa5..08fd47132394 100755 --- a/src/Illuminate/Database/DatabaseTransactionRecord.php +++ b/src/Illuminate/Database/DatabaseTransactionRecord.php @@ -32,13 +32,19 @@ class DatabaseTransactionRecord */ protected $callbacks = []; + /** + * The callbacks that should be executed after rollback. + * + * @var array + */ + protected $callbacksForRollback = []; + /** * Create a new database transaction record instance. * * @param string $connection * @param int $level * @param \Illuminate\Database\DatabaseTransactionRecord|null $parent - * @return void */ public function __construct($connection, $level, ?DatabaseTransactionRecord $parent = null) { @@ -58,6 +64,17 @@ public function addCallback($callback) $this->callbacks[] = $callback; } + /** + * Register a callback to be executed after rollback. + * + * @param callable $callback + * @return void + */ + public function addCallbackForRollback($callback) + { + $this->callbacksForRollback[] = $callback; + } + /** * Execute all of the callbacks. * @@ -70,6 +87,18 @@ public function executeCallbacks() } } + /** + * Execute all of the callbacks for rollback. + * + * @return void + */ + public function executeCallbacksForRollback() + { + foreach ($this->callbacksForRollback as $callback) { + $callback(); + } + } + /** * Get all of the callbacks. * @@ -79,4 +108,14 @@ public function getCallbacks() { return $this->callbacks; } + + /** + * Get all of the callbacks for rollback. + * + * @return array + */ + public function getCallbacksForRollback() + { + return $this->callbacksForRollback; + } } diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index ee2889a2d18a..9713c66d82f4 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -29,8 +29,6 @@ class DatabaseTransactionsManager /** * Create a new database transactions manager instance. - * - * @return void */ public function __construct() { @@ -141,6 +139,8 @@ public function rollback($connection, $newTransactionLevel) do { $this->removeCommittedTransactionsThatAreChildrenOf($this->currentTransaction[$connection]); + $this->currentTransaction[$connection]->executeCallbacksForRollback(); + $this->currentTransaction[$connection] = $this->currentTransaction[$connection]->parent; } while ( isset($this->currentTransaction[$connection]) && @@ -158,6 +158,12 @@ public function rollback($connection, $newTransactionLevel) */ protected function removeAllTransactionsForConnection($connection) { + if ($this->currentTransaction) { + for ($currentTransaction = $this->currentTransaction[$connection]; isset($currentTransaction); $currentTransaction = $currentTransaction->parent) { + $currentTransaction->executeCallbacksForRollback(); + } + } + $this->currentTransaction[$connection] = null; $this->pendingTransactions = $this->pendingTransactions->reject( @@ -205,6 +211,19 @@ public function addCallback($callback) $callback(); } + /** + * Register a callback for transaction rollback. + * + * @param callable $callback + * @return void + */ + public function addCallbackForRollback($callback) + { + if ($current = $this->callbackApplicableTransactions()->last()) { + return $current->addCallbackForRollback($callback); + } + } + /** * Get the transactions that are applicable to callbacks. * diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index f489b28409dc..72b5a043288e 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -65,6 +65,7 @@ protected function causedByLostConnection(Throwable $e) 'SSL: Handshake timed out', 'SSL error: sslv3 alert unexpected message', 'unrecognized SSL error code:', + 'SQLSTATE[HY000] [1045] Access denied for user', 'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it', 'SQLSTATE[HY000] [2002] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond', 'SQLSTATE[HY000] [2002] Network is unreachable', diff --git a/src/Illuminate/Database/Eloquent/Attributes/CollectedBy.php b/src/Illuminate/Database/Eloquent/Attributes/CollectedBy.php index 14eb3a43745d..356b85d83233 100644 --- a/src/Illuminate/Database/Eloquent/Attributes/CollectedBy.php +++ b/src/Illuminate/Database/Eloquent/Attributes/CollectedBy.php @@ -11,7 +11,6 @@ class CollectedBy * Create a new attribute instance. * * @param class-string<\Illuminate\Database\Eloquent\Collection<*, *>> $collectionClass - * @return void */ public function __construct(public string $collectionClass) { diff --git a/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php index 3db5182db7e5..32a9fffc07ca 100644 --- a/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php +++ b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php @@ -11,7 +11,6 @@ class ObservedBy * Create a new attribute instance. * * @param array|string $classes - * @return void */ public function __construct(public array|string $classes) { diff --git a/src/Illuminate/Database/Eloquent/Attributes/Scope.php b/src/Illuminate/Database/Eloquent/Attributes/Scope.php new file mode 100644 index 000000000000..d340db490f98 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/Scope.php @@ -0,0 +1,16 @@ + $factoryClass - * @return void */ public function __construct(public string $factoryClass) { diff --git a/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php b/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php index 84eaf9e10582..8bd028032e67 100644 --- a/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php +++ b/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php @@ -59,7 +59,6 @@ class BroadcastableModelEventOccurred implements ShouldBroadcast * * @param \Illuminate\Database\Eloquent\Model $model * @param string $event - * @return void */ public function __construct($model, $event) { @@ -75,8 +74,8 @@ public function __construct($model, $event) public function broadcastOn() { $channels = empty($this->channels) - ? ($this->model->broadcastOn($this->event) ?: []) - : $this->channels; + ? ($this->model->broadcastOn($this->event) ?: []) + : $this->channels; return (new BaseCollection($channels)) ->map(fn ($channel) => $channel instanceof Model ? new PrivateChannel($channel) : $channel) @@ -93,8 +92,8 @@ public function broadcastAs() $default = class_basename($this->model).ucfirst($this->event); return method_exists($this->model, 'broadcastAs') - ? ($this->model->broadcastAs($this->event) ?: $default) - : $default; + ? ($this->model->broadcastAs($this->event) ?: $default) + : $default; } /** diff --git a/src/Illuminate/Database/Eloquent/BroadcastsEvents.php b/src/Illuminate/Database/Eloquent/BroadcastsEvents.php index f075dbc58322..c0461ddb0afd 100644 --- a/src/Illuminate/Database/Eloquent/BroadcastsEvents.php +++ b/src/Illuminate/Database/Eloquent/BroadcastsEvents.php @@ -130,16 +130,16 @@ public function newBroadcastableModelEvent($event) { return tap($this->newBroadcastableEvent($event), function ($event) { $event->connection = property_exists($this, 'broadcastConnection') - ? $this->broadcastConnection - : $this->broadcastConnection(); + ? $this->broadcastConnection + : $this->broadcastConnection(); $event->queue = property_exists($this, 'broadcastQueue') - ? $this->broadcastQueue - : $this->broadcastQueue(); + ? $this->broadcastQueue + : $this->broadcastQueue(); $event->afterCommit = property_exists($this, 'broadcastAfterCommit') - ? $this->broadcastAfterCommit - : $this->broadcastAfterCommit(); + ? $this->broadcastAfterCommit + : $this->broadcastAfterCommit(); }); } diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 1c27bb2ff773..4e22b9ae9fa2 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -168,7 +168,6 @@ class Builder implements BuilderContract * Create a new Eloquent query builder instance. * * @param \Illuminate\Database\Query\Builder $query - * @return void */ public function __construct(QueryBuilder $query) { @@ -447,6 +446,67 @@ public function hydrate(array $items) }, $items)); } + /** + * Insert into the database after merging the model's default attributes, setting timestamps, and casting values. + * + * @param array> $values + * @return bool + */ + public function fillAndInsert(array $values) + { + return $this->insert($this->fillForInsert($values)); + } + + /** + * Insert (ignoring errors) into the database after merging the model's default attributes, setting timestamps, and casting values. + * + * @param array> $values + * @return int + */ + public function fillAndInsertOrIgnore(array $values) + { + return $this->insertOrIgnore($this->fillForInsert($values)); + } + + /** + * Insert a record into the database and get its ID after merging the model's default attributes, setting timestamps, and casting values. + * + * @param array $values + * @return int + */ + public function fillAndInsertGetId(array $values) + { + return $this->insertGetId($this->fillForInsert([$values])[0]); + } + + /** + * Enrich the given values by merging in the model's default attributes, adding timestamps, and casting values. + * + * @param array> $values + * @return array> + */ + public function fillForInsert(array $values) + { + if (empty($values)) { + return []; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } + + $this->model->unguarded(function () use (&$values) { + foreach ($values as $key => $rowValues) { + $values[$key] = tap( + $this->newModelInstance($rowValues), + fn ($model) => $model->setUniqueIds() + )->getAttributes(); + } + }); + + return $this->addTimestampsToUpsertValues($values); + } + /** * Create a collection of models from a raw query. * @@ -1018,7 +1078,7 @@ public function pluck($column, $key = null) * @param string $pageName * @param int|null $page * @param \Closure|int|null $total - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator * * @throws \InvalidArgumentException */ @@ -1505,7 +1565,8 @@ protected function callScope(callable $scope, array $parameters = []) // scope so that we can properly group the added scope constraints in the // query as their own isolated nested where statement and avoid issues. $originalWhereCount = is_null($query->wheres) - ? 0 : count($query->wheres); + ? 0 + : count($query->wheres); $result = $scope(...$parameters) ?? $this; @@ -1595,7 +1656,7 @@ protected function createNestedWhere($whereSlice, $boolean = 'and') } /** - * Set the relationships that should be eager loaded. + * Specify relationships that should be eager loaded. * * @param array): mixed)|string>|string $relations * @param (\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null $callback @@ -1779,8 +1840,8 @@ protected function createSelectWithConstraint($name) return [explode(':', $name)[0], static function ($query) use ($name) { $query->select(array_map(static function ($column) use ($query) { return $query instanceof BelongsToMany - ? $query->getRelated()->qualifyColumn($column) - : $column; + ? $query->getRelated()->qualifyColumn($column) + : $column; }, explode(',', explode(':', $name)[1]))); }]; } @@ -1819,16 +1880,19 @@ protected function addNestedWiths($name, $results) * * @param \Illuminate\Contracts\Database\Query\Expression|array|string $attributes * @param mixed $value + * @param bool $asConditions * @return $this */ - public function withAttributes(Expression|array|string $attributes, $value = null) + public function withAttributes(Expression|array|string $attributes, $value = null, $asConditions = true) { if (! is_array($attributes)) { $attributes = [$attributes => $value]; } - foreach ($attributes as $column => $value) { - $this->where($this->qualifyColumn($column), $value); + if ($asConditions) { + foreach ($attributes as $column => $value) { + $this->where($this->qualifyColumn($column), $value); + } } $this->pendingAttributes = array_merge($this->pendingAttributes, $attributes); @@ -1955,6 +2019,26 @@ public function withoutEagerLoads() return $this->setEagerLoads([]); } + /** + * Get the "limit" value from the query or null if it's not set. + * + * @return mixed + */ + public function getLimit() + { + return $this->query->getLimit(); + } + + /** + * Get the "offset" value from the query or null if it's not set. + * + * @return mixed + */ + public function getOffset() + { + return $this->query->getOffset(); + } + /** * Get the default key name of the table. * diff --git a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php index e71df5a3df3c..e36b13df2184 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Support\Collection; +use Illuminate\Support\Str; use InvalidArgumentException; class AsCollection implements Castable @@ -21,6 +22,7 @@ public static function castUsing(array $arguments) { public function __construct(protected array $arguments) { + $this->arguments = array_pad(array_values($this->arguments), 2, ''); } public function get($model, $key, $value, $attributes) @@ -31,13 +33,29 @@ public function get($model, $key, $value, $attributes) $data = Json::decode($attributes[$key]); - $collectionClass = $this->arguments[0] ?? Collection::class; + $collectionClass = empty($this->arguments[0]) ? Collection::class : $this->arguments[0]; if (! is_a($collectionClass, Collection::class, true)) { throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].'); } - return is_array($data) ? new $collectionClass($data) : null; + if (! is_array($data)) { + return null; + } + + $instance = new $collectionClass($data); + + if (! isset($this->arguments[1]) || ! $this->arguments[1]) { + return $instance; + } + + if (is_string($this->arguments[1])) { + $this->arguments[1] = Str::parseCallback($this->arguments[1]); + } + + return is_callable($this->arguments[1]) + ? $instance->map($this->arguments[1]) + : $instance->mapInto($this->arguments[1][0]); } public function set($model, $key, $value, $attributes) @@ -48,13 +66,29 @@ public function set($model, $key, $value, $attributes) } /** - * Specify the collection for the cast. + * Specify the type of object each item in the collection should be mapped to. + * + * @param array{class-string, string}|class-string $map + * @return string + */ + public static function of($map) + { + return static::using('', $map); + } + + /** + * Specify the collection type for the cast. * * @param class-string $class + * @param array{class-string, string}|class-string $map * @return string */ - public static function using($class) + public static function using($class, $map = null) { - return static::class.':'.$class; + if (is_array($map) && is_callable($map)) { + $map = $map[0].'@'.$map[1]; + } + + return static::class.':'.implode(',', [$class, $map]); } } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index a192d2b0c121..b5912fa20b10 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Str; use InvalidArgumentException; class AsEncryptedCollection implements Castable @@ -22,21 +23,34 @@ public static function castUsing(array $arguments) { public function __construct(protected array $arguments) { + $this->arguments = array_pad(array_values($this->arguments), 2, ''); } public function get($model, $key, $value, $attributes) { - $collectionClass = $this->arguments[0] ?? Collection::class; + $collectionClass = empty($this->arguments[0]) ? Collection::class : $this->arguments[0]; if (! is_a($collectionClass, Collection::class, true)) { throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].'); } - if (isset($attributes[$key])) { - return new $collectionClass(Json::decode(Crypt::decryptString($attributes[$key]))); + if (! isset($attributes[$key])) { + return null; } - return null; + $instance = new $collectionClass(Json::decode(Crypt::decryptString($attributes[$key]))); + + if (! isset($this->arguments[1]) || ! $this->arguments[1]) { + return $instance; + } + + if (is_string($this->arguments[1])) { + $this->arguments[1] = Str::parseCallback($this->arguments[1]); + } + + return is_callable($this->arguments[1]) + ? $instance->map($this->arguments[1]) + : $instance->mapInto($this->arguments[1][0]); } public function set($model, $key, $value, $attributes) @@ -50,14 +64,30 @@ public function set($model, $key, $value, $attributes) }; } + /** + * Specify the type of object each item in the collection should be mapped to. + * + * @param array{class-string, string}|class-string $map + * @return string + */ + public static function of($map) + { + return static::using('', $map); + } + /** * Specify the collection for the cast. * * @param class-string $class + * @param array{class-string, string}|class-string $map * @return string */ - public static function using($class) + public static function using($class, $map = null) { - return static::class.':'.$class; + if (is_array($map) && is_callable($map)) { + $map = $map[0].'@'.$map[1]; + } + + return static::class.':'.implode(',', [$class, $map]); } } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsHtmlString.php b/src/Illuminate/Database/Eloquent/Casts/AsHtmlString.php new file mode 100644 index 000000000000..d4182d258f79 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Casts/AsHtmlString.php @@ -0,0 +1,32 @@ + + */ + public static function castUsing(array $arguments) + { + return new class implements CastsAttributes + { + public function get($model, $key, $value, $attributes) + { + return isset($value) ? new HtmlString($value) : null; + } + + public function set($model, $key, $value, $attributes) + { + return isset($value) ? (string) $value : null; + } + }; + } +} diff --git a/src/Illuminate/Database/Eloquent/Casts/Attribute.php b/src/Illuminate/Database/Eloquent/Casts/Attribute.php index 4fe2d807b690..26d13ba3fbe3 100644 --- a/src/Illuminate/Database/Eloquent/Casts/Attribute.php +++ b/src/Illuminate/Database/Eloquent/Casts/Attribute.php @@ -37,7 +37,6 @@ class Attribute * * @param callable|null $get * @param callable|null $set - * @return void */ public function __construct(?callable $get = null, ?callable $set = null) { diff --git a/src/Illuminate/Database/Eloquent/Casts/Json.php b/src/Illuminate/Database/Eloquent/Casts/Json.php index 6b1a3dc77796..783d5b9986f6 100644 --- a/src/Illuminate/Database/Eloquent/Casts/Json.php +++ b/src/Illuminate/Database/Eloquent/Casts/Json.php @@ -21,9 +21,11 @@ class Json /** * Encode the given value. */ - public static function encode(mixed $value): mixed + public static function encode(mixed $value, int $flags = 0): mixed { - return isset(static::$encoder) ? (static::$encoder)($value) : json_encode($value); + return isset(static::$encoder) + ? (static::$encoder)($value, $flags) + : json_encode($value, $flags); } /** @@ -32,8 +34,8 @@ public static function encode(mixed $value): mixed public static function decode(mixed $value, ?bool $associative = true): mixed { return isset(static::$decoder) - ? (static::$decoder)($value, $associative) - : json_decode($value, $associative); + ? (static::$decoder)($value, $associative) + : json_decode($value, $associative); } /** diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index e2d912ef6040..1c9dad35263f 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -248,6 +248,35 @@ public function loadMissing($relations) return $this; } + /** + * Load a relationship path for models of the given type if it is not already eager loaded. + * + * @param array> $tuples + * @return void + */ + public function loadMissingRelationshipChain(array $tuples) + { + [$relation, $class] = array_shift($tuples); + + $this->filter(function ($model) use ($relation, $class) { + return ! is_null($model) && + ! $model->relationLoaded($relation) && + $model::class === $class; + })->load($relation); + + if (empty($tuples)) { + return; + } + + $models = $this->pluck($relation)->whereNotNull(); + + if ($models->first() instanceof BaseCollection) { + $models = $models->collapse(); + } + + (new static($models))->loadMissingRelationshipChain($tuples); + } + /** * Load a relationship path if it is not already eager loaded. * @@ -672,6 +701,19 @@ public function pad($size, $value) return $this->toBase()->pad($size, $value); } + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param (callable(TModel, TKey): bool)|TModel|string $key + * @param TModel|string|null $operator + * @param TModel|null $value + * @return \Illuminate\Support\Collection, static> + */ + public function partition($key, $operator = null, $value = null) + { + return parent::partition(...func_get_args())->toBase(); + } + /** * Get an array with the values of a given key. * @@ -708,6 +750,24 @@ protected function duplicateComparator($strict) return fn ($a, $b) => $a->is($b); } + /** + * Enable relationship autoloading for all models in this collection. + * + * @return $this + */ + public function withRelationshipAutoloading() + { + $callback = fn ($tuples) => $this->loadMissingRelationshipChain($tuples); + + foreach ($this as $model) { + if (! $model->hasRelationAutoloadCallback()) { + $model->autoloadRelationsUsing($callback, $this); + } + } + + return $this; + } + /** * Get the type of the entities being queued. * @@ -741,8 +801,8 @@ public function getQueueableClass() protected function getQueueableModelClass($model) { return method_exists($model, 'getQueueableClassName') - ? $model->getQueueableClassName() - : get_class($model); + ? $model->getQueueableClassName() + : get_class($model); } /** @@ -757,8 +817,8 @@ public function getQueueableIds() } return $this->first() instanceof QueueableEntity - ? $this->map->getQueueableId()->all() - : $this->modelKeys(); + ? $this->map->getQueueableId()->all() + : $this->modelKeys(); } /** @@ -824,7 +884,7 @@ public function toQuery() $class = get_class($model); - if ($this->filter(fn ($model) => ! $model instanceof $class)->isNotEmpty()) { + if ($this->reject(fn ($model) => $model instanceof $class)->isNotEmpty()) { throw new LogicException('Unable to create query for collection with mixed types.'); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php index 67229b4a0332..6a02def76ea3 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -76,8 +76,8 @@ public function mergeFillable(array $fillable) public function getGuarded() { return $this->guarded === false - ? [] - : $this->guarded; + ? [] + : $this->guarded; } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index d02f8f617128..0d0fc454bf0b 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -117,6 +117,7 @@ trait HasAttributes 'int', 'integer', 'json', + 'json:unicode', 'object', 'real', 'string', @@ -482,8 +483,8 @@ public function getAttribute($key) } return $this->isRelation($key) || $this->relationLoaded($key) - ? $this->getRelationValue($key) - : $this->throwMissingAttributeExceptionIfApplicable($key); + ? $this->getRelationValue($key) + : $this->throwMissingAttributeExceptionIfApplicable($key); } /** @@ -550,6 +551,10 @@ public function getRelationValue($key) return; } + if ($this->attemptToAutoloadRelation($key)) { + return $this->relations[$key]; + } + if ($this->preventsLazyLoading) { $this->handleLazyLoadingViolation($key); } @@ -744,8 +749,8 @@ protected function mutateAttributeForArray($key, $value) $value = $this->mutateAttributeMarkedAttribute($key, $value); $value = $value instanceof DateTimeInterface - ? $this->serializeDate($value) - : $value; + ? $this->serializeDate($value) + : $value; } else { $value = $this->mutateAttribute($key, $value); } @@ -837,6 +842,7 @@ protected function castAttribute($key, $value) return $this->fromJson($value, true); case 'array': case 'json': + case 'json:unicode': return $this->fromJson($value); case 'collection': return new BaseCollection($this->fromJson($value)); @@ -1178,7 +1184,7 @@ public function fillJsonAttribute($key, $value) $value = $this->asJson($this->getArrayAttributeWithValue( $path, $key, $value - )); + ), $this->getJsonCastFlags($key)); $this->attributes[$key] = $this->isEncryptedCastable($key) ? $this->castAttributeAsEncryptedString($key, $value) @@ -1250,8 +1256,8 @@ protected function setEnumCastableAttribute($key, $value) protected function getEnumCaseFromValue($enumClass, $value) { return is_subclass_of($enumClass, BackedEnum::class) - ? $enumClass::from($value) - : constant($enumClass.'::'.$value); + ? $enumClass::from($value) + : constant($enumClass.'::'.$value); } /** @@ -1313,7 +1319,7 @@ protected function getArrayAttributeByKey($key) */ protected function castAttributeAsJson($key, $value) { - $value = $this->asJson($value); + $value = $this->asJson($value, $this->getJsonCastFlags($key)); if ($value === false) { throw JsonEncodingException::forAttribute( @@ -1324,15 +1330,33 @@ protected function castAttributeAsJson($key, $value) return $value; } + /** + * Get the JSON casting flags for the given attribute. + * + * @param string $key + * @return int + */ + protected function getJsonCastFlags($key) + { + $flags = 0; + + if ($this->hasCast($key, ['json:unicode'])) { + $flags |= JSON_UNESCAPED_UNICODE; + } + + return $flags; + } + /** * Encode the given value as JSON. * * @param mixed $value + * @param int $flags * @return string */ - protected function asJson($value) + protected function asJson($value, $flags = 0) { - return Json::encode($value); + return Json::encode($value, $flags); } /** @@ -1669,7 +1693,7 @@ protected function isDateCastableWithCustomFormat($key) */ protected function isJsonCastable($key) { - return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); + return $this->hasCast($key, ['array', 'json', 'json:unicode', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); } /** @@ -1987,6 +2011,27 @@ public function only($attributes) return $results; } + /** + * Get all attributes except the given ones. + * + * @param array|mixed $attributes + * @return array + */ + public function except($attributes) + { + $attributes = is_array($attributes) ? $attributes : func_get_args(); + + $results = []; + + foreach ($this->getAttributes() as $key => $value) { + if (! in_array($key, $attributes)) { + $results[$key] = $this->getAttribute($key); + } + } + + return $results; + } + /** * Sync the original attributes with the current. * diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php index 74561974eb1c..fa654de966d4 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php @@ -38,7 +38,7 @@ trait HasEvents */ public static function bootHasEvents() { - static::observe(static::resolveObserveAttributes()); + static::whenBooted(fn () => static::observe(static::resolveObserveAttributes())); } /** @@ -134,7 +134,7 @@ public function getObservableEvents() [ 'retrieved', 'creating', 'created', 'updating', 'updated', 'saving', 'saved', 'restoring', 'restored', 'replicating', - 'deleting', 'deleted', 'forceDeleting', 'forceDeleted', + 'trashed', 'deleting', 'deleted', 'forceDeleting', 'forceDeleted', ], $this->observables ); diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index b991fd08227c..8382cc183f4a 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -39,6 +39,20 @@ trait HasRelationships */ protected $touches = []; + /** + * The relationship autoloader callback. + * + * @var \Closure|null + */ + protected $relationAutoloadCallback = null; + + /** + * The relationship autoloader callback context. + * + * @var mixed + */ + protected $relationAutoloadContext = null; + /** * The many to many relationship methods. * @@ -92,6 +106,99 @@ public static function resolveRelationUsing($name, Closure $callback) ); } + /** + * Determine if a relationship autoloader callback has been defined. + * + * @return bool + */ + public function hasRelationAutoloadCallback() + { + return ! is_null($this->relationAutoloadCallback); + } + + /** + * Define an automatic relationship autoloader callback for this model and its relations. + * + * @param \Closure $callback + * @param mixed $context + * @return $this + */ + public function autoloadRelationsUsing(Closure $callback, $context = null) + { + // Prevent circular relation autoloading... + if ($context && $this->relationAutoloadContext === $context) { + return $this; + } + + $this->relationAutoloadCallback = $callback; + $this->relationAutoloadContext = $context; + + foreach ($this->relations as $key => $value) { + $this->propagateRelationAutoloadCallbackToRelation($key, $value); + } + + return $this; + } + + /** + * Attempt to autoload the given relationship using the autoload callback. + * + * @param string $key + * @return bool + */ + protected function attemptToAutoloadRelation($key) + { + if (! $this->hasRelationAutoloadCallback()) { + return false; + } + + $this->invokeRelationAutoloadCallbackFor($key, []); + + return $this->relationLoaded($key); + } + + /** + * Invoke the relationship autoloader callback for the given relationships. + * + * @param string $key + * @param array $tuples + * @return void + */ + protected function invokeRelationAutoloadCallbackFor($key, $tuples) + { + $tuples = array_merge([[$key, get_class($this)]], $tuples); + + call_user_func($this->relationAutoloadCallback, $tuples); + } + + /** + * Propagate the relationship autoloader callback to the given related models. + * + * @param string $key + * @param mixed $models + * @return void + */ + protected function propagateRelationAutoloadCallbackToRelation($key, $models) + { + if (! $this->hasRelationAutoloadCallback() || ! $models) { + return; + } + + if ($models instanceof Model) { + $models = [$models]; + } + + if (! is_iterable($models)) { + return; + } + + $callback = fn (array $tuples) => $this->invokeRelationAutoloadCallbackFor($key, $tuples); + + foreach ($models as $model) { + $model->autoloadRelationsUsing($callback, $this->relationAutoloadContext); + } + } + /** * Define a one-to-one relationship. * @@ -153,9 +260,13 @@ public function hasOneThrough($related, $through, $firstKey = null, $secondKey = $secondKey = $secondKey ?: $through->getForeignKey(); return $this->newHasOneThrough( - $this->newRelatedInstance($related)->newQuery(), $this, $through, - $firstKey, $secondKey, $localKey ?: $this->getKeyName(), - $secondLocalKey ?: $through->getKeyName() + $this->newRelatedInstance($related)->newQuery(), + $this, + $through, + $firstKey, + $secondKey, + $localKey ?: $this->getKeyName(), + $secondLocalKey ?: $through->getKeyName(), ); } @@ -302,8 +413,8 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null // the relationship. In this case we'll just pass in a dummy query where we // need to remove any eager loads that may already be defined on a model. return is_null($class = $this->getAttributeFromArray($type)) || $class === '' - ? $this->morphEagerTo($name, $type, $id, $ownerKey) - : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey); + ? $this->morphEagerTo($name, $type, $id, $ownerKey) + : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey); } /** @@ -560,11 +671,17 @@ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $loca * @param string|null $parentKey * @param string|null $relatedKey * @param string|null $relation - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, - $parentKey = null, $relatedKey = null, $relation = null) - { + public function belongsToMany( + $related, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null, + $relation = null, + ) { // If no relationship name was passed, we will pull backtraces to get the // name of the calling function. We will use that function name as the // title of this relation since that is a great convention to apply. @@ -589,9 +706,14 @@ public function belongsToMany($related, $table = null, $foreignPivotKey = null, } return $this->newBelongsToMany( - $instance->newQuery(), $this, $table, $foreignPivotKey, - $relatedPivotKey, $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), $relation + $instance->newQuery(), + $this, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + $relation, ); } @@ -609,11 +731,18 @@ public function belongsToMany($related, $table = null, $foreignPivotKey = null, * @param string $parentKey * @param string $relatedKey * @param string|null $relationName - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, - $parentKey, $relatedKey, $relationName = null) - { + protected function newBelongsToMany( + Builder $query, + Model $parent, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $relationName = null, + ) { return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName); } @@ -633,10 +762,17 @@ protected function newBelongsToMany(Builder $query, Model $parent, $table, $fore * @param bool $inverse * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ - public function morphToMany($related, $name, $table = null, $foreignPivotKey = null, - $relatedPivotKey = null, $parentKey = null, - $relatedKey = null, $relation = null, $inverse = false) - { + public function morphToMany( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null, + $relation = null, + $inverse = false, + ) { $relation = $relation ?: $this->guessBelongsToManyRelation(); // First, we will need to determine the foreign key and "other key" for the @@ -660,9 +796,16 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n } return $this->newMorphToMany( - $instance->newQuery(), $this, $name, $table, - $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), $relation, $inverse + $instance->newQuery(), + $this, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + $relation, + $inverse, ); } @@ -684,12 +827,30 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n * @param bool $inverse * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ - protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, - $relatedPivotKey, $parentKey, $relatedKey, - $relationName = null, $inverse = false) - { - return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, - $relationName, $inverse); + protected function newMorphToMany( + Builder $query, + Model $parent, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $relationName = null, + $inverse = false, + ) { + return new MorphToMany( + $query, + $parent, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $relationName, + $inverse, + ); } /** @@ -707,9 +868,16 @@ protected function newMorphToMany(Builder $query, Model $parent, $name, $table, * @param string|null $relation * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ - public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null, - $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) - { + public function morphedByMany( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null, + $relation = null, + ) { $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); // For the inverse of the polymorphic many-to-many relations, we will change @@ -718,8 +886,15 @@ public function morphedByMany($related, $name, $table = null, $foreignPivotKey = $relatedPivotKey = $relatedPivotKey ?: $name.'_id'; return $this->morphToMany( - $related, $name, $table, $foreignPivotKey, - $relatedPivotKey, $parentKey, $relatedKey, $relation, true + $related, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $relation, + true, ); } @@ -753,8 +928,9 @@ public function joiningTable($related, $instance = null) // sorted alphabetically and concatenated with an underscore, so we can // just sort the models and join them together to get the table name. $segments = [ - $instance ? $instance->joiningTableSegment() - : Str::snake(class_basename($related)), + $instance + ? $instance->joiningTableSegment() + : Str::snake(class_basename($related)), $this->joiningTableSegment(), ]; @@ -919,6 +1095,8 @@ public function setRelation($relation, $value) { $this->relations[$relation] = $value; + $this->propagateRelationAutoloadCallbackToRelation($relation, $value); + return $this; } @@ -948,6 +1126,18 @@ public function setRelations(array $relations) return $this; } + /** + * Enable relationship autoloading for this model. + * + * @return $this + */ + public function withRelationshipAutoloading() + { + $this->newCollection([$this])->withRelationshipAutoloading(); + + return $this; + } + /** * Duplicate the instance and unset all the loaded relations. * diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php index 8d6c35ad89af..89d40f829aa1 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php @@ -15,7 +15,7 @@ trait HasUuids */ public function newUniqueId() { - return (string) Str::orderedUuid(); + return (string) Str::uuid7(); } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasVersion7Uuids.php b/src/Illuminate/Database/Eloquent/Concerns/HasVersion4Uuids.php similarity index 62% rename from src/Illuminate/Database/Eloquent/Concerns/HasVersion7Uuids.php rename to src/Illuminate/Database/Eloquent/Concerns/HasVersion4Uuids.php index 455bf74aa576..eac53c67ba8d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasVersion7Uuids.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasVersion4Uuids.php @@ -4,17 +4,17 @@ use Illuminate\Support\Str; -trait HasVersion7Uuids +trait HasVersion4Uuids { use HasUuids; /** - * Generate a new UUID (version 7) for the model. + * Generate a new UUID (version 4) for the model. * * @return string */ public function newUniqueId() { - return (string) Str::uuid7(); + return (string) Str::orderedUuid(); } } diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index c6162d7656a8..f9f21536d1fc 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\RelationNotFoundException; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Builder as QueryBuilder; @@ -53,8 +54,8 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ? // the subquery to only run a "where exists" clause instead of this full "count" // clause. This will make these queries run much faster compared with a count. $method = $this->canUseExistsForExistenceCheck($operator, $count) - ? 'getRelationExistenceQuery' - : 'getRelationExistenceCountQuery'; + ? 'getRelationExistenceQuery' + : 'getRelationExistenceCountQuery'; $hasQuery = $relation->{$method}( $relation->getRelated()->newQueryWithoutRelationships(), $this @@ -281,7 +282,7 @@ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boole }); } }, null, null, $boolean) - ->when($checkMorphNull, fn (self $query) => $query->orWhereMorphedTo($relation, null)); + ->when($checkMorphNull, fn (self $query) => $query->orWhereMorphedTo($relation, null)); } /** @@ -674,7 +675,7 @@ public function whereNotMorphedTo($relation, $model, $boolean = 'and') $models->groupBy(fn ($model) => $model->getMorphClass())->each(function ($models) use ($query, $relation) { $query->orWhere(function ($query) use ($relation, $models) { $query->where($relation->qualifyColumn($relation->getMorphType()), '<=>', $models->first()->getMorphClass()) - ->whereNotIn($relation->qualifyColumn($relation->getForeignKeyName()), $models->map->getKey()); + ->whereIn($relation->qualifyColumn($relation->getForeignKeyName()), $models->map->getKey()); }); }); }, null, null, $boolean); @@ -765,6 +766,63 @@ public function orWhereBelongsTo($related, $relationshipName = null) return $this->whereBelongsTo($related, $relationshipName, 'or'); } + /** + * Add a "belongs to many" relationship where clause to the query. + * + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection $related + * @param string|null $relationshipName + * @param string $boolean + * @return $this + * + * @throws \Illuminate\Database\Eloquent\RelationNotFoundException + */ + public function whereAttachedTo($related, $relationshipName = null, $boolean = 'and') + { + $relatedCollection = $related instanceof EloquentCollection ? $related : $related->newCollection([$related]); + + $related = $relatedCollection->first(); + + if ($relatedCollection->isEmpty()) { + throw new InvalidArgumentException('Collection given to whereAttachedTo method may not be empty.'); + } + + if ($relationshipName === null) { + $relationshipName = Str::plural(Str::camel(class_basename($related))); + } + + try { + $relationship = $this->model->{$relationshipName}(); + } catch (BadMethodCallException) { + throw RelationNotFoundException::make($this->model, $relationshipName); + } + + if (! $relationship instanceof BelongsToMany) { + throw RelationNotFoundException::make($this->model, $relationshipName, BelongsToMany::class); + } + + $this->has( + $relationshipName, + boolean: $boolean, + callback: fn (Builder $query) => $query->whereKey($relatedCollection), + ); + + return $this; + } + + /** + * Add a "belongs to many" relationship with an "or where" clause to the query. + * + * @param \Illuminate\Database\Eloquent\Model $related + * @param string|null $relationshipName + * @return $this + * + * @throws \RuntimeException + */ + public function orWhereAttachedTo($related, $relationshipName = null) + { + return $this->whereAttachedTo($related, $relationshipName, 'or'); + } + /** * Add subselect queries to include an aggregate value for a relationship. * @@ -841,7 +899,11 @@ public function withAggregate($relations, $column, $function = null) // the query builder. Then, we will return the builder instance back to the developer // for further constraint chaining that needs to take place on the query as needed. $alias ??= Str::snake( - preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getQuery()->getGrammar()->getValue($column)}") + preg_replace( + '/[^[:alnum:][:space:]_]/u', + '', + sprintf('%s %s %s', $name, $function, strtolower($this->getQuery()->getGrammar()->getValue($column))) + ) ); if ($function === 'exists') { @@ -963,8 +1025,8 @@ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $hasQuery->mergeConstraintsFrom($relation->getQuery()); return $this->canUseExistsForExistenceCheck($operator, $count) - ? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1) - : $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean); + ? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1) + : $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean); } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php new file mode 100644 index 000000000000..578de7d0a86f --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php @@ -0,0 +1,74 @@ +|null $resourceClass + * @return \Illuminate\Http\Resources\Json\JsonResource + * + * @throws \Throwable + */ + public function toResource(?string $resourceClass = null): JsonResource + { + if ($resourceClass === null) { + return $this->guessResource(); + } + + return $resourceClass::make($this); + } + + /** + * Guess the resource class for the model. + * + * @return \Illuminate\Http\Resources\Json\JsonResource + * + * @throws \Throwable + */ + protected function guessResource(): JsonResource + { + foreach (static::guessResourceName() as $resourceClass) { + if (is_string($resourceClass) && class_exists($resourceClass)) { + return $resourceClass::make($this); + } + } + + throw new LogicException(sprintf('Failed to find resource class for model [%s].', get_class($this))); + } + + /** + * Guess the resource class name for the model. + * + * @return array> + */ + public static function guessResourceName(): array + { + $modelClass = static::class; + + if (! Str::contains($modelClass, '\\Models\\')) { + return []; + } + + $relativeNamespace = Str::after($modelClass, '\\Models\\'); + + $relativeNamespace = Str::contains($relativeNamespace, '\\') + ? Str::before($relativeNamespace, '\\'.class_basename($modelClass)) + : ''; + + $potentialResource = sprintf( + '%s\\Http\\Resources\\%s%s', + Str::before($modelClass, '\\Models'), + strlen($relativeNamespace) > 0 ? $relativeNamespace.'\\' : '', + class_basename($modelClass) + ); + + return [$potentialResource.'Resource', $potentialResource]; + } +} diff --git a/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php b/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php index 8e40261021ef..5498dc856516 100644 --- a/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php +++ b/src/Illuminate/Database/Eloquent/Factories/BelongsToManyRelationship.php @@ -34,7 +34,6 @@ class BelongsToManyRelationship * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $factory * @param callable|array $pivot * @param string $relationship - * @return void */ public function __construct($factory, $pivot, $relationship) { @@ -51,7 +50,13 @@ public function __construct($factory, $pivot, $relationship) */ public function createFor(Model $model) { - Collection::wrap($this->factory instanceof Factory ? $this->factory->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { + $factoryInstance = $this->factory instanceof Factory; + + if ($factoryInstance) { + $relationship = $model->{$this->relationship}(); + } + + Collection::wrap($factoryInstance ? $this->factory->prependState($relationship->getQuery()->pendingAttributes)->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { $model->{$this->relationship}()->attach( $attachable, is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot diff --git a/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php b/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php index b2fb1b251a31..5979183d92f6 100644 --- a/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php +++ b/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php @@ -33,7 +33,6 @@ class BelongsToRelationship * * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory * @param string $relationship - * @return void */ public function __construct($factory, $relationship) { diff --git a/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php b/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php index 3270b305cde9..594120b38514 100644 --- a/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php +++ b/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php @@ -10,7 +10,6 @@ class CrossJoinSequence extends Sequence * Create a new cross join sequence instance. * * @param array ...$sequences - * @return void */ public function __construct(...$sequences) { diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index 7c7fc743a866..a52d840f421e 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -146,7 +146,6 @@ abstract class Factory * @param string|null $connection * @param \Illuminate\Support\Collection|null $recycle * @param bool $expandRelationships - * @return void */ public function __construct( $count = null, @@ -531,6 +530,21 @@ public function state($state) ]); } + /** + * Prepend a new state transformation to the model definition. + * + * @param (callable(array, TModel|null): array)|array $state + * @return static + */ + public function prependState($state) + { + return $this->newInstance([ + 'states' => $this->states->prepend( + is_callable($state) ? $state : fn () => $state, + ), + ]); + } + /** * Set a single model attribute. * @@ -827,8 +841,8 @@ public function modelName() $appNamespace = static::appNamespace(); return class_exists($appNamespace.'Models\\'.$namespacedFactoryBasename) - ? $appNamespace.'Models\\'.$namespacedFactoryBasename - : $appNamespace.$factoryBasename; + ? $appNamespace.'Models\\'.$namespacedFactoryBasename + : $appNamespace.$factoryBasename; }; return $resolver($this); diff --git a/src/Illuminate/Database/Eloquent/Factories/HasFactory.php b/src/Illuminate/Database/Eloquent/Factories/HasFactory.php index ca37657def04..d2747cc93c30 100644 --- a/src/Illuminate/Database/Eloquent/Factories/HasFactory.php +++ b/src/Illuminate/Database/Eloquent/Factories/HasFactory.php @@ -52,7 +52,7 @@ protected static function getUseFactoryAttribute() if ($attributes !== []) { $useFactory = $attributes[0]->newInstance(); - $factory = new $useFactory->factoryClass; + $factory = $useFactory->factoryClass::new(); $factory->guessModelNamesUsing(fn () => static::class); diff --git a/src/Illuminate/Database/Eloquent/Factories/Relationship.php b/src/Illuminate/Database/Eloquent/Factories/Relationship.php index 3eb62da38a6e..e23bc99d78b0 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Relationship.php +++ b/src/Illuminate/Database/Eloquent/Factories/Relationship.php @@ -28,7 +28,6 @@ class Relationship * * @param \Illuminate\Database\Eloquent\Factories\Factory $factory * @param string $relationship - * @return void */ public function __construct(Factory $factory, $relationship) { @@ -50,13 +49,15 @@ public function createFor(Model $parent) $this->factory->state([ $relationship->getMorphType() => $relationship->getMorphClass(), $relationship->getForeignKeyName() => $relationship->getParentKey(), - ])->create([], $parent); + ])->prependState($relationship->getQuery()->pendingAttributes)->create([], $parent); } elseif ($relationship instanceof HasOneOrMany) { $this->factory->state([ $relationship->getForeignKeyName() => $relationship->getParentKey(), - ])->create([], $parent); + ])->prependState($relationship->getQuery()->pendingAttributes)->create([], $parent); } elseif ($relationship instanceof BelongsToMany) { - $relationship->attach($this->factory->create([], $parent)); + $relationship->attach( + $this->factory->prependState($relationship->getQuery()->pendingAttributes)->create([], $parent) + ); } } diff --git a/src/Illuminate/Database/Eloquent/Factories/Sequence.php b/src/Illuminate/Database/Eloquent/Factories/Sequence.php index e523fb3eebd0..11971eced7da 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Sequence.php +++ b/src/Illuminate/Database/Eloquent/Factories/Sequence.php @@ -31,7 +31,6 @@ class Sequence implements Countable * Create a new sequence instance. * * @param mixed ...$sequence - * @return void */ public function __construct(...$sequence) { diff --git a/src/Illuminate/Database/Eloquent/HasCollection.php b/src/Illuminate/Database/Eloquent/HasCollection.php index a1b462784dd6..d430f0099b81 100644 --- a/src/Illuminate/Database/Eloquent/HasCollection.php +++ b/src/Illuminate/Database/Eloquent/HasCollection.php @@ -27,7 +27,13 @@ public function newCollection(array $models = []) { static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? static::$collectionClass); - return new static::$resolvedCollectionClasses[static::class]($models); + $collection = new static::$resolvedCollectionClasses[static::class]($models); + + if (Model::isAutomaticallyEagerLoadingRelationships()) { + $collection->withRelationshipAutoloading(); + } + + return $collection; } /** diff --git a/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php b/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php index 1c49ba28b7c4..dfcbbd677476 100644 --- a/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php +++ b/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php @@ -26,7 +26,6 @@ class HigherOrderBuilderProxy * * @param \Illuminate\Database\Eloquent\Builder<*> $builder * @param string $method - * @return void */ public function __construct(Builder $builder, $method) { diff --git a/src/Illuminate/Database/Eloquent/InvalidCastException.php b/src/Illuminate/Database/Eloquent/InvalidCastException.php index e90e9a71bd2b..f37672c0b9fa 100644 --- a/src/Illuminate/Database/Eloquent/InvalidCastException.php +++ b/src/Illuminate/Database/Eloquent/InvalidCastException.php @@ -33,7 +33,6 @@ class InvalidCastException extends RuntimeException * @param object $model * @param string $column * @param string $castType - * @return void */ public function __construct($model, $column, $castType) { diff --git a/src/Illuminate/Database/Eloquent/MassPrunable.php b/src/Illuminate/Database/Eloquent/MassPrunable.php index e2321343e62a..81e2701263ca 100644 --- a/src/Illuminate/Database/Eloquent/MassPrunable.php +++ b/src/Illuminate/Database/Eloquent/MassPrunable.php @@ -23,10 +23,12 @@ public function pruneAll(int $chunkSize = 1000) $total = 0; + $softDeletable = in_array(SoftDeletes::class, class_uses_recursive(get_class($this))); + do { - $total += $count = in_array(SoftDeletes::class, class_uses_recursive(get_class($this))) - ? $query->forceDelete() - : $query->delete(); + $total += $count = $softDeletable + ? $query->forceDelete() + : $query->delete(); if ($count > 0) { event(new ModelsPruned(static::class, $total)); diff --git a/src/Illuminate/Database/Eloquent/MissingAttributeException.php b/src/Illuminate/Database/Eloquent/MissingAttributeException.php index 87935c141dce..ef05109927ea 100755 --- a/src/Illuminate/Database/Eloquent/MissingAttributeException.php +++ b/src/Illuminate/Database/Eloquent/MissingAttributeException.php @@ -11,7 +11,6 @@ class MissingAttributeException extends OutOfBoundsException * * @param \Illuminate\Database\Eloquent\Model $model * @param string $key - * @return void */ public function __construct($model, $key) { diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 44878f7bf880..72d7e3315e36 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Eloquent; use ArrayAccess; +use Closure; use Illuminate\Contracts\Broadcasting\HasBroadcastChannel; use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; @@ -11,6 +12,7 @@ use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; use Illuminate\Contracts\Support\Jsonable; use Illuminate\Database\ConnectionResolverInterface as Resolver; +use Illuminate\Database\Eloquent\Attributes\Scope as LocalScope; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot; @@ -24,6 +26,7 @@ use JsonException; use JsonSerializable; use LogicException; +use ReflectionMethod; use Stringable; abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, Stringable, UrlRoutable @@ -37,6 +40,7 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt Concerns\HidesAttributes, Concerns\GuardsAttributes, Concerns\PreventsCircularRecursion, + Concerns\TransformsToResource, ForwardsCalls; /** @use HasCollection<\Illuminate\Database\Eloquent\Collection> */ use HasCollection; @@ -146,6 +150,13 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt */ protected static $booted = []; + /** + * The callbacks that should be executed after the model has booted. + * + * @var array + */ + protected static $bootedCallbacks = []; + /** * The array of trait initializers that will be called on each new instance. * @@ -174,6 +185,13 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt */ protected static $modelsShouldPreventLazyLoading = false; + /** + * Indicates whether relations should be automatically loaded on all models when they are accessed. + * + * @var bool + */ + protected static $modelsShouldAutomaticallyEagerLoadRelationships = false; + /** * The callback that is responsible for handling lazy loading violations. * @@ -248,7 +266,6 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt * Create a new Eloquent model instance. * * @param array $attributes - * @return void */ public function __construct(array $attributes = []) { @@ -277,6 +294,12 @@ protected function bootIfNotBooted() static::boot(); static::booted(); + static::$bootedCallbacks[static::class] ??= []; + + foreach (static::$bootedCallbacks[static::class] as $callback) { + $callback(); + } + $this->fireModelEvent('booted', false); } } @@ -355,6 +378,19 @@ protected static function booted() // } + /** + * Register a closure to be executed after the model has booted. + * + * @param \Closure $callback + * @return void + */ + protected static function whenBooted(Closure $callback) + { + static::$bootedCallbacks[static::class] ??= []; + + static::$bootedCallbacks[static::class][] = $callback; + } + /** * Clear the list of booted models so they will be re-booted. * @@ -363,6 +399,7 @@ protected static function booted() public static function clearBootedModels() { static::$booted = []; + static::$bootedCallbacks = []; static::$globalScopes = []; } @@ -443,6 +480,17 @@ public static function preventLazyLoading($value = true) static::$modelsShouldPreventLazyLoading = $value; } + /** + * Determine if model relationships should be automatically eager loaded when accessed. + * + * @param bool $value + * @return void + */ + public static function automaticallyEagerLoadRelationships($value = true) + { + static::$modelsShouldAutomaticallyEagerLoadRelationships = $value; + } + /** * Register a callback that is responsible for handling lazy loading violations. * @@ -1095,7 +1143,8 @@ public function push() // us to recurse into all of these nested relations for the model instance. foreach ($this->relations as $models) { $models = $models instanceof Collection - ? $models->all() : [$models]; + ? $models->all() + : [$models]; foreach (array_filter($models) as $model) { if (! $model->push()) { @@ -1639,7 +1688,8 @@ public function newPivot(self $parent, array $attributes, $table, $exists, $usin */ public function hasNamedScope($scope) { - return method_exists($this, 'scope'.ucfirst($scope)); + return method_exists($this, 'scope'.ucfirst($scope)) || + static::isScopeMethodWithAttribute($scope); } /** @@ -1651,9 +1701,26 @@ public function hasNamedScope($scope) */ public function callNamedScope($scope, array $parameters = []) { + if ($this->isScopeMethodWithAttribute($scope)) { + return $this->{$scope}(...$parameters); + } + return $this->{'scope'.ucfirst($scope)}(...$parameters); } + /** + * Determine if the given method has a scope attribute. + * + * @param string $method + * @return bool + */ + protected static function isScopeMethodWithAttribute(string $method) + { + return method_exists(static::class, $method) && + (new ReflectionMethod(static::class, $method)) + ->getAttributes(LocalScope::class) !== []; + } + /** * Convert the model instance to an array. * @@ -2209,6 +2276,16 @@ public static function preventsLazyLoading() return static::$modelsShouldPreventLazyLoading; } + /** + * Determine if relationships are being automatically eager loaded when accessed. + * + * @return bool + */ + public static function isAutomaticallyEagerLoadingRelationships() + { + return static::$modelsShouldAutomaticallyEagerLoadRelationships; + } + /** * Determine if discarding guarded attribute fills is disabled. * @@ -2381,6 +2458,10 @@ public function __call($method, $parameters) */ public static function __callStatic($method, $parameters) { + if (static::isScopeMethodWithAttribute($method)) { + return static::query()->$method(...$parameters); + } + return (new static)->$method(...$parameters); } @@ -2420,6 +2501,8 @@ public function __sleep() $this->classCastCache = []; $this->attributeCastCache = []; + $this->relationAutoloadCallback = null; + $this->relationAutoloadContext = null; return array_keys(get_object_vars($this)); } @@ -2434,5 +2517,9 @@ public function __wakeup() $this->bootIfNotBooted(); $this->initializeTraits(); + + if (static::isAutomaticallyEagerLoadingRelationships()) { + $this->withRelationshipAutoloading(); + } } } diff --git a/src/Illuminate/Database/Eloquent/ModelInspector.php b/src/Illuminate/Database/Eloquent/ModelInspector.php index 96afe44c187a..b0db2130c0a3 100644 --- a/src/Illuminate/Database/Eloquent/ModelInspector.php +++ b/src/Illuminate/Database/Eloquent/ModelInspector.php @@ -47,7 +47,6 @@ class ModelInspector * Create a new model inspector instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct(Application $app) { diff --git a/src/Illuminate/Database/Eloquent/Prunable.php b/src/Illuminate/Database/Eloquent/Prunable.php index 737769107191..b1314af362e5 100644 --- a/src/Illuminate/Database/Eloquent/Prunable.php +++ b/src/Illuminate/Database/Eloquent/Prunable.php @@ -2,8 +2,10 @@ namespace Illuminate\Database\Eloquent; +use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Database\Events\ModelsPruned; use LogicException; +use Throwable; trait Prunable { @@ -21,9 +23,21 @@ public function pruneAll(int $chunkSize = 1000) ->when(in_array(SoftDeletes::class, class_uses_recursive(static::class)), function ($query) { $query->withTrashed(); })->chunkById($chunkSize, function ($models) use (&$total) { - $models->each->prune(); + $models->each(function ($model) use (&$total) { + try { + $model->prune(); - $total += $models->count(); + $total++; + } catch (Throwable $e) { + $handler = app(ExceptionHandler::class); + + if ($handler) { + $handler->report($e); + } else { + throw $e; + } + } + }); event(new ModelsPruned(static::class, $total)); }); @@ -51,8 +65,8 @@ public function prune() $this->pruning(); return in_array(SoftDeletes::class, class_uses_recursive(static::class)) - ? $this->forceDelete() - : $this->delete(); + ? $this->forceDelete() + : $this->delete(); } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php index d38d512af924..e145040a92c9 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php @@ -59,7 +59,6 @@ class BelongsTo extends Relation * @param string $foreignKey * @param string $ownerKey * @param string $relationName - * @return void */ public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName) { diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 7019cf49c069..e125a760410b 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -20,8 +20,12 @@ /** * @template TRelatedModel of \Illuminate\Database\Eloquent\Model * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot = \Illuminate\Database\Eloquent\Relations\Pivot + * @template TAccessor of string = 'pivot' * - * @extends \Illuminate\Database\Eloquent\Relations\Relation> + * @extends \Illuminate\Database\Eloquent\Relations\Relation> + * + * @todo use TAccessor when PHPStan bug is fixed: https://github.com/phpstan/phpstan/issues/12756 */ class BelongsToMany extends Relation { @@ -128,14 +132,14 @@ class BelongsToMany extends Relation /** * The class name of the custom pivot model to use for the relationship. * - * @var string + * @var class-string */ protected $using; /** * The name of the accessor to use for the "pivot" relationship. * - * @var string + * @var TAccessor */ protected $accessor = 'pivot'; @@ -150,7 +154,6 @@ class BelongsToMany extends Relation * @param string $parentKey * @param string $relatedKey * @param string|null $relationName - * @return void */ public function __construct( Builder $query, @@ -316,7 +319,7 @@ protected function buildDictionary(EloquentCollection $results) /** * Get the class being used for pivot models. * - * @return string + * @return class-string */ public function getPivotClass() { @@ -326,8 +329,12 @@ public function getPivotClass() /** * Specify the custom pivot model to use for the relationship. * - * @param string $class + * @template TNewPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot + * + * @param class-string $class * @return $this + * + * @phpstan-this-out static */ public function using($class) { @@ -339,8 +346,12 @@ public function using($class) /** * Specify the custom pivot accessor to use for the relationship. * - * @param string $accessor + * @template TNewAccessor of string + * + * @param TNewAccessor $accessor * @return $this + * + * @phpstan-this-out static */ public function as($accessor) { @@ -579,7 +590,11 @@ public function orderByPivot($column, $direction = 'asc') * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : TRelatedModel&object{pivot: TPivotModel} + * ) */ public function findOrNew($id, $columns = ['*']) { @@ -595,7 +610,7 @@ public function findOrNew($id, $columns = ['*']) * * @param array $attributes * @param array $values - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function firstOrNew(array $attributes = [], array $values = []) { @@ -613,7 +628,7 @@ public function firstOrNew(array $attributes = [], array $values = []) * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -639,7 +654,7 @@ public function firstOrCreate(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { @@ -665,7 +680,7 @@ public function createOrFirst(array $attributes = [], array $values = [], array * @param array $values * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) { @@ -683,7 +698,11 @@ public function updateOrCreate(array $attributes, array $values = [], array $joi * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel|null) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : (TRelatedModel&object{pivot: TPivotModel})|null + * ) */ public function find($id, $columns = ['*']) { @@ -701,7 +720,7 @@ public function find($id, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException @@ -718,7 +737,7 @@ public function findSole($id, $columns = ['*']) * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { @@ -738,7 +757,11 @@ public function findMany($ids, $columns = ['*']) * * @param mixed $id * @param array $columns - * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TRelatedModel) + * @return ( + * $id is (\Illuminate\Contracts\Support\Arrayable|array) + * ? \Illuminate\Database\Eloquent\Collection + * : TRelatedModel&object{pivot: TPivotModel} + * ) * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -769,8 +792,8 @@ public function findOrFail($id, $columns = ['*']) * @param (\Closure(): TValue)|null $callback * @return ( * $id is (\Illuminate\Contracts\Support\Arrayable|array) - * ? \Illuminate\Database\Eloquent\Collection|TValue - * : TRelatedModel|TValue + * ? \Illuminate\Database\Eloquent\Collection|TValue + * : (TRelatedModel&object{pivot: TPivotModel})|TValue * ) */ public function findOr($id, $columns = ['*'], ?Closure $callback = null) @@ -803,7 +826,7 @@ public function findOr($id, $columns = ['*'], ?Closure $callback = null) * @param mixed $operator * @param mixed $value * @param string $boolean - * @return TRelatedModel|null + * @return (TRelatedModel&object{pivot: TPivotModel})|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -814,7 +837,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = * Execute the query and get the first result. * * @param array $columns - * @return TRelatedModel|null + * @return (TRelatedModel&object{pivot: TPivotModel})|null */ public function first($columns = ['*']) { @@ -827,7 +850,7 @@ public function first($columns = ['*']) * Execute the query and get the first result or throw an exception. * * @param array $columns - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ @@ -847,7 +870,7 @@ public function firstOrFail($columns = ['*']) * * @param (\Closure(): TValue)|list $columns * @param (\Closure(): TValue)|null $callback - * @return TRelatedModel|TValue + * @return (TRelatedModel&object{pivot: TPivotModel})|TValue */ public function firstOr($columns = ['*'], ?Closure $callback = null) { @@ -868,8 +891,8 @@ public function firstOr($columns = ['*'], ?Closure $callback = null) public function getResults() { return ! is_null($this->parent->{$this->parentKey}) - ? $this->get() - : $this->related->newCollection(); + ? $this->get() + : $this->related->newCollection(); } /** @inheritDoc */ @@ -924,11 +947,14 @@ protected function shouldSelect(array $columns = ['*']) */ protected function aliasedPivotColumns() { - $defaults = [$this->foreignPivotKey, $this->relatedPivotKey]; - - return (new BaseCollection(array_merge($defaults, $this->pivotColumns)))->map(function ($column) { - return $this->qualifyPivotColumn($column).' as pivot_'.$column; - })->unique()->all(); + return (new BaseCollection([ + $this->foreignPivotKey, + $this->relatedPivotKey, + ...$this->pivotColumns, + ])) + ->map(fn ($column) => $this->qualifyPivotColumn($column).' as pivot_'.$column) + ->unique() + ->all(); } /** @@ -938,7 +964,7 @@ protected function aliasedPivotColumns() * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { @@ -956,7 +982,7 @@ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', * @param array $columns * @param string $pageName * @param int|null $page - * @return \Illuminate\Contracts\Pagination\Paginator + * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { @@ -974,7 +1000,7 @@ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'p * @param array $columns * @param string $cursorName * @param string|null $cursor - * @return \Illuminate\Contracts\Pagination\CursorPaginator + * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { @@ -1096,7 +1122,7 @@ public function each(callable $callback, $count = 1000) * Query lazily, by chunks of the given size. * * @param int $chunkSize - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazy($chunkSize = 1000) { @@ -1113,7 +1139,7 @@ public function lazy($chunkSize = 1000) * @param int $chunkSize * @param string|null $column * @param string|null $alias - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { @@ -1136,7 +1162,7 @@ public function lazyById($chunkSize = 1000, $column = null, $alias = null) * @param int $chunkSize * @param string|null $column * @param string|null $alias - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) { @@ -1156,7 +1182,7 @@ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) /** * Get a lazy collection for the given query. * - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function cursor() { @@ -1296,7 +1322,7 @@ public function allRelatedIds() * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function save(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1313,7 +1339,7 @@ public function save(Model $model, array $pivotAttributes = [], $touch = true) * @param TRelatedModel $model * @param array $pivotAttributes * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = true) { @@ -1364,7 +1390,7 @@ public function saveManyQuietly($models, array $pivotAttributes = []) * @param array $attributes * @param array $joining * @param bool $touch - * @return TRelatedModel + * @return TRelatedModel&object{pivot: TPivotModel} */ public function create(array $attributes = [], array $joining = [], $touch = true) { @@ -1387,7 +1413,7 @@ public function create(array $attributes = [], array $joining = [], $touch = tru * * @param iterable $records * @param array $joinings - * @return array + * @return array */ public function createMany(iterable $records, array $joinings = []) { @@ -1621,7 +1647,7 @@ public function getRelationName() /** * Get the name of the pivot accessor for this relationship. * - * @return string + * @return TAccessor */ public function getPivotAccessor() { @@ -1651,7 +1677,7 @@ public function qualifyPivotColumn($column) } return str_contains($column, '.') - ? $column - : $this->table.'.'.$column; + ? $column + : $this->table.'.'.$column; } } diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php index f09de9451eff..8de013a1a38a 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -207,10 +207,7 @@ protected function attachNew(array $records, array $current, $touch = true) */ public function updateExistingPivot($id, array $attributes, $touch = true) { - if ($this->using && - empty($this->pivotWheres) && - empty($this->pivotWhereIns) && - empty($this->pivotWhereNulls)) { + if ($this->using) { return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch); } @@ -218,7 +215,7 @@ public function updateExistingPivot($id, array $attributes, $touch = true) $attributes = $this->addTimestampsToAttachment($attributes, true); } - $updated = $this->newPivotStatementForId($this->parseId($id))->update( + $updated = $this->newPivotStatementForId($id)->update( $this->castAttributes($attributes) ); @@ -239,10 +236,7 @@ public function updateExistingPivot($id, array $attributes, $touch = true) */ protected function updateExistingPivotUsingCustomClass($id, array $attributes, $touch) { - $pivot = $this->getCurrentlyAttachedPivots() - ->where($this->foreignPivotKey, $this->parent->{$this->parentKey}) - ->where($this->relatedPivotKey, $this->parseId($id)) - ->first(); + $pivot = $this->getCurrentlyAttachedPivotsForIds($id)->first(); $updated = $pivot ? $pivot->fill($attributes)->isDirty() : false; @@ -260,21 +254,21 @@ protected function updateExistingPivotUsingCustomClass($id, array $attributes, $ /** * Attach a model to the parent. * - * @param mixed $id + * @param mixed $ids * @param array $attributes * @param bool $touch * @return void */ - public function attach($id, array $attributes = [], $touch = true) + public function attach($ids, array $attributes = [], $touch = true) { if ($this->using) { - $this->attachUsingCustomClass($id, $attributes); + $this->attachUsingCustomClass($ids, $attributes); } else { // Here we will insert the attachment records into the pivot table. Once we have // inserted the records, we will touch the relationships if necessary and the // function will return. We can parse the IDs before inserting the records. $this->newPivotStatement()->insert($this->formatAttachRecords( - $this->parseIds($id), $attributes + $this->parseIds($ids), $attributes )); } @@ -286,14 +280,14 @@ public function attach($id, array $attributes = [], $touch = true) /** * Attach a model to the parent using a custom class. * - * @param mixed $id + * @param mixed $ids * @param array $attributes * @return void */ - protected function attachUsingCustomClass($id, array $attributes) + protected function attachUsingCustomClass($ids, array $attributes) { $records = $this->formatAttachRecords( - $this->parseIds($id), $attributes + $this->parseIds($ids), $attributes ); foreach ($records as $record) { @@ -356,8 +350,8 @@ protected function formatAttachRecord($key, $value, $attributes, $hasTimestamps) protected function extractAttachIdAndAttributes($key, $value, array $attributes) { return is_array($value) - ? [$key, array_merge($value, $attributes)] - : [$value, $attributes]; + ? [$key, array_merge($value, $attributes)] + : [$value, $attributes]; } /** @@ -435,11 +429,7 @@ public function hasPivotColumn($column) */ public function detach($ids = null, $touch = true) { - if ($this->using && - ! empty($ids) && - empty($this->pivotWheres) && - empty($this->pivotWhereIns) && - empty($this->pivotWhereNulls)) { + if ($this->using) { $results = $this->detachUsingCustomClass($ids); } else { $query = $this->newPivotQuery(); @@ -480,11 +470,10 @@ protected function detachUsingCustomClass($ids) { $results = 0; - foreach ($this->parseIds($ids) as $id) { - $results += $this->newPivot([ - $this->foreignPivotKey => $this->parent->{$this->parentKey}, - $this->relatedPivotKey => $id, - ], true)->delete(); + $records = $this->getCurrentlyAttachedPivotsForIds($ids); + + foreach ($records as $record) { + $results += $record->delete(); } return $results; @@ -497,15 +486,31 @@ protected function detachUsingCustomClass($ids) */ protected function getCurrentlyAttachedPivots() { - return $this->newPivotQuery()->get()->map(function ($record) { - $class = $this->using ?: Pivot::class; - - $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); + return $this->getCurrentlyAttachedPivotsForIds(); + } - return $pivot - ->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) - ->setRelatedModel($this->related); - }); + /** + * Get the pivot models that are currently attached, filtered by related model keys. + * + * @param mixed $ids + * @return \Illuminate\Support\Collection + */ + protected function getCurrentlyAttachedPivotsForIds($ids = null) + { + return $this->newPivotQuery() + ->when(! is_null($ids), fn ($query) => $query->whereIn( + $this->getQualifiedRelatedPivotKeyName(), $this->parseIds($ids) + )) + ->get() + ->map(function ($record) { + $class = $this->using ?: Pivot::class; + + $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); + + return $pivot + ->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) + ->setRelatedModel($this->related); + }); } /** @@ -557,7 +562,7 @@ public function newPivotStatement() */ public function newPivotStatementForId($id) { - return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id)); + return $this->newPivotQuery()->whereIn($this->getQualifiedRelatedPivotKeyName(), $this->parseIds($id)); } /** @@ -671,8 +676,8 @@ protected function castKey($key) protected function castAttributes($attributes) { return $this->using - ? $this->newPivot()->fill($attributes)->getAttributes() - : $attributes; + ? $this->newPivot()->fill($attributes)->getAttributes() + : $attributes; } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/HasMany.php b/src/Illuminate/Database/Eloquent/Relations/HasMany.php index 15b66f56dec6..1337b50246b0 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasMany.php @@ -38,8 +38,8 @@ function ($hasOne) { public function getResults() { return ! is_null($this->getParentKey()) - ? $this->query->get() - : $this->related->newCollection(); + ? $this->query->get() + : $this->related->newCollection(); } /** @inheritDoc */ diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 37e8410b58b6..b0905f3ae2bb 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -29,7 +29,7 @@ public function one() $this->farParent, $this->throughParent, $this->getFirstKeyName(), - $this->secondKey, + $this->getForeignKeyName(), $this->getLocalKeyName(), $this->getSecondLocalKeyName(), )); @@ -68,7 +68,7 @@ public function match(array $models, EloquentCollection $results, $relation) public function getResults() { return ! is_null($this->farParent->{$this->localKey}) - ? $this->get() - : $this->related->newCollection(); + ? $this->get() + : $this->related->newCollection(); } } diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 3d8426c2a85d..7451491cbaf9 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -41,7 +41,6 @@ abstract class HasOneOrMany extends Relation * @param TDeclaringModel $parent * @param string $foreignKey * @param string $localKey - * @return void */ public function __construct(Builder $query, Model $parent, $foreignKey, $localKey) { @@ -437,6 +436,34 @@ public function createManyQuietly(iterable $records) return Model::withoutEvents(fn () => $this->createMany($records)); } + /** + * Create a Collection of new instances of the related model, allowing mass-assignment. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function forceCreateMany(iterable $records) + { + $instances = $this->related->newCollection(); + + foreach ($records as $record) { + $instances->push($this->forceCreate($record)); + } + + return $instances; + } + + /** + * Create a Collection of new instances of the related model, allowing mass-assignment and without raising any events to the parent model. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function forceCreateManyQuietly(iterable $records) + { + return Model::withoutEvents(fn () => $this->forceCreateMany($records)); + } + /** * Set the foreign ID for creating a related model. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php index 6e74acf74651..97c011d6cefb 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php @@ -77,7 +77,6 @@ abstract class HasOneOrManyThrough extends Relation * @param string $secondKey * @param string $localKey * @param string $secondLocalKey - * @return void */ public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { @@ -98,12 +97,14 @@ public function __construct(Builder $query, Model $farParent, Model $throughPare */ public function addConstraints() { + $query = $this->getRelationQuery(); + $localValue = $this->farParent[$this->localKey]; - $this->performJoin(); + $this->performJoin($query); if (static::$constraints) { - $this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue); + $query->where($this->getQualifiedFirstKeyName(), '=', $localValue); } } @@ -115,7 +116,7 @@ public function addConstraints() */ protected function performJoin(?Builder $query = null) { - $query = $query ?: $this->query; + $query ??= $this->query; $farKey = $this->getQualifiedFarKeyName(); @@ -168,7 +169,8 @@ public function addEagerConstraints(array $models) $this->whereInEager( $whereIn, $this->getQualifiedFirstKeyName(), - $this->getKeys($models, $this->localKey) + $this->getKeys($models, $this->localKey), + $this->getRelationQuery(), ); } @@ -468,7 +470,7 @@ public function get($columns = ['*']) * @param array $columns * @param string $pageName * @param int $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php index 21de2e301213..4d42007f037f 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php @@ -2,10 +2,15 @@ namespace Illuminate\Database\Eloquent\Relations; +use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\Concerns\CanBeOneOfMany; +use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; +use Illuminate\Database\Query\JoinClause; /** * @template TRelatedModel of \Illuminate\Database\Eloquent\Model @@ -14,13 +19,17 @@ * * @extends \Illuminate\Database\Eloquent\Relations\HasOneOrManyThrough */ -class HasOneThrough extends HasOneOrManyThrough +class HasOneThrough extends HasOneOrManyThrough implements SupportsPartialRelations { - use InteractsWithDictionary, SupportsDefaultModels; + use ComparesRelatedModels, CanBeOneOfMany, InteractsWithDictionary, SupportsDefaultModels; /** @inheritDoc */ public function getResults() { + if (is_null($this->getParentKey())) { + return $this->getDefaultFor($this->farParent); + } + return $this->first() ?: $this->getDefaultFor($this->farParent); } @@ -54,6 +63,39 @@ public function match(array $models, EloquentCollection $results, $relation) return $models; } + /** @inheritDoc */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($this->isOneOfMany()) { + $this->mergeOneOfManyJoinsTo($query); + } + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** @inheritDoc */ + public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null) + { + $query->addSelect([$this->getQualifiedFirstKeyName()]); + + // We need to join subqueries that aren't the inner-most subquery which is joined in the CanBeOneOfMany::ofMany method... + if ($this->getOneOfManySubQuery() !== null) { + $this->performJoin($query); + } + } + + /** @inheritDoc */ + public function getOneOfManySubQuerySelectColumns() + { + return [$this->getQualifiedFirstKeyName()]; + } + + /** @inheritDoc */ + public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) + { + $join->on($this->qualifySubSelectColumn($this->firstKey), '=', $this->getQualifiedFirstKeyName()); + } + /** * Make a new related instance for the given model. * @@ -64,4 +106,16 @@ public function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance(); } + + /** @inheritDoc */ + protected function getRelatedKeyFrom(Model $model) + { + return $model->getAttribute($this->getForeignKeyName()); + } + + /** @inheritDoc */ + public function getParentKey() + { + return $this->farParent->getAttribute($this->localKey); + } } diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphMany.php index 86fab64d4e80..fd7830956dda 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphMany.php @@ -39,8 +39,8 @@ function ($morphOne) { public function getResults() { return ! is_null($this->getParentKey()) - ? $this->query->get() - : $this->related->newCollection(); + ? $this->query->get() + : $this->related->newCollection(); } /** @inheritDoc */ diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php index 1e879c1dcef1..6d6a34c31f38 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php @@ -25,7 +25,7 @@ abstract class MorphOneOrMany extends HasOneOrMany /** * The class name of the parent model. * - * @var string + * @var class-string */ protected $morphClass; @@ -37,7 +37,6 @@ abstract class MorphOneOrMany extends HasOneOrMany * @param string $type * @param string $id * @param string $localKey - * @return void */ public function __construct(Builder $query, Model $parent, $type, $id, $localKey) { @@ -159,7 +158,7 @@ public function getMorphType() /** * Get the class name of the parent model. * - * @return string + * @return class-string */ public function getMorphClass() { diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index 566e198c9bea..01aea33950fd 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -18,7 +18,7 @@ class MorphPivot extends Pivot * * Explicitly define this so it's not included in saved attributes. * - * @var string + * @var class-string */ protected $morphClass; @@ -100,7 +100,7 @@ public function setMorphType($morphType) /** * Set the morph class for the pivot. * - * @param string $morphClass + * @param class-string $morphClass * @return \Illuminate\Database\Eloquent\Relations\MorphPivot */ public function setMorphClass($morphClass) diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php index cc42984552a1..22e0cfce7227 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -83,7 +83,6 @@ class MorphTo extends BelongsTo * @param string|null $ownerKey * @param string $type * @param string $relation - * @return void */ public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) { @@ -176,10 +175,10 @@ protected function getResultsByType($type) protected function gatherKeysByType($type, $keyType) { return $keyType !== 'string' - ? array_keys($this->dictionary[$type]) - : array_map(function ($modelId) { - return (string) $modelId; - }, array_filter(array_keys($this->dictionary[$type]))); + ? array_keys($this->dictionary[$type]) + : array_map(function ($modelId) { + return (string) $modelId; + }, array_filter(array_keys($this->dictionary[$type]))); } /** @@ -237,8 +236,8 @@ public function associate($model) { if ($model instanceof Model) { $foreignKey = $this->ownerKey && $model->{$this->ownerKey} - ? $this->ownerKey - : $model->getKeyName(); + ? $this->ownerKey + : $model->getKeyName(); } $this->parent->setAttribute( diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 157202bccf21..162ebec1777b 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -10,8 +10,10 @@ /** * @template TRelatedModel of \Illuminate\Database\Eloquent\Model * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * @template TPivotModel of \Illuminate\Database\Eloquent\Relations\Pivot = \Illuminate\Database\Eloquent\Relations\MorphPivot + * @template TAccessor of string = 'pivot' * - * @extends \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @extends \Illuminate\Database\Eloquent\Relations\BelongsToMany */ class MorphToMany extends BelongsToMany { @@ -25,7 +27,7 @@ class MorphToMany extends BelongsToMany /** * The class name of the morph type constraint. * - * @var string + * @var class-string */ protected $morphClass; @@ -51,7 +53,6 @@ class MorphToMany extends BelongsToMany * @param string $relatedKey * @param string|null $relationName * @param bool $inverse - * @return void */ public function __construct( Builder $query, @@ -122,15 +123,15 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Get the pivot models that are currently attached. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ protected function getCurrentlyAttachedPivots() { return parent::getCurrentlyAttachedPivots()->map(function ($record) { return $record instanceof MorphPivot - ? $record->setMorphType($this->morphType) - ->setMorphClass($this->morphClass) - : $record; + ? $record->setMorphType($this->morphType) + ->setMorphClass($this->morphClass) + : $record; }); } @@ -149,7 +150,7 @@ public function newPivotQuery() * * @param array $attributes * @param bool $exists - * @return \Illuminate\Database\Eloquent\Relations\Pivot + * @return TPivotModel */ public function newPivot(array $attributes = [], $exists = false) { @@ -157,8 +158,9 @@ public function newPivot(array $attributes = [], $exists = false) $attributes = array_merge([$this->morphType => $this->morphClass], $attributes); - $pivot = $using ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists) - : MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists); + $pivot = $using + ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists) + : MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists); $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) ->setRelatedModel($this->related) @@ -177,11 +179,15 @@ public function newPivot(array $attributes = [], $exists = false) */ protected function aliasedPivotColumns() { - $defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType]; - - return (new Collection(array_merge($defaults, $this->pivotColumns)))->map(function ($column) { - return $this->qualifyPivotColumn($column).' as pivot_'.$column; - })->unique()->all(); + return (new Collection([ + $this->foreignPivotKey, + $this->relatedPivotKey, + $this->morphType, + ...$this->pivotColumns, + ])) + ->map(fn ($column) => $this->qualifyPivotColumn($column).' as pivot_'.$column) + ->unique() + ->all(); } /** @@ -207,7 +213,7 @@ public function getQualifiedMorphTypeName() /** * Get the class name of the parent model. * - * @return string + * @return class-string */ public function getMorphClass() { diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index e9e431adda51..ad7d75168e78 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -88,7 +88,6 @@ abstract class Relation implements BuilderContract * * @param \Illuminate\Database\Eloquent\Builder $query * @param TDeclaringModel $parent - * @return void */ public function __construct(Builder $query, Model $parent) { @@ -172,8 +171,8 @@ abstract public function getResults(); public function getEager() { return $this->eagerKeysWereEmpty - ? $this->query->getModel()->newCollection() - : $this->get(); + ? $this->related->newCollection() + : $this->get(); } /** @@ -424,9 +423,9 @@ protected function whereInEager(string $whereIn, string $key, array $modelKeys, protected function whereInMethod(Model $model, $key) { return $model->getKeyName() === last(explode('.', $key)) - && in_array($model->getKeyType(), ['int', 'integer']) - ? 'whereIntegerInRaw' - : 'whereIn'; + && in_array($model->getKeyType(), ['int', 'integer']) + ? 'whereIntegerInRaw' + : 'whereIn'; } /** @@ -477,7 +476,8 @@ public static function morphMap(?array $map = null, $merge = true) if (is_array($map)) { static::$morphMap = $merge && static::$morphMap - ? $map + static::$morphMap : $map; + ? $map + static::$morphMap + : $map; } return static::$morphMap; diff --git a/src/Illuminate/Database/Events/ConnectionEvent.php b/src/Illuminate/Database/Events/ConnectionEvent.php index 818c7850f3dd..0721a37c56c2 100644 --- a/src/Illuminate/Database/Events/ConnectionEvent.php +++ b/src/Illuminate/Database/Events/ConnectionEvent.php @@ -22,7 +22,6 @@ abstract class ConnectionEvent * Create a new event instance. * * @param \Illuminate\Database\Connection $connection - * @return void */ public function __construct($connection) { diff --git a/src/Illuminate/Database/Events/DatabaseBusy.php b/src/Illuminate/Database/Events/DatabaseBusy.php index 8e903a907535..04fc40bf3cd1 100644 --- a/src/Illuminate/Database/Events/DatabaseBusy.php +++ b/src/Illuminate/Database/Events/DatabaseBusy.php @@ -4,29 +4,15 @@ class DatabaseBusy { - /** - * The database connection name. - * - * @var string - */ - public $connectionName; - - /** - * The number of open connections. - * - * @var int - */ - public $connections; - /** * Create a new event instance. * - * @param string $connectionName - * @param int $connections + * @param string $connectionName The database connection name. + * @param int $connections The number of open connections. */ - public function __construct($connectionName, $connections) - { - $this->connectionName = $connectionName; - $this->connections = $connections; + public function __construct( + public $connectionName, + public $connections, + ) { } } diff --git a/src/Illuminate/Database/Events/DatabaseRefreshed.php b/src/Illuminate/Database/Events/DatabaseRefreshed.php index 0610269e22cd..66879b6aae6a 100644 --- a/src/Illuminate/Database/Events/DatabaseRefreshed.php +++ b/src/Illuminate/Database/Events/DatabaseRefreshed.php @@ -11,12 +11,10 @@ class DatabaseRefreshed implements MigrationEventContract * * @param string|null $database * @param bool $seeding - * @return void */ public function __construct( public ?string $database = null, public bool $seeding = false, ) { - // } } diff --git a/src/Illuminate/Database/Events/MigrationEvent.php b/src/Illuminate/Database/Events/MigrationEvent.php index 157303d2e2b5..83f10871a1d2 100644 --- a/src/Illuminate/Database/Events/MigrationEvent.php +++ b/src/Illuminate/Database/Events/MigrationEvent.php @@ -26,7 +26,6 @@ abstract class MigrationEvent implements MigrationEventContract * * @param \Illuminate\Database\Migrations\Migration $migration * @param string $method - * @return void */ public function __construct(Migration $migration, $method) { diff --git a/src/Illuminate/Database/Events/MigrationsEvent.php b/src/Illuminate/Database/Events/MigrationsEvent.php index ae1824040753..dbed4949e9c3 100644 --- a/src/Illuminate/Database/Events/MigrationsEvent.php +++ b/src/Illuminate/Database/Events/MigrationsEvent.php @@ -6,30 +6,15 @@ abstract class MigrationsEvent implements MigrationEventContract { - /** - * The migration method that was invoked. - * - * @var string - */ - public $method; - - /** - * The options provided when the migration method was invoked. - * - * @var array - */ - public $options; - /** * Create a new event instance. * - * @param string $method - * @param array $options - * @return void + * @param string $method The migration method that was invoked. + * @param array $options The options provided when the migration method was invoked. */ - public function __construct($method, array $options = []) - { - $this->method = $method; - $this->options = $options; + public function __construct( + public $method, + public array $options = [], + ) { } } diff --git a/src/Illuminate/Database/Events/MigrationsPruned.php b/src/Illuminate/Database/Events/MigrationsPruned.php index 86b48e9c9d3c..16e519e27c06 100644 --- a/src/Illuminate/Database/Events/MigrationsPruned.php +++ b/src/Illuminate/Database/Events/MigrationsPruned.php @@ -32,7 +32,6 @@ class MigrationsPruned * * @param \Illuminate\Database\Connection $connection * @param string $path - * @return void */ public function __construct(Connection $connection, string $path) { diff --git a/src/Illuminate/Database/Events/ModelPruningFinished.php b/src/Illuminate/Database/Events/ModelPruningFinished.php index d2701c4743c2..e27c687e847f 100644 --- a/src/Illuminate/Database/Events/ModelPruningFinished.php +++ b/src/Illuminate/Database/Events/ModelPruningFinished.php @@ -4,21 +4,13 @@ class ModelPruningFinished { - /** - * The class names of the models that were pruned. - * - * @var array - */ - public $models; - /** * Create a new event instance. * - * @param array $models - * @return void + * @param array $models The class names of the models that were pruned. */ - public function __construct($models) - { - $this->models = $models; + public function __construct( + public $models, + ) { } } diff --git a/src/Illuminate/Database/Events/ModelPruningStarting.php b/src/Illuminate/Database/Events/ModelPruningStarting.php index e6cc4d8426a9..a45f912dc283 100644 --- a/src/Illuminate/Database/Events/ModelPruningStarting.php +++ b/src/Illuminate/Database/Events/ModelPruningStarting.php @@ -4,21 +4,13 @@ class ModelPruningStarting { - /** - * The class names of the models that will be pruned. - * - * @var array - */ - public $models; - /** * Create a new event instance. * - * @param array $models - * @return void + * @param array $models The class names of the models that will be pruned. */ - public function __construct($models) - { - $this->models = $models; + public function __construct( + public $models + ) { } } diff --git a/src/Illuminate/Database/Events/ModelsPruned.php b/src/Illuminate/Database/Events/ModelsPruned.php index ca8bee9e0f5d..2d9605e5fe60 100644 --- a/src/Illuminate/Database/Events/ModelsPruned.php +++ b/src/Illuminate/Database/Events/ModelsPruned.php @@ -4,30 +4,15 @@ class ModelsPruned { - /** - * The class name of the model that was pruned. - * - * @var string - */ - public $model; - - /** - * The number of pruned records. - * - * @var int - */ - public $count; - /** * Create a new event instance. * - * @param string $model - * @param int $count - * @return void + * @param string $model The class name of the model that was pruned. + * @param int $count The number of pruned records. */ - public function __construct($model, $count) - { - $this->model = $model; - $this->count = $count; + public function __construct( + public $model, + public $count, + ) { } } diff --git a/src/Illuminate/Database/Events/NoPendingMigrations.php b/src/Illuminate/Database/Events/NoPendingMigrations.php index 2a1cb348533f..ab9eb6b620ea 100644 --- a/src/Illuminate/Database/Events/NoPendingMigrations.php +++ b/src/Illuminate/Database/Events/NoPendingMigrations.php @@ -6,21 +6,13 @@ class NoPendingMigrations implements MigrationEvent { - /** - * The migration method that was called. - * - * @var string - */ - public $method; - /** * Create a new event instance. * - * @param string $method - * @return void + * @param string $method The migration method that was called. */ - public function __construct($method) - { - $this->method = $method; + public function __construct( + public $method, + ) { } } diff --git a/src/Illuminate/Database/Events/QueryExecuted.php b/src/Illuminate/Database/Events/QueryExecuted.php index 644d947332c3..960df9da0954 100644 --- a/src/Illuminate/Database/Events/QueryExecuted.php +++ b/src/Illuminate/Database/Events/QueryExecuted.php @@ -46,7 +46,6 @@ class QueryExecuted * @param array $bindings * @param float|null $time * @param \Illuminate\Database\Connection $connection - * @return void */ public function __construct($sql, $bindings, $time, $connection) { diff --git a/src/Illuminate/Database/Events/SchemaDumped.php b/src/Illuminate/Database/Events/SchemaDumped.php index 1cbbfff96ec6..416462027c30 100644 --- a/src/Illuminate/Database/Events/SchemaDumped.php +++ b/src/Illuminate/Database/Events/SchemaDumped.php @@ -30,7 +30,6 @@ class SchemaDumped * * @param \Illuminate\Database\Connection $connection * @param string $path - * @return void */ public function __construct($connection, $path) { diff --git a/src/Illuminate/Database/Events/SchemaLoaded.php b/src/Illuminate/Database/Events/SchemaLoaded.php index 061a079a9611..d86ae5307499 100644 --- a/src/Illuminate/Database/Events/SchemaLoaded.php +++ b/src/Illuminate/Database/Events/SchemaLoaded.php @@ -30,7 +30,6 @@ class SchemaLoaded * * @param \Illuminate\Database\Connection $connection * @param string $path - * @return void */ public function __construct($connection, $path) { diff --git a/src/Illuminate/Database/Events/StatementPrepared.php b/src/Illuminate/Database/Events/StatementPrepared.php index 2f603235da25..43f02a0e26a3 100644 --- a/src/Illuminate/Database/Events/StatementPrepared.php +++ b/src/Illuminate/Database/Events/StatementPrepared.php @@ -4,30 +4,15 @@ class StatementPrepared { - /** - * The database connection instance. - * - * @var \Illuminate\Database\Connection - */ - public $connection; - - /** - * The PDO statement. - * - * @var \PDOStatement - */ - public $statement; - /** * Create a new event instance. * - * @param \Illuminate\Database\Connection $connection - * @param \PDOStatement $statement - * @return void + * @param \Illuminate\Database\Connection $connection The database connection instance. + * @param \PDOStatement $statement The PDO statement. */ - public function __construct($connection, $statement) - { - $this->statement = $statement; - $this->connection = $connection; + public function __construct( + public $connection, + public $statement, + ) { } } diff --git a/src/Illuminate/Database/Grammar.php b/src/Illuminate/Database/Grammar.php index 8dd9bc353ef4..d56482dc889b 100755 --- a/src/Illuminate/Database/Grammar.php +++ b/src/Illuminate/Database/Grammar.php @@ -19,11 +19,14 @@ abstract class Grammar protected $connection; /** - * The grammar table prefix. + * Create a new grammar instance. * - * @var string + * @param \Illuminate\Database\Connection $connection */ - protected $tablePrefix = ''; + public function __construct(Connection $connection) + { + $this->connection = $connection; + } /** * Wrap an array of values. @@ -33,40 +36,43 @@ abstract class Grammar */ public function wrapArray(array $values) { - return array_map([$this, 'wrap'], $values); + return array_map($this->wrap(...), $values); } /** * Wrap a table in keyword identifiers. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table + * @param string|null $prefix * @return string */ - public function wrapTable($table) + public function wrapTable($table, $prefix = null) { if ($this->isExpression($table)) { return $this->getValue($table); } + $prefix ??= $this->connection->getTablePrefix(); + // If the table being wrapped has an alias we'll need to separate the pieces // so we can prefix the table and then wrap each of the segments on their // own and then join these both back together using the "as" connector. if (stripos($table, ' as ') !== false) { - return $this->wrapAliasedTable($table); + return $this->wrapAliasedTable($table, $prefix); } // If the table being wrapped has a custom schema name specified, we need to // prefix the last segment as the table name then wrap each segment alone // and eventually join them both back together using the dot connector. if (str_contains($table, '.')) { - $table = substr_replace($table, '.'.$this->tablePrefix, strrpos($table, '.'), 1); + $table = substr_replace($table, '.'.$prefix, strrpos($table, '.'), 1); return (new Collection(explode('.', $table))) ->map($this->wrapValue(...)) ->implode('.'); } - return $this->wrapValue($this->tablePrefix.$table); + return $this->wrapValue($prefix.$table); } /** @@ -115,13 +121,16 @@ protected function wrapAliasedValue($value) * Wrap a table that has an alias. * * @param string $value + * @param string|null $prefix * @return string */ - protected function wrapAliasedTable($value) + protected function wrapAliasedTable($value, $prefix = null) { $segments = preg_split('/\s+as\s+/i', $value); - return $this->wrapTable($segments[0]).' as '.$this->wrapValue($this->tablePrefix.$segments[1]); + $prefix ??= $this->connection->getTablePrefix(); + + return $this->wrapTable($segments[0], $prefix).' as '.$this->wrapValue($prefix.$segments[1]); } /** @@ -134,8 +143,8 @@ protected function wrapSegments($segments) { return (new Collection($segments))->map(function ($segment, $key) use ($segments) { return $key == 0 && count($segments) > 1 - ? $this->wrapTable($segment) - : $this->wrapValue($segment); + ? $this->wrapTable($segment) + : $this->wrapValue($segment); })->implode('.'); } @@ -186,7 +195,7 @@ protected function isJsonSelector($value) */ public function columnize(array $columns) { - return implode(', ', array_map([$this, 'wrap'], $columns)); + return implode(', ', array_map($this->wrap(...), $columns)); } /** @@ -197,7 +206,7 @@ public function columnize(array $columns) */ public function parameterize(array $values) { - return implode(', ', array_map([$this, 'parameter'], $values)); + return implode(', ', array_map($this->parameter(...), $values)); } /** @@ -235,10 +244,6 @@ public function quoteString($value) */ public function escape($value, $binary = false) { - if (is_null($this->connection)) { - throw new RuntimeException("The database driver's grammar implementation does not support escaping values."); - } - return $this->connection->escape($value, $binary); } @@ -281,35 +286,26 @@ public function getDateFormat() /** * Get the grammar's table prefix. * + * @deprecated Use DB::getTablePrefix() + * * @return string */ public function getTablePrefix() { - return $this->tablePrefix; + return $this->connection->getTablePrefix(); } /** * Set the grammar's table prefix. * + * @deprecated Use DB::setTablePrefix() + * * @param string $prefix * @return $this */ public function setTablePrefix($prefix) { - $this->tablePrefix = $prefix; - - return $this; - } - - /** - * Set the grammar's database connection. - * - * @param \Illuminate\Database\Connection $connection - * @return $this - */ - public function setConnection($connection) - { - $this->connection = $connection; + $this->connection->setTablePrefix($prefix); return $this; } diff --git a/src/Illuminate/Database/LazyLoadingViolationException.php b/src/Illuminate/Database/LazyLoadingViolationException.php index 36d8fec43ce2..f0a90f6c95f2 100644 --- a/src/Illuminate/Database/LazyLoadingViolationException.php +++ b/src/Illuminate/Database/LazyLoadingViolationException.php @@ -25,7 +25,6 @@ class LazyLoadingViolationException extends RuntimeException * * @param object $model * @param string $relation - * @return void */ public function __construct($model, $relation) { diff --git a/src/Illuminate/Database/MariaDbConnection.php b/src/Illuminate/Database/MariaDbConnection.php index ebd33a15b9b5..c4040b6c34ad 100755 --- a/src/Illuminate/Database/MariaDbConnection.php +++ b/src/Illuminate/Database/MariaDbConnection.php @@ -47,9 +47,7 @@ public function getServerVersion(): string */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new QueryGrammar($this); } /** @@ -73,9 +71,7 @@ public function getSchemaBuilder() */ protected function getDefaultSchemaGrammar() { - ($grammar = new SchemaGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new SchemaGrammar($this); } /** diff --git a/src/Illuminate/Database/MigrationServiceProvider.php b/src/Illuminate/Database/MigrationServiceProvider.php index cab266bb2f9d..037106c73579 100755 --- a/src/Illuminate/Database/MigrationServiceProvider.php +++ b/src/Illuminate/Database/MigrationServiceProvider.php @@ -82,6 +82,8 @@ protected function registerMigrator() return new Migrator($repository, $app['db'], $app['files'], $app['events']); }); + + $this->app->bind(Migrator::class, fn ($app) => $app['migrator']); } /** @@ -220,7 +222,7 @@ protected function registerMigrateStatusCommand() public function provides() { return array_merge([ - 'migrator', 'migration.repository', 'migration.creator', + 'migrator', 'migration.repository', 'migration.creator', Migrator::class, ], array_values($this->commands)); } } diff --git a/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php b/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php index c5d5252854f1..a762da81b603 100755 --- a/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php +++ b/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php @@ -32,7 +32,6 @@ class DatabaseMigrationRepository implements MigrationRepositoryInterface * * @param \Illuminate\Database\ConnectionResolverInterface $resolver * @param string $table - * @return void */ public function __construct(Resolver $resolver, $table) { diff --git a/src/Illuminate/Database/Migrations/Migration.php b/src/Illuminate/Database/Migrations/Migration.php index a58f7848a7e1..35c8d43be388 100755 --- a/src/Illuminate/Database/Migrations/Migration.php +++ b/src/Illuminate/Database/Migrations/Migration.php @@ -27,4 +27,14 @@ public function getConnection() { return $this->connection; } + + /** + * Determine if this migration should run. + * + * @return bool + */ + public function shouldRun(): bool + { + return true; + } } diff --git a/src/Illuminate/Database/Migrations/MigrationCreator.php b/src/Illuminate/Database/Migrations/MigrationCreator.php index d8f1ce9d0b1e..ba98eb658148 100755 --- a/src/Illuminate/Database/Migrations/MigrationCreator.php +++ b/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -35,7 +35,6 @@ class MigrationCreator * * @param \Illuminate\Filesystem\Filesystem $files * @param string $customStubPath - * @return void */ public function __construct(Filesystem $files, $customStubPath) { @@ -114,16 +113,16 @@ protected function getStub($table, $create) { if (is_null($table)) { $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.stub') - ? $customPath - : $this->stubPath().'/migration.stub'; + ? $customPath + : $this->stubPath().'/migration.stub'; } elseif ($create) { $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.create.stub') - ? $customPath - : $this->stubPath().'/migration.create.stub'; + ? $customPath + : $this->stubPath().'/migration.create.stub'; } else { $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.update.stub') - ? $customPath - : $this->stubPath().'/migration.update.stub'; + ? $customPath + : $this->stubPath().'/migration.update.stub'; } return $this->files->get($stub); diff --git a/src/Illuminate/Database/Migrations/MigrationResult.php b/src/Illuminate/Database/Migrations/MigrationResult.php new file mode 100644 index 000000000000..649eb5b269d3 --- /dev/null +++ b/src/Illuminate/Database/Migrations/MigrationResult.php @@ -0,0 +1,10 @@ +pretendToRun($migration, 'up'); } - $this->write(Task::class, $name, fn () => $this->runMigration($migration, 'up')); + $shouldRunMigration = $migration instanceof Migration + ? $migration->shouldRun() + : true; + + if (! $shouldRunMigration) { + $this->write(Task::class, $name, fn () => MigrationResult::Skipped->value); + } else { + $this->write(Task::class, $name, fn () => $this->runMigration($migration, 'up')); - // Once we have run a migrations class, we will log that it was run in this - // repository so that we don't try to run it next time we do a migration - // in the application. A migration repository keeps the migrate order. - $this->repository->log($name, $batch); + // Once we have run a migrations class, we will log that it was run in this + // repository so that we don't try to run it next time we do a migration + // in the application. A migration repository keeps the migrate order. + $this->repository->log($name, $batch); + } } /** @@ -437,8 +444,8 @@ protected function runMigration($migration, $method) $this->getSchemaGrammar($connection)->supportsSchemaTransactions() && $migration->withinTransaction - ? $connection->transaction($callback) - : $callback(); + ? $connection->transaction($callback) + : $callback(); } /** @@ -541,8 +548,8 @@ protected function resolvePath(string $path) if (is_object($migration)) { return method_exists($migration, '__construct') - ? $this->files->getRequire($path) - : clone $migration; + ? $this->files->getRequire($path) + : clone $migration; } return new $class; diff --git a/src/Illuminate/Database/MultipleRecordsFoundException.php b/src/Illuminate/Database/MultipleRecordsFoundException.php index b14a8598fb73..baeee221194c 100755 --- a/src/Illuminate/Database/MultipleRecordsFoundException.php +++ b/src/Illuminate/Database/MultipleRecordsFoundException.php @@ -19,7 +19,6 @@ class MultipleRecordsFoundException extends RuntimeException * @param int $count * @param int $code * @param \Throwable|null $previous - * @return void */ public function __construct($count, $code = 0, $previous = null) { diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index abc1edb4f831..54c1aff0b7e9 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -121,9 +121,7 @@ public function getServerVersion(): string */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new QueryGrammar($this); } /** @@ -147,9 +145,7 @@ public function getSchemaBuilder() */ protected function getDefaultSchemaGrammar() { - ($grammar = new SchemaGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new SchemaGrammar($this); } /** diff --git a/src/Illuminate/Database/PostgresConnection.php b/src/Illuminate/Database/PostgresConnection.php index 06fa2e1d8e48..f80b5dce5df1 100755 --- a/src/Illuminate/Database/PostgresConnection.php +++ b/src/Illuminate/Database/PostgresConnection.php @@ -62,9 +62,7 @@ protected function isUniqueConstraintError(Exception $exception) */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new QueryGrammar($this); } /** @@ -88,9 +86,7 @@ public function getSchemaBuilder() */ protected function getDefaultSchemaGrammar() { - ($grammar = new SchemaGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new SchemaGrammar($this); } /** diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 538994fe4b25..1f00b7bd655f 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -63,7 +63,17 @@ class Builder implements BuilderContract /** * The current query value bindings. * - * @var array + * @var array{ + * select: list, + * from: list, + * join: list, + * where: list, + * groupBy: list, + * having: list, + * order: list, + * union: list, + * unionOrder: list, + * } */ public $bindings = [ 'select' => [], @@ -251,8 +261,6 @@ class Builder implements BuilderContract /** * Create a new query builder instance. - * - * @return void */ public function __construct( ConnectionInterface $connection, @@ -868,7 +876,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' // where null clause to the query. So, we will allow a short-cut here to // that method for convenience so the developer doesn't have to check. if (is_null($value)) { - return $this->whereNull($column, $boolean, $operator !== '='); + return $this->whereNull($column, $boolean, ! in_array($operator, ['=', '<=>'], true)); } $type = 'Basic'; @@ -960,7 +968,7 @@ public function prepareValueAndOperator($value, $operator, $useDefault = false) protected function invalidOperatorAndValue($operator, $value) { return is_null($value) && in_array($operator, $this->operators) && - ! in_array($operator, ['=', '<>', '!=']); + ! in_array($operator, ['=', '<=>', '<>', '!=']); } /** @@ -2400,8 +2408,8 @@ public function groupByRaw($sql, array $bindings = []) * Add a "having" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|\Closure|string $column - * @param string|int|float|null $operator - * @param string|int|float|null $value + * @param \DateTimeInterface|string|int|float|null $operator + * @param \Illuminate\Contracts\Database\Query\Expression|\DateTimeInterface|string|int|float|null $value * @param string $boolean * @return $this */ @@ -2452,8 +2460,8 @@ public function having($column, $operator = null, $value = null, $boolean = 'and * Add an "or having" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|\Closure|string $column - * @param string|int|float|null $operator - * @param string|int|float|null $value + * @param \DateTimeInterface|string|int|float|null $operator + * @param \Illuminate\Contracts\Database\Query\Expression|\DateTimeInterface|string|int|float|null $value * @return $this */ public function orHaving($column, $operator = null, $value = null) @@ -2792,7 +2800,9 @@ public function forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id') { $this->orders = $this->removeExistingOrdersFor($column); - if (! is_null($lastId)) { + if (is_null($lastId)) { + $this->whereNotNull($column); + } else { $this->where($column, '<', $lastId); } @@ -2812,7 +2822,9 @@ public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id') { $this->orders = $this->removeExistingOrdersFor($column); - if (! is_null($lastId)) { + if (is_null($lastId)) { + $this->whereNotNull($column); + } else { $this->where($column, '>', $lastId); } @@ -2850,10 +2862,9 @@ public function reorder($column = null, $direction = 'asc') protected function removeExistingOrdersFor($column) { return (new Collection($this->orders)) - ->reject(function ($order) use ($column) { - return isset($order['column']) - ? $order['column'] === $column : false; - })->values()->all(); + ->reject(fn ($order) => isset($order['column']) && $order['column'] === $column) + ->values() + ->all(); } /** @@ -3142,7 +3153,7 @@ protected function withoutGroupLimitKeys($items) * @param string $pageName * @param int|null $page * @param \Closure|int|null $total - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null, $total = null) { @@ -3305,7 +3316,8 @@ protected function withoutSelectAliases(array $columns) { return array_map(function ($column) { return is_string($column) && ($aliasPosition = stripos($column, ' as ')) !== false - ? substr($column, 0, $aliasPosition) : $column; + ? substr($column, 0, $aliasPosition) + : $column; }, $columns); } @@ -3377,8 +3389,8 @@ function () { return $this->applyAfterQueryCallbacks( is_array($queryResult[0]) - ? $this->pluckFromArrayColumn($queryResult, $column, $key) - : $this->pluckFromObjectColumn($queryResult, $column, $key) + ? $this->pluckFromArrayColumn($queryResult, $column, $key) + : $this->pluckFromObjectColumn($queryResult, $column, $key) ); } @@ -3633,7 +3645,8 @@ public function numericAggregate($function, $columns = ['*']) // cast it to one. When it does we will cast it to a float since it needs to be // cast to the expected data type for the developers out of pure convenience. return ! str_contains((string) $result, '.') - ? (int) $result : (float) $result; + ? (int) $result + : (float) $result; } /** @@ -4070,8 +4083,8 @@ protected function forSubQuery() public function getColumns() { return ! is_null($this->columns) - ? array_map(fn ($column) => $this->grammar->getValue($column), $this->columns) - : []; + ? array_map(fn ($column) => $this->grammar->getValue($column), $this->columns) + : []; } /** @@ -4097,10 +4110,34 @@ protected function getUnionBuilders() : new Collection; } + /** + * Get the "limit" value for the query or null if it's not set. + * + * @return mixed + */ + public function getLimit() + { + $value = $this->unions ? $this->unionLimit : $this->limit; + + return ! is_null($value) ? (int) $value : null; + } + + /** + * Get the "offset" value for the query or null if it's not set. + * + * @return mixed + */ + public function getOffset() + { + $value = $this->unions ? $this->unionOffset : $this->offset; + + return ! is_null($value) ? (int) $value : null; + } + /** * Get the current query value bindings in a flattened array. * - * @return array + * @return list */ public function getBindings() { @@ -4110,7 +4147,17 @@ public function getBindings() /** * Get the raw array of bindings. * - * @return array + * @return array{ + * select: list, + * from: list, + * join: list, + * where: list, + * groupBy: list, + * having: list, + * order: list, + * union: list, + * unionOrder: list, + * } */ public function getRawBindings() { @@ -4120,6 +4167,7 @@ public function getRawBindings() /** * Set the bindings on the query builder. * + * @param list $bindings * @param string $type * @return $this * @@ -4153,7 +4201,7 @@ public function addBinding($value, $type = 'where') if (is_array($value)) { $this->bindings[$type] = array_values(array_map( - [$this, 'castBinding'], + $this->castBinding(...), array_merge($this->bindings[$type], $value), )); } else { @@ -4181,6 +4229,7 @@ public function castBinding($value) /** * Merge an array of bindings into our bindings. * + * @param self $query * @return $this */ public function mergeBindings(self $query) @@ -4193,7 +4242,8 @@ public function mergeBindings(self $query) /** * Remove all of the expressions from a list of bindings. * - * @return array + * @param array $bindings + * @return list */ public function cleanBindings(array $bindings) { @@ -4201,7 +4251,7 @@ public function cleanBindings(array $bindings) ->reject(function ($binding) { return $binding instanceof ExpressionContract; }) - ->map([$this, 'castBinding']) + ->map($this->castBinding(...)) ->values() ->all(); } diff --git a/src/Illuminate/Database/Query/Expression.php b/src/Illuminate/Database/Query/Expression.php index 1da00d5e9bc1..1568e1ff9436 100755 --- a/src/Illuminate/Database/Query/Expression.php +++ b/src/Illuminate/Database/Query/Expression.php @@ -14,7 +14,6 @@ class Expression implements ExpressionContract * Create a new raw query expression. * * @param TValue $value - * @return void */ public function __construct( protected $value diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index c64aa49a783f..9b35083af603 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -1203,7 +1203,7 @@ public function compileInsertOrIgnore(Builder $query, array $values) * * @param \Illuminate\Database\Query\Builder $query * @param array $values - * @param string $sequence + * @param string|null $sequence * @return string */ public function compileInsertGetId(Builder $query, $values, $sequence) diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index 2103f6c906fe..9207fe54565f 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -108,9 +108,14 @@ protected function whereLike(Builder $query, $where) */ protected function whereDate(Builder $query, $where) { + $column = $this->wrap($where['column']); $value = $this->parameter($where['value']); - return $this->wrap($where['column']).'::date '.$where['operator'].' '.$value; + if ($this->isJsonSelector($where['column'])) { + $column = '('.$column.')'; + } + + return $column.'::date '.$where['operator'].' '.$value; } /** @@ -122,9 +127,14 @@ protected function whereDate(Builder $query, $where) */ protected function whereTime(Builder $query, $where) { + $column = $this->wrap($where['column']); $value = $this->parameter($where['value']); - return $this->wrap($where['column']).'::time '.$where['operator'].' '.$value; + if ($this->isJsonSelector($where['column'])) { + $column = '('.$column.')'; + } + + return $column.'::time '.$where['operator'].' '.$value; } /** @@ -373,7 +383,7 @@ public function compileInsertOrIgnoreUsing(Builder $query, array $columns, strin * * @param \Illuminate\Database\Query\Builder $query * @param array $values - * @param string $sequence + * @param string|null $sequence * @return string */ public function compileInsertGetId(Builder $query, $values, $sequence) @@ -821,8 +831,16 @@ public static function customOperators(array $operators) * @param bool $value * @return void */ - public static function cascadeOnTrucate(bool $value = true) + public static function cascadeOnTruncate(bool $value = true) { static::$cascadeTruncate = $value; } + + /** + * @deprecated use cascadeOnTruncate + */ + public static function cascadeOnTrucate(bool $value = true) + { + self::cascadeOnTruncate($value); + } } diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 0999f4ec6f86..9fb8d8a31589 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -161,8 +161,8 @@ protected function dateBasedWhere($type, Builder $query, $where) protected function compileIndexHint(Builder $query, $indexHint) { return $indexHint->type === 'force' - ? "indexed by {$indexHint->index}" - : ''; + ? "indexed by {$indexHint->index}" + : ''; } /** @@ -439,8 +439,12 @@ protected function compileDeleteWithJoinsOrLimit(Builder $query) */ public function compileTruncate(Builder $query) { + [$schema, $table] = $query->getConnection()->getSchemaBuilder()->parseSchemaAndTable($query->from); + + $schema = $schema ? $this->wrapValue($schema).'.' : ''; + return [ - 'delete from sqlite_sequence where name = ?' => [$this->getTablePrefix().$query->from], + 'delete from '.$schema.'sqlite_sequence where name = ?' => [$query->getConnection()->getTablePrefix().$table], 'delete from '.$this->wrapTable($query->from) => [], ]; } diff --git a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php index ff1ac5cd3e68..c5e91c50e1bf 100755 --- a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -114,8 +114,8 @@ protected function compileFrom(Builder $query, $table) protected function compileIndexHint(Builder $query, $indexHint) { return $indexHint->type === 'force' - ? "with (index({$indexHint->index}))" - : ''; + ? "with (index({$indexHint->index}))" + : ''; } /** @@ -281,8 +281,8 @@ protected function compileDeleteWithoutJoins(Builder $query, $table, $where) $sql = parent::compileDeleteWithoutJoins($query, $table, $where); return ! is_null($query->limit) && $query->limit > 0 && $query->offset <= 0 - ? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql) - : $sql; + ? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql) + : $sql; } /** @@ -557,12 +557,13 @@ protected function wrapJsonBooleanValue($value) * Wrap a table in keyword identifiers. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table + * @param string|null $prefix * @return string */ - public function wrapTable($table) + public function wrapTable($table, $prefix = null) { if (! $this->isExpression($table)) { - return $this->wrapTableValuedFunction(parent::wrapTable($table)); + return $this->wrapTableValuedFunction(parent::wrapTable($table, $prefix)); } return $this->getValue($table); diff --git a/src/Illuminate/Database/Query/IndexHint.php b/src/Illuminate/Database/Query/IndexHint.php index 2a720a2dee2b..5659daa548af 100755 --- a/src/Illuminate/Database/Query/IndexHint.php +++ b/src/Illuminate/Database/Query/IndexHint.php @@ -23,7 +23,6 @@ class IndexHint * * @param string $type * @param string $index - * @return void */ public function __construct($type, $index) { diff --git a/src/Illuminate/Database/Query/JoinClause.php b/src/Illuminate/Database/Query/JoinClause.php index 37a002c57245..a9168087b254 100755 --- a/src/Illuminate/Database/Query/JoinClause.php +++ b/src/Illuminate/Database/Query/JoinClause.php @@ -54,7 +54,6 @@ class JoinClause extends Builder * @param \Illuminate\Database\Query\Builder $parentQuery * @param string $type * @param string $table - * @return void */ public function __construct(Builder $parentQuery, $type, $table) { diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 21b1fa97f9f7..331f2d8a688e 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -39,12 +39,7 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu return is_numeric($id) ? (int) $id : $id; } - /** - * Process the results of a columns query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processColumns($results) { return array_map(function ($result) { @@ -71,12 +66,7 @@ public function processColumns($results) }, $results); } - /** - * Process the results of an indexes query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processIndexes($results) { return array_map(function ($result) { @@ -92,12 +82,7 @@ public function processIndexes($results) }, $results); } - /** - * Process the results of a foreign keys query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processForeignKeys($results) { return array_map(function ($result) { diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index 80babf37fb6b..871575a5c488 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -30,12 +30,7 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu return is_numeric($id) ? (int) $id : $id; } - /** - * Process the results of a types query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processTypes($results) { return array_map(function ($result) { @@ -44,6 +39,7 @@ public function processTypes($results) return [ 'name' => $result->name, 'schema' => $result->schema, + 'schema_qualified_name' => $result->schema.'.'.$result->name, 'implicit' => (bool) $result->implicit, 'type' => match (strtolower($result->type)) { 'b' => 'base', @@ -78,12 +74,7 @@ public function processTypes($results) }, $results); } - /** - * Process the results of a columns query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processColumns($results) { return array_map(function ($result) { @@ -111,12 +102,7 @@ public function processColumns($results) }, $results); } - /** - * Process the results of an indexes query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processIndexes($results) { return array_map(function ($result) { @@ -132,12 +118,7 @@ public function processIndexes($results) }, $results); } - /** - * Process the results of a foreign keys query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processForeignKeys($results) { return array_map(function ($result) { diff --git a/src/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 936e6245b170..46f692e49a58 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -36,11 +36,30 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu return is_numeric($id) ? (int) $id : $id; } + /** + * Process the results of a schemas query. + * + * @param list> $results + * @return list + */ + public function processSchemas($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'path' => $result->path ?? null, // SQLite Only... + 'default' => (bool) $result->default, + ]; + }, $results); + } + /** * Process the results of a tables query. * - * @param array $results - * @return array + * @param list> $results + * @return list */ public function processTables($results) { @@ -49,7 +68,8 @@ public function processTables($results) return [ 'name' => $result->name, - 'schema' => $result->schema ?? null, // PostgreSQL and SQL Server + 'schema' => $result->schema ?? null, + 'schema_qualified_name' => isset($result->schema) ? $result->schema.'.'.$result->name : $result->name, 'size' => isset($result->size) ? (int) $result->size : null, 'comment' => $result->comment ?? null, // MySQL and PostgreSQL 'collation' => $result->collation ?? null, // MySQL only @@ -61,8 +81,8 @@ public function processTables($results) /** * Process the results of a views query. * - * @param array $results - * @return array + * @param list> $results + * @return list */ public function processViews($results) { @@ -71,7 +91,8 @@ public function processViews($results) return [ 'name' => $result->name, - 'schema' => $result->schema ?? null, // PostgreSQL and SQL Server + 'schema' => $result->schema ?? null, + 'schema_qualified_name' => isset($result->schema) ? $result->schema.'.'.$result->name : $result->name, 'definition' => $result->definition, ]; }, $results); @@ -80,8 +101,8 @@ public function processViews($results) /** * Process the results of a types query. * - * @param array $results - * @return array + * @param list> $results + * @return list */ public function processTypes($results) { @@ -91,8 +112,8 @@ public function processTypes($results) /** * Process the results of a columns query. * - * @param array $results - * @return array + * @param list> $results + * @return list */ public function processColumns($results) { @@ -102,8 +123,8 @@ public function processColumns($results) /** * Process the results of an indexes query. * - * @param array $results - * @return array + * @param list> $results + * @return list, type: string, unique: bool, primary: bool}> */ public function processIndexes($results) { @@ -113,8 +134,8 @@ public function processIndexes($results) /** * Process the results of a foreign keys query. * - * @param array $results - * @return array + * @param list> $results + * @return list, foreign_schema: string, foreign_table: string, foreign_columns: list, on_update: string, on_delete: string}> */ public function processForeignKeys($results) { diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index dadeaeb467ad..ed4916a7a54d 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -4,13 +4,7 @@ class SQLiteProcessor extends Processor { - /** - * Process the results of a columns query. - * - * @param array $results - * @param string $sql - * @return array - */ + /** @inheritDoc */ public function processColumns($results, $sql = '') { $hasPrimaryKey = array_sum(array_column($results, 'primary')) === 1; @@ -57,12 +51,7 @@ public function processColumns($results, $sql = '') }, $results); } - /** - * Process the results of an indexes query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processIndexes($results) { $primaryCount = 0; @@ -90,12 +79,7 @@ public function processIndexes($results) return $indexes; } - /** - * Process the results of a foreign keys query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processForeignKeys($results) { return array_map(function ($result) { @@ -104,7 +88,7 @@ public function processForeignKeys($results) return [ 'name' => null, 'columns' => explode(',', $result->columns), - 'foreign_schema' => null, + 'foreign_schema' => $result->foreign_schema, 'foreign_table' => $result->foreign_table, 'foreign_columns' => explode(',', $result->foreign_columns), 'on_update' => strtolower($result->on_update), diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index f5679552e22b..8d000c4579ac 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -55,12 +55,7 @@ protected function processInsertGetIdForOdbc(Connection $connection) return is_object($row) ? $row->insertid : $row['insertid']; } - /** - * Process the results of a columns query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processColumns($results) { return array_map(function ($result) { @@ -90,12 +85,7 @@ public function processColumns($results) }, $results); } - /** - * Process the results of an indexes query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processIndexes($results) { return array_map(function ($result) { @@ -111,12 +101,7 @@ public function processIndexes($results) }, $results); } - /** - * Process the results of a foreign keys query. - * - * @param array $results - * @return array - */ + /** @inheritDoc */ public function processForeignKeys($results) { return array_map(function ($result) { diff --git a/src/Illuminate/Database/QueryException.php b/src/Illuminate/Database/QueryException.php index 84aebb1a670b..a83657188538 100644 --- a/src/Illuminate/Database/QueryException.php +++ b/src/Illuminate/Database/QueryException.php @@ -2,6 +2,7 @@ namespace Illuminate\Database; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use PDOException; use Throwable; @@ -36,7 +37,6 @@ class QueryException extends PDOException * @param string $sql * @param array $bindings * @param \Throwable $previous - * @return void */ public function __construct($connectionName, $sql, array $bindings, Throwable $previous) { @@ -87,6 +87,16 @@ public function getSql() return $this->sql; } + /** + * Get the raw SQL representation of the query with embedded bindings. + */ + public function getRawSql(): string + { + return DB::connection($this->getConnectionName()) + ->getQueryGrammar() + ->substituteBindingsIntoRawSql($this->getSql(), $this->getBindings()); + } + /** * Get the bindings for the query. * diff --git a/src/Illuminate/Database/SQLiteConnection.php b/src/Illuminate/Database/SQLiteConnection.php index bccd33118118..53cbfad42c9d 100755 --- a/src/Illuminate/Database/SQLiteConnection.php +++ b/src/Illuminate/Database/SQLiteConnection.php @@ -12,25 +12,6 @@ class SQLiteConnection extends Connection { - /** - * Create a new database connection instance. - * - * @param \PDO|\Closure $pdo - * @param string $database - * @param string $tablePrefix - * @param array $config - * @return void - */ - public function __construct($pdo, $database = '', $tablePrefix = '', array $config = []) - { - parent::__construct($pdo, $database, $tablePrefix, $config); - - $this->configureForeignKeyConstraints(); - $this->configureBusyTimeout(); - $this->configureJournalMode(); - $this->configureSynchronous(); - } - /** * {@inheritdoc} */ @@ -39,98 +20,6 @@ public function getDriverTitle() return 'SQLite'; } - /** - * Enable or disable foreign key constraints if configured. - * - * @return void - */ - protected function configureForeignKeyConstraints(): void - { - $enableForeignKeyConstraints = $this->getConfig('foreign_key_constraints'); - - if ($enableForeignKeyConstraints === null) { - return; - } - - $schemaBuilder = $this->getSchemaBuilder(); - - try { - $enableForeignKeyConstraints - ? $schemaBuilder->enableForeignKeyConstraints() - : $schemaBuilder->disableForeignKeyConstraints(); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the busy timeout if configured. - * - * @return void - */ - protected function configureBusyTimeout(): void - { - $milliseconds = $this->getConfig('busy_timeout'); - - if ($milliseconds === null) { - return; - } - - try { - $this->getSchemaBuilder()->setBusyTimeout($milliseconds); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the journal mode if configured. - * - * @return void - */ - protected function configureJournalMode(): void - { - $mode = $this->getConfig('journal_mode'); - - if ($mode === null) { - return; - } - - try { - $this->getSchemaBuilder()->setJournalMode($mode); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the synchronous mode if configured. - * - * @return void - */ - protected function configureSynchronous(): void - { - $mode = $this->getConfig('synchronous'); - - if ($mode === null) { - return; - } - - try { - $this->getSchemaBuilder()->setSynchronous($mode); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - /** * Escape a binary value for safe SQL embedding. * @@ -162,9 +51,7 @@ protected function isUniqueConstraintError(Exception $exception) */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new QueryGrammar($this); } /** @@ -188,9 +75,7 @@ public function getSchemaBuilder() */ protected function getDefaultSchemaGrammar() { - ($grammar = new SchemaGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new SchemaGrammar($this); } /** diff --git a/src/Illuminate/Database/SQLiteDatabaseDoesNotExistException.php b/src/Illuminate/Database/SQLiteDatabaseDoesNotExistException.php index f93cfe444bbb..8ea87cf39e48 100644 --- a/src/Illuminate/Database/SQLiteDatabaseDoesNotExistException.php +++ b/src/Illuminate/Database/SQLiteDatabaseDoesNotExistException.php @@ -17,7 +17,6 @@ class SQLiteDatabaseDoesNotExistException extends InvalidArgumentException * Create a new exception instance. * * @param string $path - * @return void */ public function __construct($path) { diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index ca2eed4eb55b..b7687e839f34 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -18,18 +18,21 @@ class Blueprint use Macroable; /** - * The table the blueprint describes. - * - * @var string + * The database connection instance. */ - protected $table; + protected Connection $connection; /** - * The prefix of the table. + * The schema grammar instance. + */ + protected Grammar $grammar; + + /** + * The table the blueprint describes. * * @var string */ - protected $prefix; + protected $table; /** * The columns that should be added to the table. @@ -90,15 +93,15 @@ class Blueprint /** * Create a new schema blueprint. * + * @param \Illuminate\Database\Connection $connection * @param string $table * @param \Closure|null $callback - * @param string $prefix - * @return void */ - public function __construct($table, ?Closure $callback = null, $prefix = '') + public function __construct(Connection $connection, $table, ?Closure $callback = null) { + $this->connection = $connection; + $this->grammar = $connection->getSchemaGrammar(); $this->table = $table; - $this->prefix = $prefix; if (! is_null($callback)) { $callback($this); @@ -108,34 +111,30 @@ public function __construct($table, ?Closure $callback = null, $prefix = '') /** * Execute the blueprint against the database. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - public function build(Connection $connection, Grammar $grammar) + public function build() { - foreach ($this->toSql($connection, $grammar) as $statement) { - $connection->statement($statement); + foreach ($this->toSql() as $statement) { + $this->connection->statement($statement); } } /** * Get the raw SQL statements for the blueprint. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return array */ - public function toSql(Connection $connection, Grammar $grammar) + public function toSql() { - $this->addImpliedCommands($connection, $grammar); + $this->addImpliedCommands(); $statements = []; // Each type of command has a corresponding compiler function on the schema // grammar which is used to build the necessary SQL statements to build // the blueprint element, so we'll just call that compilers function. - $this->ensureCommandsAreValid($connection); + $this->ensureCommandsAreValid(); foreach ($this->commands as $command) { if ($command->shouldBeSkipped) { @@ -144,12 +143,12 @@ public function toSql(Connection $connection, Grammar $grammar) $method = 'compile'.ucfirst($command->name); - if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { + if (method_exists($this->grammar, $method) || $this->grammar::hasMacro($method)) { if ($this->hasState()) { $this->state->update($command); } - if (! is_null($sql = $grammar->$method($this, $command, $connection))) { + if (! is_null($sql = $this->grammar->$method($this, $command))) { $statements = array_merge($statements, (array) $sql); } } @@ -161,12 +160,11 @@ public function toSql(Connection $connection, Grammar $grammar) /** * Ensure the commands on the blueprint are valid for the connection type. * - * @param \Illuminate\Database\Connection $connection * @return void * * @throws \BadMethodCallException */ - protected function ensureCommandsAreValid(Connection $connection) + protected function ensureCommandsAreValid() { // } @@ -189,15 +187,12 @@ protected function commandsNamed(array $names) /** * Add the commands that are implied by the blueprint's state. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - protected function addImpliedCommands(Connection $connection, Grammar $grammar) + protected function addImpliedCommands() { - $this->addFluentIndexes($connection, $grammar); - - $this->addFluentCommands($connection, $grammar); + $this->addFluentIndexes(); + $this->addFluentCommands(); if (! $this->creating()) { $this->commands = array_map( @@ -207,25 +202,23 @@ protected function addImpliedCommands(Connection $connection, Grammar $grammar) $this->commands ); - $this->addAlterCommands($connection, $grammar); + $this->addAlterCommands(); } } /** * Add the index commands fluently specified on columns. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - protected function addFluentIndexes(Connection $connection, Grammar $grammar) + protected function addFluentIndexes() { foreach ($this->columns as $column) { foreach (['primary', 'unique', 'index', 'fulltext', 'fullText', 'spatialIndex'] as $index) { // If the column is supposed to be changed to an auto increment column and // the specified index is primary, there is no need to add a command on // MySQL, as it will be handled during the column definition instead. - if ($index === 'primary' && $column->autoIncrement && $column->change && $grammar instanceof MySqlGrammar) { + if ($index === 'primary' && $column->autoIncrement && $column->change && $this->grammar instanceof MySqlGrammar) { continue 2; } @@ -265,14 +258,12 @@ protected function addFluentIndexes(Connection $connection, Grammar $grammar) /** * Add the fluent commands specified on any columns. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - public function addFluentCommands(Connection $connection, Grammar $grammar) + public function addFluentCommands() { foreach ($this->columns as $column) { - foreach ($grammar->getFluentCommands() as $commandName) { + foreach ($this->grammar->getFluentCommands() as $commandName) { $this->addCommand($commandName, compact('column')); } } @@ -281,17 +272,15 @@ public function addFluentCommands(Connection $connection, Grammar $grammar) /** * Add the alter commands if whenever needed. * - * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - public function addAlterCommands(Connection $connection, Grammar $grammar) + public function addAlterCommands() { - if (! $grammar instanceof SQLiteGrammar) { + if (! $this->grammar instanceof SQLiteGrammar) { return; } - $alterCommands = $grammar->getAlterCommands($connection); + $alterCommands = $this->grammar->getAlterCommands(); [$commands, $lastCommandWasAlter, $hasAlterCommand] = [ [], false, false, @@ -314,7 +303,7 @@ public function addAlterCommands(Connection $connection, Grammar $grammar) } if ($hasAlterCommand) { - $this->state = new BlueprintState($this, $connection, $grammar); + $this->state = new BlueprintState($this, $this->connection); } $this->commands = $commands; @@ -532,7 +521,7 @@ public function dropForeignIdFor($model, $column = null) $model = new $model; } - return $this->dropForeign([$column ?: $model->getForeignKey()]); + return $this->dropColumn($column ?: $model->getForeignKey()); } /** @@ -1169,8 +1158,10 @@ public function date($column) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function dateTime($column, $precision = 0) + public function dateTime($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('dateTime', $column, compact('precision')); } @@ -1181,8 +1172,10 @@ public function dateTime($column, $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function dateTimeTz($column, $precision = 0) + public function dateTimeTz($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('dateTimeTz', $column, compact('precision')); } @@ -1193,8 +1186,10 @@ public function dateTimeTz($column, $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function time($column, $precision = 0) + public function time($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('time', $column, compact('precision')); } @@ -1205,8 +1200,10 @@ public function time($column, $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function timeTz($column, $precision = 0) + public function timeTz($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('timeTz', $column, compact('precision')); } @@ -1217,8 +1214,10 @@ public function timeTz($column, $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function timestamp($column, $precision = 0) + public function timestamp($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('timestamp', $column, compact('precision')); } @@ -1229,8 +1228,10 @@ public function timestamp($column, $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function timestampTz($column, $precision = 0) + public function timestampTz($column, $precision = null) { + $precision ??= $this->defaultTimePrecision(); + return $this->addColumn('timestampTz', $column, compact('precision')); } @@ -1240,7 +1241,7 @@ public function timestampTz($column, $precision = 0) * @param int|null $precision * @return void */ - public function timestamps($precision = 0) + public function timestamps($precision = null) { $this->timestamp('created_at', $precision)->nullable(); @@ -1255,7 +1256,7 @@ public function timestamps($precision = 0) * @param int|null $precision * @return void */ - public function nullableTimestamps($precision = 0) + public function nullableTimestamps($precision = null) { $this->timestamps($precision); } @@ -1266,7 +1267,7 @@ public function nullableTimestamps($precision = 0) * @param int|null $precision * @return void */ - public function timestampsTz($precision = 0) + public function timestampsTz($precision = null) { $this->timestampTz('created_at', $precision)->nullable(); @@ -1279,7 +1280,7 @@ public function timestampsTz($precision = 0) * @param int|null $precision * @return void */ - public function datetimes($precision = 0) + public function datetimes($precision = null) { $this->datetime('created_at', $precision)->nullable(); @@ -1293,7 +1294,7 @@ public function datetimes($precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function softDeletes($column = 'deleted_at', $precision = 0) + public function softDeletes($column = 'deleted_at', $precision = null) { return $this->timestamp($column, $precision)->nullable(); } @@ -1305,7 +1306,7 @@ public function softDeletes($column = 'deleted_at', $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function softDeletesTz($column = 'deleted_at', $precision = 0) + public function softDeletesTz($column = 'deleted_at', $precision = null) { return $this->timestampTz($column, $precision)->nullable(); } @@ -1317,7 +1318,7 @@ public function softDeletesTz($column = 'deleted_at', $precision = 0) * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function softDeletesDatetime($column = 'deleted_at', $precision = 0) + public function softDeletesDatetime($column = 'deleted_at', $precision = null) { return $this->datetime($column, $precision)->nullable(); } @@ -1692,9 +1693,13 @@ protected function dropIndexCommand($command, $type, $index) */ protected function createIndexName($type, array $columns) { - $table = str_contains($this->table, '.') - ? substr_replace($this->table, '.'.$this->prefix, strrpos($this->table, '.'), 1) - : $this->prefix.$this->table; + $table = $this->table; + + if ($this->connection->getConfig('prefix_indexes')) { + $table = str_contains($this->table, '.') + ? substr_replace($this->table, '.'.$this->connection->getTablePrefix(), strrpos($this->table, '.'), 1) + : $this->connection->getTablePrefix().$this->table; + } $index = strtolower($table.'_'.implode('_', $columns).'_'.$type); @@ -1813,11 +1818,13 @@ public function getTable() /** * Get the table prefix. * + * @deprecated Use DB::getTablePrefix() + * * @return string */ public function getPrefix() { - return $this->prefix; + return $this->connection->getTablePrefix(); } /** @@ -1843,7 +1850,6 @@ public function getCommands() /** * Determine if the blueprint has state. * - * @param mixed $name * @return bool */ private function hasState(): bool @@ -1886,4 +1892,12 @@ public function getChangedColumns() return (bool) $column->change; }); } + + /** + * Get the default time precision. + */ + protected function defaultTimePrecision(): ?int + { + return $this->connection->getSchemaBuilder()::$defaultTimePrecision; + } } diff --git a/src/Illuminate/Database/Schema/BlueprintState.php b/src/Illuminate/Database/Schema/BlueprintState.php index 4a2ea127a97a..a4ad1149d479 100644 --- a/src/Illuminate/Database/Schema/BlueprintState.php +++ b/src/Illuminate/Database/Schema/BlueprintState.php @@ -4,7 +4,6 @@ use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; -use Illuminate\Database\Schema\Grammars\Grammar; use Illuminate\Support\Collection; use Illuminate\Support\Fluent; use Illuminate\Support\Str; @@ -25,13 +24,6 @@ class BlueprintState */ protected $connection; - /** - * The grammar instance. - * - * @var \Illuminate\Database\Schema\Grammars\Grammar - */ - protected $grammar; - /** * The columns. * @@ -65,14 +57,11 @@ class BlueprintState * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Database\Connection $connection - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar - * @return void */ - public function __construct(Blueprint $blueprint, Connection $connection, Grammar $grammar) + public function __construct(Blueprint $blueprint, Connection $connection) { $this->blueprint = $blueprint; $this->connection = $connection; - $this->grammar = $grammar; $schema = $connection->getSchemaBuilder(); $table = $blueprint->getTable(); @@ -87,9 +76,11 @@ public function __construct(Blueprint $blueprint, Connection $connection, Gramma 'collation' => $column['collation'], 'comment' => $column['comment'], 'virtualAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'virtual' - ? $column['generation']['expression'] : null, + ? $column['generation']['expression'] + : null, 'storedAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'stored' - ? $column['generation']['expression'] : null, + ? $column['generation']['expression'] + : null, ]))->all(); [$primary, $indexes] = (new Collection($schema->getIndexes($table)))->map(fn ($index) => new IndexDefinition([ diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 9af11e2e0836..d70cb9314231 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -9,6 +9,9 @@ use InvalidArgumentException; use LogicException; +/** + * @template TResolver of \Closure(string, \Closure, string): \Illuminate\Database\Schema\Blueprint + */ class Builder { use Macroable; @@ -30,9 +33,9 @@ class Builder /** * The Blueprint resolver callback. * - * @var \Closure + * @var TResolver|null */ - protected $resolver; + protected static $resolver = null; /** * The default string length for migrations. @@ -41,6 +44,11 @@ class Builder */ public static $defaultStringLength = 255; + /** + * The default time precision for migrations. + */ + public static ?int $defaultTimePrecision = 0; + /** * The default relationship morph key type. * @@ -52,7 +60,6 @@ class Builder * Create a new database Schema manager. * * @param \Illuminate\Database\Connection $connection - * @return void */ public function __construct(Connection $connection) { @@ -71,6 +78,14 @@ public static function defaultStringLength($length) static::$defaultStringLength = $length; } + /** + * Set the default time precision for migrations. + */ + public static function defaultTimePrecision(?int $precision): void + { + static::$defaultTimePrecision = $precision; + } + /** * Set the default morph key type for migrations. * @@ -113,12 +128,12 @@ public static function morphUsingUlids() * * @param string $name * @return bool - * - * @throws \LogicException */ public function createDatabase($name) { - throw new LogicException('This database driver does not support creating databases.'); + return $this->connection->statement( + $this->grammar->compileCreateDatabase($name) + ); } /** @@ -126,12 +141,24 @@ public function createDatabase($name) * * @param string $name * @return bool - * - * @throws \LogicException */ public function dropDatabaseIfExists($name) { - throw new LogicException('This database driver does not support dropping databases.'); + return $this->connection->statement( + $this->grammar->compileDropDatabaseIfExists($name) + ); + } + + /** + * Get the schemas that belong to the connection. + * + * @return list + */ + public function getSchemas() + { + return $this->connection->getPostProcessor()->processSchemas( + $this->connection->selectFromWriteConnection($this->grammar->compileSchemas()) + ); } /** @@ -142,9 +169,15 @@ public function dropDatabaseIfExists($name) */ public function hasTable($table) { + [$schema, $table] = $this->parseSchemaAndTable($table); + $table = $this->connection->getTablePrefix().$table; - foreach ($this->getTables() as $value) { + if ($sql = $this->grammar->compileTableExists($schema, $table)) { + return (bool) $this->connection->scalar($sql); + } + + foreach ($this->getTables($schema ?? $this->getCurrentSchemaName()) as $value) { if (strtolower($table) === strtolower($value['name'])) { return true; } @@ -161,9 +194,11 @@ public function hasTable($table) */ public function hasView($view) { + [$schema, $view] = $this->parseSchemaAndTable($view); + $view = $this->connection->getTablePrefix().$view; - foreach ($this->getViews() as $value) { + foreach ($this->getViews($schema ?? $this->getCurrentSchemaName()) as $value) { if (strtolower($view) === strtolower($value['name'])) { return true; } @@ -173,47 +208,57 @@ public function hasView($view) } /** - * Get the tables that belong to the database. + * Get the tables that belong to the connection. * - * @return array + * @param string|string[]|null $schema + * @return list */ - public function getTables() + public function getTables($schema = null) { return $this->connection->getPostProcessor()->processTables( - $this->connection->selectFromWriteConnection($this->grammar->compileTables()) + $this->connection->selectFromWriteConnection($this->grammar->compileTables($schema)) ); } /** - * Get the names of the tables that belong to the database. + * Get the names of the tables that belong to the connection. * - * @return array + * @param string|string[]|null $schema + * @param bool $schemaQualified + * @return list */ - public function getTableListing() + public function getTableListing($schema = null, $schemaQualified = true) { - return array_column($this->getTables(), 'name'); + return array_column( + $this->getTables($schema), + $schemaQualified ? 'schema_qualified_name' : 'name' + ); } /** - * Get the views that belong to the database. + * Get the views that belong to the connection. * - * @return array + * @param string|string[]|null $schema + * @return list */ - public function getViews() + public function getViews($schema = null) { return $this->connection->getPostProcessor()->processViews( - $this->connection->selectFromWriteConnection($this->grammar->compileViews()) + $this->connection->selectFromWriteConnection($this->grammar->compileViews($schema)) ); } /** - * Get the user-defined types that belong to the database. + * Get the user-defined types that belong to the connection. * - * @return array + * @param string|string[]|null $schema + * @return list */ - public function getTypes() + public function getTypes($schema = null) { - throw new LogicException('This database driver does not support user-defined types.'); + return $this->connection->getPostProcessor()->processTypes( + $this->connection->selectFromWriteConnection($this->grammar->compileTypes($schema)) + ); } /** @@ -226,7 +271,7 @@ public function getTypes() public function hasColumn($table, $column) { return in_array( - strtolower($column), array_map('strtolower', $this->getColumnListing($table)) + strtolower($column), array_map(strtolower(...), $this->getColumnListing($table)) ); } @@ -234,12 +279,12 @@ public function hasColumn($table, $column) * Determine if the given table has given columns. * * @param string $table - * @param array $columns + * @param array $columns * @return bool */ public function hasColumns($table, array $columns) { - $tableColumns = array_map('strtolower', $this->getColumnListing($table)); + $tableColumns = array_map(strtolower(...), $this->getColumnListing($table)); foreach ($columns as $column) { if (! in_array(strtolower($column), $tableColumns)) { @@ -305,7 +350,7 @@ public function getColumnType($table, $column, $fullDefinition = false) * Get the column listing for a given table. * * @param string $table - * @return array + * @return list */ public function getColumnListing($table) { @@ -316,14 +361,18 @@ public function getColumnListing($table) * Get the columns for a given table. * * @param string $table - * @return array + * @return list */ public function getColumns($table) { + [$schema, $table] = $this->parseSchemaAndTable($table); + $table = $this->connection->getTablePrefix().$table; return $this->connection->getPostProcessor()->processColumns( - $this->connection->selectFromWriteConnection($this->grammar->compileColumns($table)) + $this->connection->selectFromWriteConnection( + $this->grammar->compileColumns($schema, $table) + ) ); } @@ -331,14 +380,18 @@ public function getColumns($table) * Get the indexes for a given table. * * @param string $table - * @return array + * @return list, type: string, unique: bool, primary: bool}> */ public function getIndexes($table) { + [$schema, $table] = $this->parseSchemaAndTable($table); + $table = $this->connection->getTablePrefix().$table; return $this->connection->getPostProcessor()->processIndexes( - $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($table)) + $this->connection->selectFromWriteConnection( + $this->grammar->compileIndexes($schema, $table) + ) ); } @@ -346,7 +399,7 @@ public function getIndexes($table) * Get the names of the indexes for a given table. * * @param string $table - * @return array + * @return list */ public function getIndexListing($table) { @@ -387,10 +440,14 @@ public function hasIndex($table, $index, $type = null) */ 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($table)) + $this->connection->selectFromWriteConnection( + $this->grammar->compileForeignKeys($schema, $table) + ) ); } @@ -452,7 +509,7 @@ public function dropIfExists($table) * Drop columns from a table schema. * * @param string $table - * @param string|array $columns + * @param string|array $columns * @return void */ public function dropColumns($table, $columns) @@ -561,7 +618,7 @@ public function withoutForeignKeyConstraints(Closure $callback) */ protected function build(Blueprint $blueprint) { - $blueprint->build($this->connection, $this->grammar); + $blueprint->build(); } /** @@ -573,48 +630,82 @@ protected function build(Blueprint $blueprint) */ protected function createBlueprint($table, ?Closure $callback = null) { - $prefix = $this->connection->getConfig('prefix_indexes') - ? $this->connection->getConfig('prefix') - : ''; + $connection = $this->connection; - if (isset($this->resolver)) { - return call_user_func($this->resolver, $table, $callback, $prefix); + if (static::$resolver !== null) { + return call_user_func(static::$resolver, $connection, $table, $callback); } - return Container::getInstance()->make(Blueprint::class, compact('table', 'callback', 'prefix')); + return Container::getInstance()->make(Blueprint::class, compact('connection', 'table', 'callback')); } /** - * Get the database connection instance. + * Get the names of the current schemas for the connection. * - * @return \Illuminate\Database\Connection + * @return string[]|null */ - public function getConnection() + public function getCurrentSchemaListing() { - return $this->connection; + return null; } /** - * Set the database connection instance. + * Get the default schema name for the connection. * - * @param \Illuminate\Database\Connection $connection - * @return $this + * @return string|null */ - public function setConnection(Connection $connection) + public function getCurrentSchemaName() { - $this->connection = $connection; + return $this->getCurrentSchemaListing()[0] ?? null; + } + + /** + * Parse the given database object reference and extract the schema and table. + * + * @param string $reference + * @param string|bool|null $withDefaultSchema + * @return array + */ + public function parseSchemaAndTable($reference, $withDefaultSchema = null) + { + $segments = explode('.', $reference); + + if (count($segments) > 2) { + throw new InvalidArgumentException( + "Using three-part references is not supported, you may use `Schema::connection('{$segments[0]}')` instead." + ); + } + + $table = $segments[1] ?? $segments[0]; + + $schema = match (true) { + isset($segments[1]) => $segments[0], + is_string($withDefaultSchema) => $withDefaultSchema, + $withDefaultSchema => $this->getCurrentSchemaName(), + default => null, + }; + + return [$schema, $table]; + } - return $this; + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; } /** * Set the Schema Blueprint resolver callback. * - * @param \Closure $resolver + * @param TResolver|null $resolver * @return void */ - public function blueprintResolver(Closure $resolver) + public function blueprintResolver(?Closure $resolver) { - $this->resolver = $resolver; + static::$resolver = $resolver; } } diff --git a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php index d846f88ea497..c7f66d19bb96 100644 --- a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php @@ -18,7 +18,6 @@ class ForeignIdColumnDefinition extends ColumnDefinition * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param array $attributes - * @return void */ public function __construct(Blueprint $blueprint, $attributes = []) { diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index b21fcf23b65d..ed683d256d30 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -5,11 +5,9 @@ use BackedEnum; use Illuminate\Contracts\Database\Query\Expression; use Illuminate\Database\Concerns\CompilesJsonPaths; -use Illuminate\Database\Connection; use Illuminate\Database\Grammar as BaseGrammar; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; -use LogicException; use RuntimeException; abstract class Grammar extends BaseGrammar @@ -41,27 +39,129 @@ abstract class Grammar extends BaseGrammar * Compile a create database command. * * @param string $name - * @param \Illuminate\Database\Connection $connection - * @return void - * - * @throws \LogicException + * @return string */ - public function compileCreateDatabase($name, $connection) + public function compileCreateDatabase($name) { - throw new LogicException('This database driver does not support creating databases.'); + return sprintf('create database %s', + $this->wrapValue($name), + ); } /** * Compile a drop database if exists command. * * @param string $name - * @return void - * - * @throws \LogicException + * @return string */ public function compileDropDatabaseIfExists($name) { - throw new LogicException('This database driver does not support dropping databases.'); + return sprintf('drop database if exists %s', + $this->wrapValue($name) + ); + } + + /** + * Compile the query to determine the schemas. + * + * @return string + */ + public function compileSchemas() + { + throw new RuntimeException('This database driver does not support retrieving schemas.'); + } + + /** + * Compile the query to determine if the given table exists. + * + * @param string|null $schema + * @param string $table + * @return string|null + */ + public function compileTableExists($schema, $table) + { + // + } + + /** + * Compile the query to determine the tables. + * + * @param string|string[]|null $schema + * @return string + * + * @throws \RuntimeException + */ + public function compileTables($schema) + { + throw new RuntimeException('This database driver does not support retrieving tables.'); + } + + /** + * Compile the query to determine the views. + * + * @param string|string[]|null $schema + * @return string + * + * @throws \RuntimeException + */ + public function compileViews($schema) + { + throw new RuntimeException('This database driver does not support retrieving views.'); + } + + /** + * Compile the query to determine the user-defined types. + * + * @param string|string[]|null $schema + * @return string + * + * @throws \RuntimeException + */ + public function compileTypes($schema) + { + throw new RuntimeException('This database driver does not support retrieving user-defined types.'); + } + + /** + * Compile the query to determine the columns. + * + * @param string|null $schema + * @param string $table + * @return string + * + * @throws \RuntimeException + */ + public function compileColumns($schema, $table) + { + throw new RuntimeException('This database driver does not support retrieving columns.'); + } + + /** + * Compile the query to determine the indexes. + * + * @param string|null $schema + * @param string $table + * @return string + * + * @throws \RuntimeException + */ + public function compileIndexes($schema, $table) + { + throw new RuntimeException('This database driver does not support retrieving indexes.'); + } + + /** + * Compile the query to determine the foreign keys. + * + * @param string|null $schema + * @param string $table + * @return string + * + * @throws \RuntimeException + */ + public function compileForeignKeys($schema, $table) + { + throw new RuntimeException('This database driver does not support retrieving foreign keys.'); } /** @@ -69,10 +169,9 @@ public function compileDropDatabaseIfExists($name) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string + * @return list|string */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileRenameColumn(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s rename column %s to %s', $this->wrapTable($blueprint), @@ -86,14 +185,13 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string + * @return list|string * * @throws \RuntimeException */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileChange(Blueprint $blueprint, Fluent $command) { - throw new LogicException('This database driver does not support modifying columns.'); + throw new RuntimeException('This database driver does not support modifying columns.'); } /** @@ -329,8 +427,8 @@ protected function hasCommand(Blueprint $blueprint, $name) * Add a prefix to an array of values. * * @param string $prefix - * @param array $values - * @return array + * @param array $values + * @return array */ public function prefixArray($prefix, array $values) { @@ -343,12 +441,14 @@ public function prefixArray($prefix, array $values) * Wrap a table in keyword identifiers. * * @param mixed $table + * @param string|null $prefix * @return string */ - public function wrapTable($table) + public function wrapTable($table, $prefix = null) { return parent::wrapTable( - $table instanceof Blueprint ? $table->getTable() : $table + $table instanceof Blueprint ? $table->getTable() : $table, + $prefix ); } @@ -382,8 +482,8 @@ protected function getDefaultValue($value) } return is_bool($value) - ? "'".(int) $value."'" - : "'".(string) $value."'"; + ? "'".(int) $value."'" + : "'".(string) $value."'"; } /** diff --git a/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php b/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php index 2996406a27fb..3cb682626587 100755 --- a/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php @@ -2,27 +2,19 @@ namespace Illuminate\Database\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; class MariaDbGrammar extends MySqlGrammar { - /** - * Compile a rename column command. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileRenameColumn(Blueprint $blueprint, Fluent $command) { - if (version_compare($connection->getServerVersion(), '10.5.2', '<')) { - return $this->compileLegacyRenameColumn($blueprint, $command, $connection); + if (version_compare($this->connection->getServerVersion(), '10.5.2', '<')) { + return $this->compileLegacyRenameColumn($blueprint, $command); } - return parent::compileRenameColumn($blueprint, $command, $connection); + return parent::compileRenameColumn($blueprint, $command); } /** @@ -33,6 +25,10 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ protected function typeUuid(Fluent $column) { + if (version_compare($this->connection->getServerVersion(), '10.7.0', '<')) { + return 'char(36)'; + } + return 'uuid'; } diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 456210c08da7..db992eac43ab 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\ColumnDefinition; @@ -40,56 +39,48 @@ class MySqlGrammar extends Grammar * Compile a create database command. * * @param string $name - * @param \Illuminate\Database\Connection $connection * @return string */ - public function compileCreateDatabase($name, $connection) + public function compileCreateDatabase($name) { - $charset = $connection->getConfig('charset'); - $collation = $connection->getConfig('collation'); + $sql = parent::compileCreateDatabase($name); - if (! $charset || ! $collation) { - return sprintf( - 'create database %s', - $this->wrapValue($name), - ); + if ($charset = $this->connection->getConfig('charset')) { + $sql .= sprintf(' default character set %s', $this->wrapValue($charset)); } - return sprintf( - 'create database %s default character set %s default collate %s', - $this->wrapValue($name), - $this->wrapValue($charset), - $this->wrapValue($collation), - ); + if ($collation = $this->connection->getConfig('collation')) { + $sql .= sprintf(' default collate %s', $this->wrapValue($collation)); + } + + return $sql; } /** - * Compile a drop database if exists command. + * Compile the query to determine the schemas. * - * @param string $name * @return string */ - public function compileDropDatabaseIfExists($name) + public function compileSchemas() { - return sprintf( - 'drop database if exists %s', - $this->wrapValue($name) - ); + return 'select schema_name as name, schema_name = schema() as `default` from information_schema.schemata where ' + .$this->compileSchemaWhereClause(null, 'schema_name') + .' order by schema_name'; } /** * Compile the query to determine if the given table exists. * - * @param string $database + * @param string|null $schema * @param string $table * @return string */ - public function compileTableExists($database, $table) + public function compileTableExists($schema, $table) { return sprintf( 'select exists (select 1 from information_schema.tables where ' ."table_schema = %s and table_name = %s and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as `exists`", - $this->quoteString($database), + $schema ? $this->quoteString($schema) : 'schema()', $this->quoteString($table) ); } @@ -97,44 +88,59 @@ public function compileTableExists($database, $table) /** * Compile the query to determine the tables. * - * @param string $database + * @param string|string[]|null $schema * @return string */ - public function compileTables($database) + public function compileTables($schema) { return sprintf( - 'select table_name as `name`, (data_length + index_length) as `size`, ' + 'select table_name as `name`, table_schema as `schema`, (data_length + index_length) as `size`, ' .'table_comment as `comment`, engine as `engine`, table_collation as `collation` ' - ."from information_schema.tables where table_schema = %s and table_type in ('BASE TABLE', 'SYSTEM VERSIONED') " - .'order by table_name', - $this->quoteString($database) + ."from information_schema.tables where table_type in ('BASE TABLE', 'SYSTEM VERSIONED') and " + .$this->compileSchemaWhereClause($schema, 'table_schema') + .' order by table_schema, table_name', + $this->quoteString($schema) ); } /** * Compile the query to determine the views. * - * @param string $database + * @param string|string[]|null $schema * @return string */ - public function compileViews($database) + public function compileViews($schema) { - return sprintf( - 'select table_name as `name`, view_definition as `definition` ' - .'from information_schema.views where table_schema = %s ' - .'order by table_name', - $this->quoteString($database) - ); + return 'select table_name as `name`, table_schema as `schema`, view_definition as `definition` ' + .'from information_schema.views where ' + .$this->compileSchemaWhereClause($schema, 'table_schema') + .' order by table_schema, table_name'; + } + + /** + * Compile the query to compare the schema. + * + * @param string|string[]|null $schema + * @param string $column + * @return string + */ + protected function compileSchemaWhereClause($schema, $column) + { + return $column.(match (true) { + ! empty($schema) && is_array($schema) => ' in ('.$this->quoteString($schema).')', + ! empty($schema) => ' = '.$this->quoteString($schema), + default => " not in ('information_schema', 'mysql', 'ndbinfo', 'performance_schema', 'sys')", + }); } /** * Compile the query to determine the columns. * - * @param string $database + * @param string|null $schema * @param string $table * @return string */ - public function compileColumns($database, $table) + public function compileColumns($schema, $table) { return sprintf( 'select column_name as `name`, data_type as `type_name`, column_type as `type`, ' @@ -143,7 +149,7 @@ public function compileColumns($database, $table) .'generation_expression as `expression`, extra as `extra` ' .'from information_schema.columns where table_schema = %s and table_name = %s ' .'order by ordinal_position asc', - $this->quoteString($database), + $schema ? $this->quoteString($schema) : 'schema()', $this->quoteString($table) ); } @@ -151,18 +157,18 @@ public function compileColumns($database, $table) /** * Compile the query to determine the indexes. * - * @param string $database + * @param string|null $schema * @param string $table * @return string */ - public function compileIndexes($database, $table) + public function compileIndexes($schema, $table) { return sprintf( 'select index_name as `name`, group_concat(column_name order by seq_in_index) as `columns`, ' .'index_type as `type`, not non_unique as `unique` ' .'from information_schema.statistics where table_schema = %s and table_name = %s ' .'group by index_name, index_type, non_unique', - $this->quoteString($database), + $schema ? $this->quoteString($schema) : 'schema()', $this->quoteString($table) ); } @@ -170,11 +176,11 @@ public function compileIndexes($database, $table) /** * Compile the query to determine the foreign keys. * - * @param string $database + * @param string|null $schema * @param string $table * @return string */ - public function compileForeignKeys($database, $table) + public function compileForeignKeys($schema, $table) { return sprintf( 'select kc.constraint_name as `name`, ' @@ -188,7 +194,7 @@ public function compileForeignKeys($database, $table) .'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), + $schema ? $this->quoteString($schema) : 'schema()', $this->quoteString($table) ); } @@ -198,26 +204,25 @@ public function compileForeignKeys($database, $table) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection * @return string */ - public function compileCreate(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileCreate(Blueprint $blueprint, Fluent $command) { $sql = $this->compileCreateTable( - $blueprint, $command, $connection + $blueprint, $command ); // Once we have the primary SQL, we can add the encoding option to the SQL for // the table. Then, we can check if a storage engine has been supplied for // the table. If so, we will add the engine declaration to the SQL query. $sql = $this->compileCreateEncoding( - $sql, $connection, $blueprint + $sql, $blueprint ); // Finally, we will append the engine configuration onto this SQL statement as // the final thing we do before returning this finished SQL. Once this gets // added the query will be ready to execute against the real connections. - return $this->compileCreateEngine($sql, $connection, $blueprint); + return $this->compileCreateEngine($sql, $blueprint); } /** @@ -225,10 +230,9 @@ public function compileCreate(Blueprint $blueprint, Fluent $command, Connection * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection * @return string */ - protected function compileCreateTable($blueprint, $command, $connection) + protected function compileCreateTable($blueprint, $command) { $tableStructure = $this->getColumns($blueprint); @@ -253,18 +257,17 @@ protected function compileCreateTable($blueprint, $command, $connection) * Append the character set specifications to a command. * * @param string $sql - * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string */ - protected function compileCreateEncoding($sql, Connection $connection, Blueprint $blueprint) + protected function compileCreateEncoding($sql, Blueprint $blueprint) { // First we will set the character set if one has been set on either the create // blueprint itself or on the root configuration for the connection that the // table is being created on. We will add these to the create table query. if (isset($blueprint->charset)) { $sql .= ' default character set '.$blueprint->charset; - } elseif (! is_null($charset = $connection->getConfig('charset'))) { + } elseif (! is_null($charset = $this->connection->getConfig('charset'))) { $sql .= ' default character set '.$charset; } @@ -273,7 +276,7 @@ protected function compileCreateEncoding($sql, Connection $connection, Blueprint // connection that the query is targeting. We'll add it to this SQL query. if (isset($blueprint->collation)) { $sql .= " collate '{$blueprint->collation}'"; - } elseif (! is_null($collation = $connection->getConfig('collation'))) { + } elseif (! is_null($collation = $this->connection->getConfig('collation'))) { $sql .= " collate '{$collation}'"; } @@ -284,15 +287,14 @@ protected function compileCreateEncoding($sql, Connection $connection, Blueprint * Append the engine specifications to a command. * * @param string $sql - * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string */ - protected function compileCreateEngine($sql, Connection $connection, Blueprint $blueprint) + protected function compileCreateEngine($sql, Blueprint $blueprint) { if (isset($blueprint->engine)) { return $sql.' engine = '.$blueprint->engine; - } elseif (! is_null($engine = $connection->getConfig('engine'))) { + } elseif (! is_null($engine = $this->connection->getConfig('engine'))) { return $sql.' engine = '.$engine; } @@ -329,24 +331,18 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent } } - /** - * Compile a rename column command. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileRenameColumn(Blueprint $blueprint, Fluent $command) { - $version = $connection->getServerVersion(); + $isMaria = $this->connection->isMaria(); + $version = $this->connection->getServerVersion(); - if (($connection->isMaria() && version_compare($version, '10.5.2', '<')) || - (! $connection->isMaria() && version_compare($version, '8.0.3', '<'))) { - return $this->compileLegacyRenameColumn($blueprint, $command, $connection); + if (($isMaria && version_compare($version, '10.5.2', '<')) || + (! $isMaria && version_compare($version, '8.0.3', '<'))) { + return $this->compileLegacyRenameColumn($blueprint, $command); } - return parent::compileRenameColumn($blueprint, $command, $connection); + return parent::compileRenameColumn($blueprint, $command); } /** @@ -354,12 +350,11 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection * @return string */ - protected function compileLegacyRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + protected function compileLegacyRenameColumn(Blueprint $blueprint, Fluent $command) { - $column = (new Collection($connection->getSchemaBuilder()->getColumns($blueprint->getTable()))) + $column = (new Collection($this->connection->getSchemaBuilder()->getColumns($blueprint->getTable()))) ->firstWhere('name', $command->from); $modifiers = $this->addModifiers($column['type'], $blueprint, new ColumnDefinition([ @@ -380,9 +375,11 @@ protected function compileLegacyRenameColumn(Blueprint $blueprint, Fluent $comma 'collation' => $column['collation'], 'comment' => $column['comment'], 'virtualAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'virtual' - ? $column['generation']['expression'] : null, + ? $column['generation']['expression'] + : null, 'storedAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'stored' - ? $column['generation']['expression'] : null, + ? $column['generation']['expression'] + : null, ])); return sprintf('alter table %s change %s %s %s', @@ -393,17 +390,8 @@ protected function compileLegacyRenameColumn(Blueprint $blueprint, Fluent $comma ); } - /** - * Compile a change column command into a series of SQL statements. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - * - * @throws \RuntimeException - */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileChange(Blueprint $blueprint, Fluent $command) { $column = $command->column; @@ -650,23 +638,23 @@ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) /** * Compile the SQL needed to drop all tables. * - * @param array $tables + * @param array $tables * @return string */ public function compileDropAllTables($tables) { - return 'drop table '.implode(',', $this->wrapArray($tables)); + return 'drop table '.implode(', ', $this->escapeNames($tables)); } /** * Compile the SQL needed to drop all views. * - * @param array $views + * @param array $views * @return string */ public function compileDropAllViews($views) { - return 'drop view '.implode(',', $this->wrapArray($views)); + return 'drop view '.implode(', ', $this->escapeNames($views)); } /** @@ -704,6 +692,20 @@ public function compileTableComment(Blueprint $blueprint, Fluent $command) ); } + /** + * Quote-escape the given tables, views, or types. + * + * @param array $names + * @return array + */ + public function escapeNames($names) + { + return array_map( + fn ($name) => (new Collection(explode('.', $name)))->map($this->wrapValue(...))->implode('.'), + $names + ); + } + /** * Create the column definition for a char type. * @@ -1090,7 +1092,7 @@ protected function typeGeometry(Fluent $column) return sprintf('%s%s', $subtype ?? 'geometry', match (true) { - $column->srid && $this->connection?->isMaria() => ' ref_system_id='.$column->srid, + $column->srid && $this->connection->isMaria() => ' ref_system_id='.$column->srid, (bool) $column->srid => ' srid '.$column->srid, default => '', } diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index c9a579fdad2e..a40f7a62e153 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Collection; @@ -43,36 +42,35 @@ class PostgresGrammar extends Grammar * Compile a create database command. * * @param string $name - * @param \Illuminate\Database\Connection $connection * @return string */ - public function compileCreateDatabase($name, $connection) + public function compileCreateDatabase($name) { - return sprintf( - 'create database %s encoding %s', - $this->wrapValue($name), - $this->wrapValue($connection->getConfig('charset')), - ); + $sql = parent::compileCreateDatabase($name); + + if ($charset = $this->connection->getConfig('charset')) { + $sql .= sprintf(' encoding %s', $this->wrapValue($charset)); + } + + return $sql; } /** - * Compile a drop database if exists command. + * Compile the query to determine the schemas. * - * @param string $name * @return string */ - public function compileDropDatabaseIfExists($name) + public function compileSchemas() { - return sprintf( - 'drop database if exists %s', - $this->wrapValue($name) - ); + return 'select nspname as name, nspname = current_schema() as "default" from pg_namespace where ' + .$this->compileSchemaWhereClause(null, 'nspname') + .' order by nspname'; } /** * Compile the query to determine if the given table exists. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -81,7 +79,7 @@ public function compileTableExists($schema, $table) return sprintf( 'select exists (select 1 from pg_class c, pg_namespace n where ' ."n.nspname = %s and c.relname = %s and c.relkind in ('r', 'p') and n.oid = c.relnamespace)", - $this->quoteString($schema), + $schema ? $this->quoteString($schema) : 'current_schema()', $this->quoteString($table) ); } @@ -89,32 +87,38 @@ public function compileTableExists($schema, $table) /** * Compile the query to determine the tables. * + * @param string|string[]|null $schema * @return string */ - public function compileTables() + public function compileTables($schema) { 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 in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " - .'order by c.relname'; + ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and " + .$this->compileSchemaWhereClause($schema, 'n.nspname') + .' order by n.nspname, c.relname'; } /** * Compile the query to determine the views. * + * @param string|string[]|null $schema * @return string */ - public function compileViews() + public function compileViews($schema) { - return "select viewname as name, schemaname as schema, definition from pg_views where schemaname not in ('pg_catalog', 'information_schema') order by viewname"; + return 'select viewname as name, schemaname as schema, definition from pg_views where ' + .$this->compileSchemaWhereClause($schema, 'schemaname') + .' order by schemaname, viewname'; } /** * Compile the query to determine the user-defined types. * + * @param string|string[]|null $schema * @return string */ - public function compileTypes() + public function compileTypes($schema) { 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 " @@ -123,14 +127,30 @@ public function compileTypes() .'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 not exists (select 1 from pg_depend d where d.objid in (t.oid, t.typelem) and d.deptype = 'e') " - ."and n.nspname not in ('pg_catalog', 'information_schema')"; + ."and not exists (select 1 from pg_depend d where d.objid in (t.oid, t.typelem) and d.deptype = 'e') and " + .$this->compileSchemaWhereClause($schema, 'n.nspname'); + } + + /** + * Compile the query to compare the schema. + * + * @param string|string[]|null $schema + * @param string $column + * @return string + */ + protected function compileSchemaWhereClause($schema, $column) + { + return $column.(match (true) { + ! empty($schema) && is_array($schema) => ' in ('.$this->quoteString($schema).')', + ! empty($schema) => ' = '.$this->quoteString($schema), + default => " <> 'information_schema' and $column not like 'pg\_%'", + }); } /** * Compile the query to determine the columns. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -141,20 +161,20 @@ public function compileColumns($schema, $table) .'(select tc.collcollate from pg_catalog.pg_collation tc where tc.oid = a.attcollation) as collation, ' .'not a.attnotnull as nullable, ' .'(select pg_get_expr(adbin, adrelid) from pg_attrdef where c.oid = pg_attrdef.adrelid and pg_attrdef.adnum = a.attnum) as default, ' - .(version_compare($this->connection?->getServerVersion(), '12.0', '<') ? "'' as generated, " : 'a.attgenerated as generated, ') + .(version_compare($this->connection->getServerVersion(), '12.0', '<') ? "'' as generated, " : 'a.attgenerated as generated, ') .'col_description(c.oid, a.attnum) as comment ' .'from pg_attribute a, pg_class c, pg_type t, pg_namespace n ' .'where c.relname = %s and n.nspname = %s and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid and n.oid = c.relnamespace ' .'order by a.attnum', $this->quoteString($table), - $this->quoteString($schema) + $schema ? $this->quoteString($schema) : 'current_schema()' ); } /** * Compile the query to determine the indexes. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -173,14 +193,14 @@ public function compileIndexes($schema, $table) .'where tc.relname = %s and tn.nspname = %s ' .'group by ic.relname, am.amname, i.indisunique, i.indisprimary', $this->quoteString($table), - $this->quoteString($schema) + $schema ? $this->quoteString($schema) : 'current_schema()' ); } /** * Compile the query to determine the foreign keys. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -203,7 +223,7 @@ public function compileForeignKeys($schema, $table) ."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) + $schema ? $this->quoteString($schema) : 'current_schema()' ); } @@ -249,23 +269,16 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent { if ($command->column->autoIncrement && $value = $command->column->get('startingValue', $command->column->get('from'))) { - $table = last(explode('.', $blueprint->getTable())); + [$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); - return 'alter sequence '.$blueprint->getPrefix().$table.'_'.$command->column->name.'_seq restart with '.$value; + $table = ($schema ? $schema.'.' : '').$this->connection->getTablePrefix().$table; + + return 'alter sequence '.$table.'_'.$command->column->name.'_seq restart with '.$value; } } - /** - * Compile a change column command into a series of SQL statements. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - * - * @throws \RuntimeException - */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileChange(Blueprint $blueprint, Fluent $command) { $column = $command->column; @@ -314,9 +327,16 @@ public function compilePrimary(Blueprint $blueprint, Fluent $command) */ public function compileUnique(Blueprint $blueprint, Fluent $command) { - $sql = sprintf('alter table %s add constraint %s unique (%s)', + $uniqueStatement = 'unique'; + + if (! is_null($command->nullsNotDistinct)) { + $uniqueStatement .= ' nulls '.($command->nullsNotDistinct ? 'not distinct' : 'distinct'); + } + + $sql = sprintf('alter table %s add constraint %s %s (%s)', $this->wrapTable($blueprint), $this->wrap($command->index), + $uniqueStatement, $this->columnize($command->columns) ); @@ -439,45 +459,45 @@ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) /** * Compile the SQL needed to drop all tables. * - * @param array $tables + * @param array $tables * @return string */ public function compileDropAllTables($tables) { - return 'drop table '.implode(',', $this->escapeNames($tables)).' cascade'; + return 'drop table '.implode(', ', $this->escapeNames($tables)).' cascade'; } /** * Compile the SQL needed to drop all views. * - * @param array $views + * @param array $views * @return string */ public function compileDropAllViews($views) { - return 'drop view '.implode(',', $this->escapeNames($views)).' cascade'; + return 'drop view '.implode(', ', $this->escapeNames($views)).' cascade'; } /** * Compile the SQL needed to drop all types. * - * @param array $types + * @param array $types * @return string */ public function compileDropAllTypes($types) { - return 'drop type '.implode(',', $this->escapeNames($types)).' cascade'; + return 'drop type '.implode(', ', $this->escapeNames($types)).' cascade'; } /** * Compile the SQL needed to drop all domains. * - * @param array $domains + * @param array $domains * @return string */ public function compileDropAllDomains($domains) { - return 'drop domain '.implode(',', $this->escapeNames($domains)).' cascade'; + return 'drop domain '.implode(', ', $this->escapeNames($domains)).' cascade'; } /** @@ -503,8 +523,8 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command) */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { - $table = last(explode('.', $blueprint->getTable())); - $index = $this->wrap("{$blueprint->getPrefix()}{$table}_pkey"); + [, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); + $index = $this->wrap("{$this->connection->getTablePrefix()}{$table}_pkey"); return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$index}"; } @@ -658,16 +678,15 @@ public function compileTableComment(Blueprint $blueprint, Fluent $command) /** * Quote-escape the given tables, views, or types. * - * @param array $names - * @return array + * @param array $names + * @return array */ public function escapeNames($names) { - return array_map(static function ($name) { - return '"'.(new Collection(explode('.', $name))) - ->map(fn ($segment) => trim($segment, '\'"')) - ->implode('"."').'"'; - }, $names); + return array_map( + fn ($name) => (new Collection(explode('.', $name)))->map($this->wrapValue(...))->implode('.'), + $names + ); } /** @@ -1206,7 +1225,7 @@ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column - * @return string|array|null + * @return string|list|null */ protected function modifyGeneratedAs(Blueprint $blueprint, Fluent $column) { diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index b40f6cf1f755..91222b7e83eb 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\IndexDefinition; @@ -30,14 +29,13 @@ class SQLiteGrammar extends Grammar /** * Get the commands to be compiled on the alter command. * - * @param \Illuminate\Database\Connection $connection * @return array */ - public function getAlterCommands(Connection $connection) + public function getAlterCommands() { $alterCommands = ['change', 'primary', 'dropPrimary', 'foreign', 'dropForeign']; - if (version_compare($connection->getServerVersion(), '3.35', '<')) { + if (version_compare($this->connection->getServerVersion(), '3.35', '<')) { $alterCommands[] = 'dropColumn'; } @@ -47,15 +45,17 @@ public function getAlterCommands(Connection $connection) /** * Compile the query to determine the SQL text that describes the given object. * + * @param string|null $schema * @param string $name * @param string $type * @return string */ - public function compileSqlCreateStatement($name, $type = 'table') + public function compileSqlCreateStatement($schema, $name, $type = 'table') { - return sprintf('select "sql" from sqlite_master where type = %s and name = %s', + return sprintf('select "sql" from %s.sqlite_master where type = %s and name = %s', + $this->wrapValue($schema ?? 'main'), $this->quoteString($type), - $this->quoteString(str_replace('.', '__', $name)) + $this->quoteString($name) ); } @@ -69,95 +69,155 @@ public function compileDbstatExists() return "select exists (select 1 from pragma_compile_options where compile_options = 'ENABLE_DBSTAT_VTAB') as enabled"; } + /** + * Compile the query to determine the schemas. + * + * @return string + */ + public function compileSchemas() + { + return 'select name, file as path, name = \'main\' as "default" from pragma_database_list order by name'; + } + /** * Compile the query to determine if the given table exists. * + * @param string|null $schema * @param string $table * @return string */ - public function compileTableExists($table) + public function compileTableExists($schema, $table) { return sprintf( - 'select exists (select 1 from sqlite_master where name = %s and type = \'table\') as "exists"', - $this->quoteString(str_replace('.', '__', $table)) + 'select exists (select 1 from %s.sqlite_master where name = %s and type = \'table\') as "exists"', + $this->wrapValue($schema ?? 'main'), + $this->quoteString($table) ); } /** * Compile the query to determine the tables. * + * @param string|string[]|null $schema * @param bool $withSize * @return string */ - public function compileTables($withSize = false) + public function compileTables($schema, $withSize = false) + { + return 'select tl.name as name, tl.schema as schema' + .($withSize ? ', (select sum(s.pgsize) ' + .'from (select tl.name as name union select il.name as name from pragma_index_list(tl.name, tl.schema) as il) as es ' + .'join dbstat(tl.schema) as s on s.name = es.name) as size' : '') + .' from pragma_table_list as tl where' + .(match (true) { + ! empty($schema) && is_array($schema) => ' tl.schema in ('.$this->quoteString($schema).') and', + ! empty($schema) => ' tl.schema = '.$this->quoteString($schema).' and', + default => '', + }) + ." tl.type in ('table', 'virtual') and tl.name not like 'sqlite\_%' escape '\' " + .'order by tl.schema, tl.name'; + } + + /** + * Compile the query for legacy versions of SQLite to determine the tables. + * + * @param string $schema + * @param bool $withSize + * @return string + */ + public function compileLegacyTables($schema, $withSize = false) { return $withSize - ? 'select m.tbl_name as name, sum(s.pgsize) as size from sqlite_master as m ' - .'join dbstat as s on s.name = m.name ' - ."where m.type in ('table', 'index') and m.tbl_name not like 'sqlite_%' " - .'group by m.tbl_name ' - .'order by m.tbl_name' - : "select name from sqlite_master where type = 'table' and name not like 'sqlite_%' order by name"; + ? sprintf( + 'select m.tbl_name as name, %s as schema, sum(s.pgsize) as size from %s.sqlite_master as m ' + .'join dbstat(%s) as s on s.name = m.name ' + ."where m.type in ('table', 'index') and m.tbl_name not like 'sqlite\_%%' escape '\' " + .'group by m.tbl_name ' + .'order by m.tbl_name', + $this->quoteString($schema), + $this->wrapValue($schema), + $this->quoteString($schema) + ) + : sprintf( + 'select name, %s as schema from %s.sqlite_master ' + ."where type = 'table' and name not like 'sqlite\_%%' escape '\' order by name", + $this->quoteString($schema), + $this->wrapValue($schema) + ); } /** * Compile the query to determine the views. * + * @param string $schema * @return string */ - public function compileViews() + public function compileViews($schema) { - return "select name, sql as definition from sqlite_master where type = 'view' order by name"; + return sprintf( + "select name, %s as schema, sql as definition from %s.sqlite_master where type = 'view' order by name", + $this->quoteString($schema), + $this->wrapValue($schema) + ); } /** * Compile the query to determine the columns. * + * @param string|null $schema * @param string $table * @return string */ - public function compileColumns($table) + public function compileColumns($schema, $table) { return sprintf( 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary", hidden as "extra" ' - .'from pragma_table_xinfo(%s) order by cid asc', - $this->quoteString(str_replace('.', '__', $table)) + .'from pragma_table_xinfo(%s, %s) order by cid asc', + $this->quoteString($table), + $this->quoteString($schema ?? 'main') ); } /** * Compile the query to determine the indexes. * + * @param string|null $schema * @param string $table * @return string */ - public function compileIndexes($table) + public function compileIndexes($schema, $table) { return sprintf( 'select \'primary\' as name, group_concat(col) as columns, 1 as "unique", 1 as "primary" ' - .'from (select name as col from pragma_table_info(%s) where pk > 0 order by pk, cid) group by name ' + .'from (select name as col from pragma_table_xinfo(%s, %s) where pk > 0 order by pk, cid) group by name ' .'union select name, group_concat(col) as columns, "unique", origin = \'pk\' as "primary" ' - .'from (select il.*, ii.name as col from pragma_index_list(%s) il, pragma_index_info(il.name) ii order by il.seq, ii.seqno) ' + .'from (select il.*, ii.name as col from pragma_index_list(%s, %s) il, pragma_index_info(il.name, %s) ii order by il.seq, ii.seqno) ' .'group by name, "unique", "primary"', - $table = $this->quoteString(str_replace('.', '__', $table)), - $table + $table = $this->quoteString($table), + $schema = $this->quoteString($schema ?? 'main'), + $table, + $schema, + $schema ); } /** * Compile the query to determine the foreign keys. * + * @param string|null $schema * @param string $table * @return string */ - public function compileForeignKeys($table) + public function compileForeignKeys($schema, $table) { return sprintf( - 'select group_concat("from") as columns, "table" as foreign_table, ' + 'select group_concat("from") as columns, %s as foreign_schema, "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) ' + .'from (select * from pragma_foreign_key_list(%s, %s) order by id desc, seq) ' .'group by id, "table", on_update, on_delete', - $this->quoteString(str_replace('.', '__', $table)) + $schema = $this->quoteString($schema ?? 'main'), + $this->quoteString($table), + $schema ); } @@ -259,12 +319,9 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - * - * @throws \RuntimeException + * @return list|string */ - public function compileAlter(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileAlter(Blueprint $blueprint, Fluent $command) { $columnNames = []; $autoIncrementColumn = null; @@ -292,11 +349,12 @@ public function compileAlter(Blueprint $blueprint, Fluent $command, Connection $ ->map(fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index)) ->all(); - $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$blueprint->getTable()); + [, $tableName] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); + $tempTable = $this->wrapTable($blueprint, '__temp__'.$this->connection->getTablePrefix()); $table = $this->wrapTable($blueprint); $columnNames = implode(', ', $columnNames); - $foreignKeyConstraintsEnabled = $connection->scalar('pragma foreign_keys'); + $foreignKeyConstraintsEnabled = $this->connection->scalar($this->pragma('foreign_keys')); return array_filter(array_merge([ $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, @@ -308,21 +366,12 @@ public function compileAlter(Blueprint $blueprint, Fluent $command, Connection $ ), sprintf('insert into %s (%s) select %s from %s', $tempTable, $columnNames, $columnNames, $table), sprintf('drop table %s', $table), - sprintf('alter table %s rename to %s', $tempTable, $table), + sprintf('alter table %s rename to %s', $tempTable, $this->wrapTable($tableName)), ], $indexes, [$foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null])); } - /** - * Compile a change column command into a series of SQL statements. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - * - * @throws \RuntimeException - */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileChange(Blueprint $blueprint, Fluent $command) { // Handled on table alteration... } @@ -348,9 +397,12 @@ public function compilePrimary(Blueprint $blueprint, Fluent $command) */ public function compileUnique(Blueprint $blueprint, Fluent $command) { - return sprintf('create unique index %s on %s (%s)', + [$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); + + return sprintf('create unique index %s%s on %s (%s)', + $schema ? $this->wrapValue($schema).'.' : '', $this->wrap($command->index), - $this->wrapTable($blueprint), + $this->wrapTable($table), $this->columnize($command->columns) ); } @@ -364,9 +416,12 @@ public function compileUnique(Blueprint $blueprint, Fluent $command) */ public function compileIndex(Blueprint $blueprint, Fluent $command) { - return sprintf('create index %s on %s (%s)', + [$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); + + return sprintf('create index %s%s on %s (%s)', + $schema ? $this->wrapValue($schema).'.' : '', $this->wrap($command->index), - $this->wrapTable($blueprint), + $this->wrapTable($table), $this->columnize($command->columns) ); } @@ -424,31 +479,40 @@ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) /** * Compile the SQL needed to drop all tables. * + * @param string|null $schema * @return string */ - public function compileDropAllTables() + public function compileDropAllTables($schema = null) { - return "delete from sqlite_master where type in ('table', 'index', 'trigger')"; + return sprintf("delete from %s.sqlite_master where type in ('table', 'index', 'trigger')", + $this->wrapValue($schema ?? 'main') + ); } /** * Compile the SQL needed to drop all views. * + * @param string|null $schema * @return string */ - public function compileDropAllViews() + public function compileDropAllViews($schema = null) { - return "delete from sqlite_master where type in ('view')"; + return sprintf("delete from %s.sqlite_master where type in ('view')", + $this->wrapValue($schema ?? 'main') + ); } /** * Compile the SQL needed to rebuild the database. * + * @param string|null $schema * @return string */ - public function compileRebuild() + public function compileRebuild($schema = null) { - return 'vacuum'; + return sprintf('vacuum %s', + $this->wrapValue($schema ?? 'main') + ); } /** @@ -456,12 +520,11 @@ public function compileRebuild() * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|null + * @return list|null */ - public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileDropColumn(Blueprint $blueprint, Fluent $command) { - if (version_compare($connection->getServerVersion(), '3.35', '<')) { + if (version_compare($this->connection->getServerVersion(), '3.35', '<')) { // Handled on table alteration... return null; @@ -495,9 +558,7 @@ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { - $index = $this->wrap($command->index); - - return "drop index {$index}"; + return $this->compileDropIndex($blueprint, $command); } /** @@ -509,9 +570,12 @@ public function compileDropUnique(Blueprint $blueprint, Fluent $command) */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { - $index = $this->wrap($command->index); + [$schema] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($blueprint->getTable()); - return "drop index {$index}"; + return sprintf('drop index %s%s', + $schema ? $this->wrapValue($schema).'.' : '', + $this->wrap($command->index) + ); } /** @@ -563,14 +627,13 @@ public function compileRename(Blueprint $blueprint, Fluent $command) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection * @return array * * @throws \RuntimeException */ - public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileRenameIndex(Blueprint $blueprint, Fluent $command) { - $indexes = $connection->getSchemaBuilder()->getIndexes($blueprint->getTable()); + $indexes = $this->connection->getSchemaBuilder()->getIndexes($blueprint->getTable()); $index = Arr::first($indexes, fn ($index) => $index['name'] === $command->from); @@ -606,7 +669,7 @@ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connec */ public function compileEnableForeignKeyConstraints() { - return $this->pragma('foreign_keys', 'ON'); + return $this->pragma('foreign_keys', 1); } /** @@ -616,72 +679,22 @@ public function compileEnableForeignKeyConstraints() */ public function compileDisableForeignKeyConstraints() { - return $this->pragma('foreign_keys', 'OFF'); - } - - /** - * Compile the command to set the busy timeout. - * - * @param int $milliseconds - * @return string - */ - public function compileSetBusyTimeout($milliseconds) - { - return $this->pragma('busy_timeout', $milliseconds); - } - - /** - * Compile the command to set the journal mode. - * - * @param string $mode - * @return string - */ - public function compileSetJournalMode($mode) - { - return $this->pragma('journal_mode', $mode); - } - - /** - * Compile the command to set the synchronous mode. - * - * @param string $mode - * @return string - */ - public function compileSetSynchronous($mode) - { - return $this->pragma('synchronous', $mode); + return $this->pragma('foreign_keys', 0); } /** - * Compile the SQL needed to enable a writable schema. + * Get the SQL to get or set a PRAGMA value. * - * @return string - */ - public function compileEnableWriteableSchema() - { - return $this->pragma('writable_schema', 1); - } - - /** - * Compile the SQL needed to disable a writable schema. - * - * @return string - */ - public function compileDisableWriteableSchema() - { - return $this->pragma('writable_schema', 0); - } - - /** - * Get the SQL to set a PRAGMA value. - * - * @param string $name + * @param string $key * @param mixed $value * @return string */ - protected function pragma(string $name, mixed $value): string + public function pragma(string $key, mixed $value = null): string { - return sprintf('PRAGMA %s = %s;', $name, $value); + return sprintf('pragma %s%s', + $key, + is_null($value) ? '' : ' = '.$value + ); } /** @@ -872,7 +885,7 @@ protected function typeEnum(Fluent $column) */ protected function typeJson(Fluent $column) { - return 'text'; + return $this->connection->getConfig('use_native_json') ? 'json' : 'text'; } /** @@ -883,7 +896,7 @@ protected function typeJson(Fluent $column) */ protected function typeJsonb(Fluent $column) { - return 'text'; + return $this->connection->getConfig('use_native_jsonb') ? 'jsonb' : 'text'; } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 81258f2e1036..5e183a5dce76 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Schema\Grammars; -use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; @@ -38,42 +37,14 @@ class SqlServerGrammar extends Grammar protected $fluentCommands = ['Default']; /** - * Compile a query to determine the name of the default schema. + * Compile the query to determine the schemas. * * @return string */ - public function compileDefaultSchema() + public function compileSchemas() { - return 'select schema_name()'; - } - - /** - * Compile a create database command. - * - * @param string $name - * @param \Illuminate\Database\Connection $connection - * @return string - */ - public function compileCreateDatabase($name, $connection) - { - return sprintf( - 'create database %s', - $this->wrapValue($name), - ); - } - - /** - * Compile a drop database if exists command. - * - * @param string $name - * @return string - */ - public function compileDropDatabaseIfExists($name) - { - return sprintf( - 'drop database if exists %s', - $this->wrapValue($name) - ); + return 'select name, iif(schema_id = schema_id(), 1, 0) as [default] from sys.schemas ' + ."where name not in ('information_schema', 'sys') and name not like 'db[_]%' order by name"; } /** @@ -94,34 +65,56 @@ public function compileTableExists($schema, $table) /** * Compile the query to determine the tables. * + * @param string|string[]|null $schema * @return string */ - public function compileTables() + public function compileTables($schema) { return 'select t.name as name, schema_name(t.schema_id) as [schema], sum(u.total_pages) * 8 * 1024 as size ' .'from sys.tables as t ' .'join sys.partitions as p on p.object_id = t.object_id ' .'join sys.allocation_units as u on u.container_id = p.hobt_id ' - .'group by t.name, t.schema_id ' - .'order by t.name'; + ."where t.is_ms_shipped = 0 and t.name <> 'sysdiagrams'" + .$this->compileSchemaWhereClause($schema, 'schema_name(t.schema_id)') + .' group by t.name, t.schema_id ' + .'order by [schema], t.name'; } /** * Compile the query to determine the views. * + * @param string|string[]|null $schema * @return string */ - public function compileViews() + public function compileViews($schema) { return 'select name, schema_name(v.schema_id) as [schema], definition from sys.views as v ' .'inner join sys.sql_modules as m on v.object_id = m.object_id ' - .'order by name'; + .'where v.is_ms_shipped = 0' + .$this->compileSchemaWhereClause($schema, 'schema_name(v.schema_id)') + .' order by [schema], name'; + } + + /** + * Compile the query to compare the schema. + * + * @param string|string[]|null $schema + * @param string $column + * @return string + */ + protected function compileSchemaWhereClause($schema, $column) + { + return match (true) { + ! empty($schema) && is_array($schema) => " and $column in (".$this->quoteString($schema).')', + ! empty($schema) => " and $column = ".$this->quoteString($schema), + default => '', + }; } /** * Compile the query to determine the columns. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -151,7 +144,7 @@ public function compileColumns($schema, $table) /** * Compile the query to determine the indexes. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -175,7 +168,7 @@ public function compileIndexes($schema, $table) /** * Compile the query to determine the foreign keys. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ @@ -212,9 +205,10 @@ public function compileForeignKeys($schema, $table) */ public function compileCreate(Blueprint $blueprint, Fluent $command) { - $columns = implode(', ', $this->getColumns($blueprint)); - - return 'create table '.$this->wrapTable($blueprint)." ($columns)"; + return sprintf('create table %s (%s)', + $this->wrapTable($blueprint, $blueprint->temporary ? '#'.$this->connection->getTablePrefix() : null), + implode(', ', $this->getColumns($blueprint)) + ); } /** @@ -232,15 +226,8 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) ); } - /** - * Compile a rename column command. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileRenameColumn(Blueprint $blueprint, Fluent $command) { return sprintf("sp_rename %s, %s, N'COLUMN'", $this->quoteString($this->wrapTable($blueprint).'.'.$this->wrap($command->from)), @@ -248,17 +235,8 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne ); } - /** - * Compile a change column command into a series of SQL statements. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - * - * @throws \RuntimeException - */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + /** @inheritDoc */ + public function compileChange(Blueprint $blueprint, Fluent $command) { return [ $this->compileDropDefaultConstraint($blueprint, $command), @@ -415,7 +393,7 @@ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $comma { $columns = $command->name === 'change' ? "'".$command->column->name."'" - : "'".implode("','", $command->columns)."'"; + : "'".implode("', '", $command->columns)."'"; $table = $this->wrapTable($blueprint); $tableName = $this->quoteString($this->wrapTable($blueprint)); @@ -1040,25 +1018,10 @@ protected function modifyPersisted(Blueprint $blueprint, Fluent $column) } } - /** - * Wrap a table in keyword identifiers. - * - * @param \Illuminate\Database\Schema\Blueprint|\Illuminate\Contracts\Database\Query\Expression|string $table - * @return string - */ - public function wrapTable($table) - { - if ($table instanceof Blueprint && $table->temporary) { - $this->setTablePrefix('#'); - } - - return parent::wrapTable($table); - } - /** * Quote the given string literal. * - * @param string|array $value + * @param string|array $value * @return string */ public function quoteString($value) diff --git a/src/Illuminate/Database/Schema/IndexDefinition.php b/src/Illuminate/Database/Schema/IndexDefinition.php index fc5d78e5b92f..d11a3c8daeed 100644 --- a/src/Illuminate/Database/Schema/IndexDefinition.php +++ b/src/Illuminate/Database/Schema/IndexDefinition.php @@ -9,6 +9,7 @@ * @method $this language(string $language) Specify a language for the full text index (PostgreSQL) * @method $this deferrable(bool $value = true) Specify that the unique index is deferrable (PostgreSQL) * @method $this initiallyImmediate(bool $value = true) Specify the default time to check the unique index constraint (PostgreSQL) + * @method $this nullsNotDistinct(bool $value = true) Specify that the null values should not be treated as distinct (PostgreSQL) */ class IndexDefinition extends Fluent { diff --git a/src/Illuminate/Database/Schema/MariaDbSchemaState.php b/src/Illuminate/Database/Schema/MariaDbSchemaState.php index f8323c4471f2..93fbba9b38a3 100644 --- a/src/Illuminate/Database/Schema/MariaDbSchemaState.php +++ b/src/Illuminate/Database/Schema/MariaDbSchemaState.php @@ -4,6 +4,23 @@ class MariaDbSchemaState extends MySqlSchemaState { + /** + * Load the given schema file into the database. + * + * @param string $path + * @return void + */ + public function load($path) + { + $command = 'mariadb '.$this->connectionString().' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"'; + + $process = $this->makeProcess($command)->setTimeout(null); + + $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + } + /** * Get the base dump command arguments for MariaDB as a string. * @@ -11,7 +28,7 @@ class MariaDbSchemaState extends MySqlSchemaState */ protected function baseDumpCommand() { - $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; + $command = 'mariadb-dump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc'; return $command.' "${:LARAVEL_LOAD_DATABASE}"'; } diff --git a/src/Illuminate/Database/Schema/MySqlBuilder.php b/src/Illuminate/Database/Schema/MySqlBuilder.php index 842130503595..6676411225ea 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -4,128 +4,6 @@ class MySqlBuilder extends Builder { - /** - * Create a database in the schema. - * - * @param string $name - * @return bool - */ - public function createDatabase($name) - { - return $this->connection->statement( - $this->grammar->compileCreateDatabase($name, $this->connection) - ); - } - - /** - * Drop a database from the schema if the database exists. - * - * @param string $name - * @return bool - */ - public function dropDatabaseIfExists($name) - { - return $this->connection->statement( - $this->grammar->compileDropDatabaseIfExists($name) - ); - } - - /** - * Determine if the given table exists. - * - * @param string $table - * @return bool - */ - public function hasTable($table) - { - $table = $this->connection->getTablePrefix().$table; - - $database = $this->connection->getDatabaseName(); - - return (bool) $this->connection->scalar( - $this->grammar->compileTableExists($database, $table) - ); - } - - /** - * Get the tables for the database. - * - * @return array - */ - public function getTables() - { - return $this->connection->getPostProcessor()->processTables( - $this->connection->selectFromWriteConnection( - $this->grammar->compileTables($this->connection->getDatabaseName()) - ) - ); - } - - /** - * Get the views for the database. - * - * @return array - */ - public function getViews() - { - return $this->connection->getPostProcessor()->processViews( - $this->connection->selectFromWriteConnection( - $this->grammar->compileViews($this->connection->getDatabaseName()) - ) - ); - } - - /** - * Get the columns for a given table. - * - * @param string $table - * @return array - */ - public function getColumns($table) - { - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->selectFromWriteConnection( - $this->grammar->compileColumns($this->connection->getDatabaseName(), $table) - ); - - return $this->connection->getPostProcessor()->processColumns($results); - } - - /** - * Get the indexes for a given table. - * - * @param string $table - * @return array - */ - public function getIndexes($table) - { - $table = $this->connection->getTablePrefix().$table; - - return $this->connection->getPostProcessor()->processIndexes( - $this->connection->selectFromWriteConnection( - $this->grammar->compileIndexes($this->connection->getDatabaseName(), $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. * @@ -133,7 +11,7 @@ public function getForeignKeys($table) */ public function dropAllTables() { - $tables = array_column($this->getTables(), 'name'); + $tables = $this->getTableListing($this->getCurrentSchemaListing()); if (empty($tables)) { return; @@ -141,11 +19,13 @@ public function dropAllTables() $this->disableForeignKeyConstraints(); - $this->connection->statement( - $this->grammar->compileDropAllTables($tables) - ); - - $this->enableForeignKeyConstraints(); + try { + $this->connection->statement( + $this->grammar->compileDropAllTables($tables) + ); + } finally { + $this->enableForeignKeyConstraints(); + } } /** @@ -155,7 +35,7 @@ public function dropAllTables() */ public function dropAllViews() { - $views = array_column($this->getViews(), 'name'); + $views = array_column($this->getViews($this->getCurrentSchemaListing()), 'schema_qualified_name'); if (empty($views)) { return; @@ -165,4 +45,14 @@ public function dropAllViews() $this->grammar->compileDropAllViews($views) ); } + + /** + * Get the names of current schemas for the connection. + * + * @return string[]|null + */ + public function getCurrentSchemaListing() + { + return [$this->connection->getDatabaseName()]; + } } diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index be5e227359f3..30729f1ef2e8 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -108,8 +108,8 @@ protected function connectionString() $config = $this->connection->getConfig(); $value .= $config['unix_socket'] ?? false - ? ' --socket="${:LARAVEL_LOAD_SOCKET}"' - : ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"'; + ? ' --socket="${:LARAVEL_LOAD_SOCKET}"' + : ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"'; if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_CA])) { $value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"'; diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index b39486c0e5a6..66f311742708 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -3,90 +3,10 @@ namespace Illuminate\Database\Schema; use Illuminate\Database\Concerns\ParsesSearchPath; -use InvalidArgumentException; class PostgresBuilder extends Builder { - use ParsesSearchPath { - parseSearchPath as baseParseSearchPath; - } - - /** - * Create a database in the schema. - * - * @param string $name - * @return bool - */ - public function createDatabase($name) - { - return $this->connection->statement( - $this->grammar->compileCreateDatabase($name, $this->connection) - ); - } - - /** - * Drop a database from the schema if the database exists. - * - * @param string $name - * @return bool - */ - public function dropDatabaseIfExists($name) - { - return $this->connection->statement( - $this->grammar->compileDropDatabaseIfExists($name) - ); - } - - /** - * Determine if the given table exists. - * - * @param string $table - * @return bool - */ - public function hasTable($table) - { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - return (bool) $this->connection->scalar( - $this->grammar->compileTableExists($schema, $table) - ); - } - - /** - * Determine if the given view exists. - * - * @param string $view - * @return bool - */ - public function hasView($view) - { - [$schema, $view] = $this->parseSchemaAndTable($view); - - $view = $this->connection->getTablePrefix().$view; - - foreach ($this->getViews() as $value) { - if (strtolower($view) === strtolower($value['name']) - && strtolower($schema) === strtolower($value['schema'])) { - return true; - } - } - - return false; - } - - /** - * 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()) - ); - } + use ParsesSearchPath; /** * Drop all tables from the database. @@ -99,14 +19,9 @@ public function dropAllTables() $excludedTables = $this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys']; - $schemas = $this->getSchemas(); - - foreach ($this->getTables() as $table) { - $qualifiedName = $table['schema'].'.'.$table['name']; - - if (in_array($table['schema'], $schemas) && - empty(array_intersect([$table['name'], $qualifiedName], $excludedTables))) { - $tables[] = $qualifiedName; + foreach ($this->getTables($this->getCurrentSchemaListing()) as $table) { + if (empty(array_intersect([$table['name'], $table['schema_qualified_name']], $excludedTables))) { + $tables[] = $table['schema_qualified_name']; } } @@ -126,15 +41,7 @@ public function dropAllTables() */ public function dropAllViews() { - $views = []; - - $schemas = $this->getSchemas(); - - foreach ($this->getViews() as $view) { - if (in_array($view['schema'], $schemas)) { - $views[] = $view['schema'].'.'.$view['name']; - } - } + $views = array_column($this->getViews($this->getCurrentSchemaListing()), 'schema_qualified_name'); if (empty($views)) { return; @@ -155,14 +62,12 @@ public function dropAllTypes() $types = []; $domains = []; - $schemas = $this->getSchemas(); - - foreach ($this->getTypes() as $type) { - if (! $type['implicit'] && in_array($type['schema'], $schemas)) { + foreach ($this->getTypes($this->getCurrentSchemaListing()) as $type) { + if (! $type['implicit']) { if ($type['type'] === 'domain') { - $domains[] = $type['schema'].'.'.$type['name']; + $domains[] = $type['schema_qualified_name']; } else { - $types[] = $type['schema'].'.'.$type['name']; + $types[] = $type['schema_qualified_name']; } } } @@ -177,111 +82,19 @@ public function dropAllTypes() } /** - * Get the columns for a given table. - * - * @param string $table - * @return array - */ - public function getColumns($table) - { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->selectFromWriteConnection( - $this->grammar->compileColumns($schema, $table) - ); - - return $this->connection->getPostProcessor()->processColumns($results); - } - - /** - * Get the indexes for a given table. - * - * @param string $table - * @return array - */ - public function getIndexes($table) - { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - return $this->connection->getPostProcessor()->processIndexes( - $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($schema, $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. + * Get the current schemas for the connection. * - * @return array + * @return string[] */ - public function getSchemas() + public function getCurrentSchemaListing() { - return $this->parseSearchPath( - $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ?: 'public' + return array_map( + fn ($schema) => $schema === '$user' ? $this->connection->getConfig('username') : $schema, + $this->parseSearchPath( + $this->connection->getConfig('search_path') + ?: $this->connection->getConfig('schema') + ?: 'public' + ) ); } - - /** - * Parse the database object reference and extract the schema and table. - * - * @param string $reference - * @return array - */ - public function parseSchemaAndTable($reference) - { - $parts = explode('.', $reference); - - if (count($parts) > 2) { - $database = $parts[0]; - - throw new InvalidArgumentException("Using three-part reference is not supported, you may use `Schema::connection('$database')` instead."); - } - - // We will use the default schema unless the schema has been specified in the - // query. If the schema has been specified in the query then we can use it - // instead of a default schema configured in the connection search path. - $schema = $this->getSchemas()[0]; - - if (count($parts) === 2) { - $schema = $parts[0]; - array_shift($parts); - } - - return [$schema, $parts[0]]; - } - - /** - * Parse the "search_path" configuration value into an array. - * - * @param string|array|null $searchPath - * @return array - */ - protected function parseSearchPath($searchPath) - { - return array_map(function ($schema) { - return $schema === '$user' - ? $this->connection->getConfig('username') - : $schema; - }, $this->baseParseSearchPath($searchPath)); - } } diff --git a/src/Illuminate/Database/Schema/PostgresSchemaState.php b/src/Illuminate/Database/Schema/PostgresSchemaState.php index a96894128b1d..25da812e61c5 100644 --- a/src/Illuminate/Database/Schema/PostgresSchemaState.php +++ b/src/Illuminate/Database/Schema/PostgresSchemaState.php @@ -59,7 +59,7 @@ public function load($path) */ protected function getMigrationTable(): string { - [$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($this->migrationTable); + [$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($this->migrationTable, withDefaultSchema: true); return $schema.'.'.$this->connection->getTablePrefix().$table; } diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 25111ebd3905..040f1623f8a1 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Schema; use Illuminate\Database\QueryException; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\File; class SQLiteBuilder extends Builder @@ -26,60 +27,65 @@ public function createDatabase($name) */ public function dropDatabaseIfExists($name) { - return File::exists($name) - ? File::delete($name) - : true; + return ! File::exists($name) || File::delete($name); } - /** - * Determine if the given table exists. - * - * @param string $table - * @return bool - */ - public function hasTable($table) + /** @inheritDoc */ + public function getTables($schema = null) { - $table = $this->connection->getTablePrefix().$table; + try { + $withSize = $this->connection->scalar($this->grammar->compileDbstatExists()); + } catch (QueryException) { + $withSize = false; + } - return (bool) $this->connection->scalar( - $this->grammar->compileTableExists($table) - ); - } + if (version_compare($this->connection->getServerVersion(), '3.37.0', '<')) { + $schema ??= array_column($this->getSchemas(), 'name'); - /** - * Get the tables for the database. - * - * @param bool $withSize - * @return array - */ - public function getTables($withSize = true) - { - if ($withSize) { - try { - $withSize = $this->connection->scalar($this->grammar->compileDbstatExists()); - } catch (QueryException $e) { - $withSize = false; + $tables = []; + + foreach (Arr::wrap($schema) as $name) { + $tables = array_merge($tables, $this->connection->selectFromWriteConnection( + $this->grammar->compileLegacyTables($name, $withSize) + )); } + + return $this->connection->getPostProcessor()->processTables($tables); } return $this->connection->getPostProcessor()->processTables( - $this->connection->selectFromWriteConnection($this->grammar->compileTables($withSize)) + $this->connection->selectFromWriteConnection( + $this->grammar->compileTables($schema, $withSize) + ) ); } - /** - * Get the columns for a given table. - * - * @param string $table - * @return array - */ + /** @inheritDoc */ + public function getViews($schema = null) + { + $schema ??= array_column($this->getSchemas(), 'name'); + + $views = []; + + foreach (Arr::wrap($schema) as $name) { + $views = array_merge($views, $this->connection->selectFromWriteConnection( + $this->grammar->compileViews($name) + )); + } + + return $this->connection->getPostProcessor()->processViews($views); + } + + /** @inheritDoc */ public function getColumns($table) { + [$schema, $table] = $this->parseSchemaAndTable($table); + $table = $this->connection->getTablePrefix().$table; return $this->connection->getPostProcessor()->processColumns( - $this->connection->selectFromWriteConnection($this->grammar->compileColumns($table)), - $this->connection->scalar($this->grammar->compileSqlCreateStatement($table)) + $this->connection->selectFromWriteConnection($this->grammar->compileColumns($schema, $table)), + $this->connection->scalar($this->grammar->compileSqlCreateStatement($schema, $table)) ); } @@ -90,22 +96,26 @@ public function getColumns($table) */ public function dropAllTables() { - $database = $this->connection->getDatabaseName(); - - if ($database !== ':memory:' && - ! str_contains($database, '?mode=memory') && - ! str_contains($database, '&mode=memory') - ) { - return $this->refreshDatabaseFile(); - } + foreach ($this->getCurrentSchemaListing() as $schema) { + $database = $schema === 'main' + ? $this->connection->getDatabaseName() + : (array_column($this->getSchemas(), 'path', 'name')[$schema] ?: ':memory:'); - $this->connection->select($this->grammar->compileEnableWriteableSchema()); + if ($database !== ':memory:' && + ! str_contains($database, '?mode=memory') && + ! str_contains($database, '&mode=memory') + ) { + $this->refreshDatabaseFile($database); + } else { + $this->pragma('writable_schema', 1); - $this->connection->select($this->grammar->compileDropAllTables()); + $this->connection->statement($this->grammar->compileDropAllTables($schema)); - $this->connection->select($this->grammar->compileDisableWriteableSchema()); + $this->pragma('writable_schema', 0); - $this->connection->select($this->grammar->compileRebuild()); + $this->connection->statement($this->grammar->compileRebuild($schema)); + } + } } /** @@ -115,61 +125,49 @@ public function dropAllTables() */ public function dropAllViews() { - $this->connection->select($this->grammar->compileEnableWriteableSchema()); + foreach ($this->getCurrentSchemaListing() as $schema) { + $this->pragma('writable_schema', 1); - $this->connection->select($this->grammar->compileDropAllViews()); + $this->connection->statement($this->grammar->compileDropAllViews($schema)); - $this->connection->select($this->grammar->compileDisableWriteableSchema()); + $this->pragma('writable_schema', 0); - $this->connection->select($this->grammar->compileRebuild()); - } - - /** - * Set the busy timeout. - * - * @param int $milliseconds - * @return bool - */ - public function setBusyTimeout($milliseconds) - { - return $this->connection->statement( - $this->grammar->compileSetBusyTimeout($milliseconds) - ); + $this->connection->statement($this->grammar->compileRebuild($schema)); + } } /** - * Set the journal mode. + * Get the value for the given pragma name or set the given value. * - * @param string $mode - * @return bool + * @param string $key + * @param mixed $value + * @return mixed */ - public function setJournalMode($mode) + public function pragma($key, $value = null) { - return $this->connection->statement( - $this->grammar->compileSetJournalMode($mode) - ); + return is_null($value) + ? $this->connection->scalar($this->grammar->pragma($key)) + : $this->connection->statement($this->grammar->pragma($key, $value)); } /** - * Set the synchronous mode. + * Empty the database file. * - * @param int $mode - * @return bool + * @param string|null $path + * @return void */ - public function setSynchronous($mode) + public function refreshDatabaseFile($path = null) { - return $this->connection->statement( - $this->grammar->compileSetSynchronous($mode) - ); + file_put_contents($path ?? $this->connection->getDatabaseName(), ''); } /** - * Empty the database file. + * Get the names of current schemas for the connection. * - * @return void + * @return string[]|null */ - public function refreshDatabaseFile() + public function getCurrentSchemaListing() { - file_put_contents($this->connection->getDatabaseName(), ''); + return ['main']; } } diff --git a/src/Illuminate/Database/Schema/SchemaState.php b/src/Illuminate/Database/Schema/SchemaState.php index d72085081487..be792138f7b4 100644 --- a/src/Illuminate/Database/Schema/SchemaState.php +++ b/src/Illuminate/Database/Schema/SchemaState.php @@ -49,7 +49,6 @@ abstract class SchemaState * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Filesystem\Filesystem|null $files * @param callable|null $processFactory - * @return void */ public function __construct(Connection $connection, ?Filesystem $files = null, ?callable $processFactory = null) { diff --git a/src/Illuminate/Database/Schema/SqlServerBuilder.php b/src/Illuminate/Database/Schema/SqlServerBuilder.php index 9b59ccc0ebd3..9161bc61af7c 100644 --- a/src/Illuminate/Database/Schema/SqlServerBuilder.php +++ b/src/Illuminate/Database/Schema/SqlServerBuilder.php @@ -2,76 +2,10 @@ namespace Illuminate\Database\Schema; -use InvalidArgumentException; +use Illuminate\Support\Arr; class SqlServerBuilder extends Builder { - /** - * Create a database in the schema. - * - * @param string $name - * @return bool - */ - public function createDatabase($name) - { - return $this->connection->statement( - $this->grammar->compileCreateDatabase($name, $this->connection) - ); - } - - /** - * Drop a database from the schema if the database exists. - * - * @param string $name - * @return bool - */ - public function dropDatabaseIfExists($name) - { - return $this->connection->statement( - $this->grammar->compileDropDatabaseIfExists($name) - ); - } - - /** - * Determine if the given table exists. - * - * @param string $table - * @return bool - */ - public function hasTable($table) - { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - return (bool) $this->connection->scalar( - $this->grammar->compileTableExists($schema, $table) - ); - } - - /** - * Determine if the given view exists. - * - * @param string $view - * @return bool - */ - public function hasView($view) - { - [$schema, $view] = $this->parseSchemaAndTable($view); - - $schema ??= $this->getDefaultSchema(); - $view = $this->connection->getTablePrefix().$view; - - foreach ($this->getViews() as $value) { - if (strtolower($view) === strtolower($value['name']) - && strtolower($schema) === strtolower($value['schema'])) { - return true; - } - } - - return false; - } - /** * Drop all tables from the database. * @@ -95,84 +29,12 @@ public function dropAllViews() } /** - * Get the columns for a given table. + * Get the default schema name for the connection. * - * @param string $table - * @return array + * @return string|null */ - public function getColumns($table) + public function getCurrentSchemaName() { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->selectFromWriteConnection( - $this->grammar->compileColumns($schema, $table) - ); - - return $this->connection->getPostProcessor()->processColumns($results); - } - - /** - * Get the indexes for a given table. - * - * @param string $table - * @return array - */ - public function getIndexes($table) - { - [$schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - return $this->connection->getPostProcessor()->processIndexes( - $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($schema, $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 default schema for the connection. - * - * @return string - */ - protected function getDefaultSchema() - { - return $this->connection->scalar($this->grammar->compileDefaultSchema()); - } - - /** - * Parse the database object reference and extract the schema and table. - * - * @param string $reference - * @return array - */ - protected function parseSchemaAndTable($reference) - { - $parts = array_pad(explode('.', $reference, 2), -2, null); - - if (str_contains($parts[1], '.')) { - $database = $parts[0]; - - throw new InvalidArgumentException("Using three-part reference is not supported, you may use `Schema::connection('$database')` instead."); - } - - return $parts; + return Arr::first($this->getSchemas(), fn ($schema) => $schema['default'])['name']; } } diff --git a/src/Illuminate/Database/Seeder.php b/src/Illuminate/Database/Seeder.php index bfb48aedf3b0..08e57b2d7834 100755 --- a/src/Illuminate/Database/Seeder.php +++ b/src/Illuminate/Database/Seeder.php @@ -110,11 +110,15 @@ public function callSilent($class, array $parameters = []) */ public function callOnce($class, $silent = false, array $parameters = []) { - if (in_array($class, static::$called)) { - return; - } + $classes = Arr::wrap($class); + + foreach ($classes as $class) { + if (in_array($class, static::$called)) { + continue; + } - $this->call($class, $silent, $parameters); + $this->call($class, $silent, $parameters); + } } /** diff --git a/src/Illuminate/Database/SqlServerConnection.php b/src/Illuminate/Database/SqlServerConnection.php index 19f7bb8afbf3..1e6fe52bfe16 100755 --- a/src/Illuminate/Database/SqlServerConnection.php +++ b/src/Illuminate/Database/SqlServerConnection.php @@ -93,9 +93,7 @@ protected function isUniqueConstraintError(Exception $exception) */ protected function getDefaultQueryGrammar() { - ($grammar = new QueryGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new QueryGrammar($this); } /** @@ -119,9 +117,7 @@ public function getSchemaBuilder() */ protected function getDefaultSchemaGrammar() { - ($grammar = new SchemaGrammar)->setConnection($this); - - return $this->withTablePrefix($grammar); + return new SchemaGrammar($this); } /** diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index b0e7412734a3..606c093f1ba9 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -17,12 +17,12 @@ "require": { "php": "^8.2", "ext-pdo": "*", - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "brick/math": "^0.11|^0.12", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "laravel/serializable-closure": "^1.3|^2.0" }, "autoload": { @@ -32,17 +32,18 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", - "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)." + "illuminate/console": "Required to use the database commands (^12.0).", + "illuminate/events": "Required to use the observers with Eloquent (^12.0).", + "illuminate/filesystem": "Required to use the migrations (^12.0).", + "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).", + "illuminate/pagination": "Required to paginate the result set (^12.0).", + "symfony/finder": "Required to use Eloquent model factories (^7.2)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Encryption/Encrypter.php b/src/Illuminate/Encryption/Encrypter.php index 0990b0a209c2..5c6c021a1965 100755 --- a/src/Illuminate/Encryption/Encrypter.php +++ b/src/Illuminate/Encryption/Encrypter.php @@ -48,7 +48,6 @@ class Encrypter implements EncrypterContract, StringEncrypter * * @param string $key * @param string $cipher - * @return void * * @throws \RuntimeException */ diff --git a/src/Illuminate/Encryption/MissingAppKeyException.php b/src/Illuminate/Encryption/MissingAppKeyException.php index d8ffcd184b51..3f6b07b63751 100644 --- a/src/Illuminate/Encryption/MissingAppKeyException.php +++ b/src/Illuminate/Encryption/MissingAppKeyException.php @@ -10,7 +10,6 @@ class MissingAppKeyException extends RuntimeException * Create a new exception instance. * * @param string $message - * @return void */ public function __construct($message = 'No application encryption key has been specified.') { diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index 0c127430432d..1c430e9c3e34 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -18,8 +18,8 @@ "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0" + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Events/CallQueuedListener.php b/src/Illuminate/Events/CallQueuedListener.php index dd99058c3e6f..e78a4c4e2c5f 100644 --- a/src/Illuminate/Events/CallQueuedListener.php +++ b/src/Illuminate/Events/CallQueuedListener.php @@ -88,7 +88,6 @@ class CallQueuedListener implements ShouldQueue * @param class-string $class * @param string $method * @param array $data - * @return void */ public function __construct($class, $method, $data) { diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 5a3c9702e198..ee409586efc8 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -71,7 +71,6 @@ class Dispatcher implements DispatcherContract * Create a new event dispatcher instance. * * @param \Illuminate\Contracts\Container\Container|null $container - * @return void */ public function __construct(?ContainerContract $container = null) { @@ -344,7 +343,8 @@ protected function shouldBroadcast(array $payload) protected function broadcastWhen($event) { return method_exists($event, 'broadcastWhen') - ? $event->broadcastWhen() : true; + ? $event->broadcastWhen() + : true; } /** @@ -372,8 +372,8 @@ public function getListeners($eventName) ); return class_exists($eventName, false) - ? $this->addInterfaceListeners($eventName, $listeners) - : $listeners; + ? $this->addInterfaceListeners($eventName, $listeners) + : $listeners; } /** @@ -489,8 +489,8 @@ public function createClassListener($listener, $wildcard = false) protected function createClassCallable($listener) { [$class, $method] = is_array($listener) - ? $listener - : $this->parseClassCallable($listener); + ? $listener + : $this->parseClassCallable($listener); if (! method_exists($class, $method)) { $method = '__invoke'; @@ -503,8 +503,8 @@ protected function createClassCallable($listener) $listener = $this->container->make($class); return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener) - ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) - : [$listener, $method]; + ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) + : [$listener, $method]; } /** diff --git a/src/Illuminate/Events/NullDispatcher.php b/src/Illuminate/Events/NullDispatcher.php index 4b2d01119cc7..b0c9cdf8ef6f 100644 --- a/src/Illuminate/Events/NullDispatcher.php +++ b/src/Illuminate/Events/NullDispatcher.php @@ -20,7 +20,6 @@ class NullDispatcher implements DispatcherContract * Create a new event dispatcher instance that does not fire. * * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher - * @return void */ public function __construct(DispatcherContract $dispatcher) { diff --git a/src/Illuminate/Events/QueuedClosure.php b/src/Illuminate/Events/QueuedClosure.php index 0a012eb57d9d..a1a2d63d1fbb 100644 --- a/src/Illuminate/Events/QueuedClosure.php +++ b/src/Illuminate/Events/QueuedClosure.php @@ -49,7 +49,6 @@ class QueuedClosure * Create a new queued closure event listener resolver. * * @param \Closure $closure - * @return void */ public function __construct(Closure $closure) { diff --git a/src/Illuminate/Events/composer.json b/src/Illuminate/Events/composer.json index df77fedb69eb..801895fd899f 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -15,12 +15,12 @@ ], "require": { "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" + "illuminate/bus": "^12.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Filesystem/AwsS3V3Adapter.php b/src/Illuminate/Filesystem/AwsS3V3Adapter.php index 8e908e81aa59..7b125e2a68fe 100644 --- a/src/Illuminate/Filesystem/AwsS3V3Adapter.php +++ b/src/Illuminate/Filesystem/AwsS3V3Adapter.php @@ -25,7 +25,6 @@ class AwsS3V3Adapter extends FilesystemAdapter * @param \League\Flysystem\AwsS3V3\AwsS3V3Adapter $adapter * @param array $config * @param \Aws\S3\S3Client $client - * @return void */ public function __construct(FilesystemOperator $driver, S3Adapter $adapter, array $config, S3Client $client) { diff --git a/src/Illuminate/Filesystem/FilesystemAdapter.php b/src/Illuminate/Filesystem/FilesystemAdapter.php index 46e23072c58d..50ce21f3671d 100644 --- a/src/Illuminate/Filesystem/FilesystemAdapter.php +++ b/src/Illuminate/Filesystem/FilesystemAdapter.php @@ -97,7 +97,6 @@ class FilesystemAdapter implements CloudFilesystemContract * @param \League\Flysystem\FilesystemOperator $driver * @param \League\Flysystem\FilesystemAdapter $adapter * @param array $config - * @return void */ public function __construct(FilesystemOperator $driver, FlysystemAdapter $adapter, array $config = []) { @@ -398,8 +397,8 @@ protected function fallbackName($name) public function put($path, $contents, $options = []) { $options = is_string($options) - ? ['visibility' => $options] - : (array) $options; + ? ['visibility' => $options] + : (array) $options; // If the given contents is actually a file or uploaded file instance than we will // automatically store the file using a stream. This provides a convenient path @@ -753,8 +752,8 @@ public function url($path) protected function getFtpUrl($path) { return isset($this->config['url']) - ? $this->concatPathToUrl($this->config['url'], $path) - : $path; + ? $this->concatPathToUrl($this->config['url'], $path) + : $path; } /** diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index ed139a81eb41..db6f82ddca0a 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -52,7 +52,6 @@ class FilesystemManager implements FactoryContract * Create a new filesystem manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -298,7 +297,16 @@ public function createScopedDriver(array $config) return $this->build(tap( is_string($config['disk']) ? $this->getConfig($config['disk']) : $config['disk'], function (&$parent) use ($config) { - $parent['prefix'] = $config['prefix']; + if (empty($parent['prefix'])) { + $parent['prefix'] = $config['prefix']; + } else { + $separator = $parent['directory_separator'] ?? DIRECTORY_SEPARATOR; + + $parentPrefix = rtrim($parent['prefix'], $separator); + $scopedPrefix = ltrim($config['prefix'], $separator); + + $parent['prefix'] = "{$parentPrefix}{$separator}{$scopedPrefix}"; + } if (isset($config['visibility'])) { $parent['visibility'] = $config['visibility']; diff --git a/src/Illuminate/Filesystem/LocalFilesystemAdapter.php b/src/Illuminate/Filesystem/LocalFilesystemAdapter.php index 37d0e6934872..bca0ea42ea24 100644 --- a/src/Illuminate/Filesystem/LocalFilesystemAdapter.php +++ b/src/Illuminate/Filesystem/LocalFilesystemAdapter.php @@ -87,7 +87,7 @@ public function diskName(string $disk) } /** - * Indiate that signed URLs should serve the corresponding files. + * Indicate that signed URLs should serve the corresponding files. * * @param bool $serve * @param \Closure|null $urlGeneratorResolver diff --git a/src/Illuminate/Filesystem/LockableFile.php b/src/Illuminate/Filesystem/LockableFile.php index 80a3b8a13c51..6afd33012cfd 100644 --- a/src/Illuminate/Filesystem/LockableFile.php +++ b/src/Illuminate/Filesystem/LockableFile.php @@ -32,7 +32,6 @@ class LockableFile * * @param string $path * @param string $mode - * @return void */ public function __construct($path, $mode) { diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index bb9ecc66237c..f5fa95db75a5 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -15,11 +15,11 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", - "symfony/finder": "^7.0.3" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "symfony/finder": "^7.2.0" }, "autoload": { "psr-4": { @@ -31,21 +31,21 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-hash": "Required to use the Filesystem class.", - "illuminate/http": "Required for handling uploaded files (^7.0).", + "illuminate/http": "Required for handling uploaded files (^12.0).", "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/mime": "Required to enable support for guessing extensions (^7.0)." + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/mime": "Required to enable support for guessing extensions (^7.2)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Foundation/AliasLoader.php b/src/Illuminate/Foundation/AliasLoader.php index 5146e443be97..9c8224538483 100755 --- a/src/Illuminate/Foundation/AliasLoader.php +++ b/src/Illuminate/Foundation/AliasLoader.php @@ -36,7 +36,6 @@ class AliasLoader * Create a new AliasLoader instance. * * @param array $aliases - * @return void */ private function __construct($aliases) { @@ -164,7 +163,7 @@ public function register() */ protected function prependToLoaderStack() { - spl_autoload_register([$this, 'load'], true, true); + spl_autoload_register($this->load(...), true, true); } /** diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 7dbfa672041e..0fed290349c5 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.44.1'; + const VERSION = '12.12.0'; /** * The base path for the Laravel installation. @@ -212,7 +212,6 @@ class Application extends Container implements ApplicationContract, CachesConfig * Create a new Illuminate application instance. * * @param string|null $basePath - * @return void */ public function __construct($basePath = null) { @@ -424,14 +423,14 @@ protected function bindPathsInContainer() $this->useBootstrapPath(value(function () { return is_dir($directory = $this->basePath('.laravel')) - ? $directory - : $this->basePath('bootstrap'); + ? $directory + : $this->basePath('bootstrap'); })); $this->useLangPath(value(function () { return is_dir($directory = $this->resourcePath('lang')) - ? $directory - : $this->basePath('lang'); + ? $directory + : $this->basePath('lang'); })); } @@ -1369,8 +1368,8 @@ protected function normalizeCachePath($key, $default) } return Str::startsWith($env, $this->absoluteCachePathPrefixes) - ? $env - : $this->basePath($env); + ? $env + : $this->basePath($env); } /** diff --git a/src/Illuminate/Foundation/Bus/PendingChain.php b/src/Illuminate/Foundation/Bus/PendingChain.php index f1935e2b6e48..bcb381e51617 100644 --- a/src/Illuminate/Foundation/Bus/PendingChain.php +++ b/src/Illuminate/Foundation/Bus/PendingChain.php @@ -61,7 +61,6 @@ class PendingChain * * @param mixed $job * @param array $chain - * @return void */ public function __construct($job, $chain) { @@ -117,8 +116,8 @@ public function delay($delay) public function catch($callback) { $this->catchCallbacks[] = $callback instanceof Closure - ? new SerializableClosure($callback) - : $callback; + ? new SerializableClosure($callback) + : $callback; return $this; } diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index f7f2d0ed71bc..443eb5eddf5a 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -31,7 +31,6 @@ class PendingDispatch * Create a new pending job dispatch. * * @param mixed $job - * @return void */ public function __construct($job) { diff --git a/src/Illuminate/Foundation/CacheBasedMaintenanceMode.php b/src/Illuminate/Foundation/CacheBasedMaintenanceMode.php index 01ff30d4b6a4..630df4f3a530 100644 --- a/src/Illuminate/Foundation/CacheBasedMaintenanceMode.php +++ b/src/Illuminate/Foundation/CacheBasedMaintenanceMode.php @@ -35,7 +35,6 @@ class CacheBasedMaintenanceMode implements MaintenanceMode * @param \Illuminate\Contracts\Cache\Factory $cache * @param string $store * @param string $key - * @return void */ public function __construct(Factory $cache, string $store, string $key) { diff --git a/src/Illuminate/Foundation/Configuration/Exceptions.php b/src/Illuminate/Foundation/Configuration/Exceptions.php index b02753abe4c0..1072a1431196 100644 --- a/src/Illuminate/Foundation/Configuration/Exceptions.php +++ b/src/Illuminate/Foundation/Configuration/Exceptions.php @@ -13,7 +13,6 @@ class Exceptions * Create a new exception handling configuration instance. * * @param \Illuminate\Foundation\Exceptions\Handler $handler - * @return void */ public function __construct(public Handler $handler) { diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php index f6bc12eb2e9e..20b50499aedf 100644 --- a/src/Illuminate/Foundation/Configuration/Middleware.php +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -451,6 +451,7 @@ public function appendToPriorityList($after, $append) public function getGlobalMiddleware() { $middleware = $this->global ?: array_values(array_filter([ + \Illuminate\Http\Middleware\ValidatePathEncoding::class, \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, $this->trustHosts ? \Illuminate\Http\Middleware\TrustHosts::class : null, \Illuminate\Http\Middleware\TrustProxies::class, diff --git a/src/Illuminate/Foundation/Console/AboutCommand.php b/src/Illuminate/Foundation/Console/AboutCommand.php index b7d39b775c43..c6d61b6d8303 100644 --- a/src/Illuminate/Foundation/Console/AboutCommand.php +++ b/src/Illuminate/Foundation/Console/AboutCommand.php @@ -53,7 +53,6 @@ class AboutCommand extends Command * Create a new command instance. * * @param \Illuminate\Support\Composer $composer - * @return void */ public function __construct(Composer $composer) { @@ -165,6 +164,7 @@ protected function gatherApplicationInformation() $formatEnabledStatus = fn ($value) => $value ? 'ENABLED' : 'OFF'; $formatCachedStatus = fn ($value) => $value ? 'CACHED' : 'NOT CACHED'; + $formatStorageLinkedStatus = fn ($value) => $value ? 'LINKED' : 'NOT LINKED'; static::addToSection('Environment', fn () => [ 'Application Name' => config('app.name'), @@ -214,9 +214,30 @@ protected function gatherApplicationInformation() 'Session' => config('session.driver'), ])); + static::addToSection('Storage', fn () => [ + ...$this->determineStoragePathLinkStatus($formatStorageLinkedStatus), + ]); + (new Collection(static::$customDataResolvers))->each->__invoke(); } + /** + * Determine storage symbolic links status. + * + * @param callable $formatStorageLinkedStatus + * @return array + */ + protected function determineStoragePathLinkStatus(callable $formatStorageLinkedStatus): array + { + return (new Collection(config('filesystems.links', []))) + ->mapWithKeys(function ($target, $link) use ($formatStorageLinkedStatus) { + $path = Str::replace(public_path(), '', $link); + + return [public_path($path) => static::format(file_exists($link), console: $formatStorageLinkedStatus)]; + }) + ->toArray(); + } + /** * Determine whether the given directory has PHP files. * diff --git a/src/Illuminate/Foundation/Console/ApiInstallCommand.php b/src/Illuminate/Foundation/Console/ApiInstallCommand.php index aeec5c0c2db2..f8e19c4853f8 100644 --- a/src/Illuminate/Foundation/Console/ApiInstallCommand.php +++ b/src/Illuminate/Foundation/Console/ApiInstallCommand.php @@ -37,7 +37,7 @@ class ApiInstallCommand extends Command /** * Execute the console command. * - * @return int + * @return void */ public function handle() { @@ -67,12 +67,11 @@ public function handle() } if ($this->option('passport')) { - Process::run(array_filter([ + Process::run([ php_binary(), artisan_binary(), 'passport:install', - $this->confirm('Would you like to use UUIDs for all client IDs?') ? '--uuids' : null, - ])); + ]); $this->components->info('API scaffolding installed. Please add the [Laravel\Passport\HasApiTokens] trait to your User model.'); } else { @@ -110,7 +109,7 @@ protected function uncommentApiRoutesFile() $appBootstrapPath, ); } else { - $this->components->warn('Unable to automatically add API route definition to bootstrap file. API route file should be registered manually.'); + $this->components->warn("Unable to automatically add API route definition to [{$appBootstrapPath}]. API route file should be registered manually."); return; } @@ -150,7 +149,7 @@ protected function installSanctum() protected function installPassport() { $this->requireComposerPackages($this->option('composer'), [ - 'laravel/passport:^12.0', + 'laravel/passport:^13.0', ]); } } diff --git a/src/Illuminate/Foundation/Console/CastMakeCommand.php b/src/Illuminate/Foundation/Console/CastMakeCommand.php index 9552472aea6f..b262b40fe396 100644 --- a/src/Illuminate/Foundation/Console/CastMakeCommand.php +++ b/src/Illuminate/Foundation/Console/CastMakeCommand.php @@ -38,8 +38,8 @@ class CastMakeCommand extends GeneratorCommand protected function getStub() { return $this->option('inbound') - ? $this->resolveStubPath('/stubs/cast.inbound.stub') - : $this->resolveStubPath('/stubs/cast.stub'); + ? $this->resolveStubPath('/stubs/cast.inbound.stub') + : $this->resolveStubPath('/stubs/cast.stub'); } /** diff --git a/src/Illuminate/Foundation/Console/CliDumper.php b/src/Illuminate/Foundation/Console/CliDumper.php index 6f5fd9a49886..44cddcb1e204 100644 --- a/src/Illuminate/Foundation/Console/CliDumper.php +++ b/src/Illuminate/Foundation/Console/CliDumper.php @@ -48,7 +48,6 @@ class CliDumper extends BaseCliDumper * @param \Symfony\Component\Console\Output\OutputInterface $output * @param string $basePath * @param string $compiledViewPath - * @return void */ public function __construct($output, $basePath, $compiledViewPath) { diff --git a/src/Illuminate/Foundation/Console/ClosureCommand.php b/src/Illuminate/Foundation/Console/ClosureCommand.php index 2c2eaf4d2744..a9817d4df22d 100644 --- a/src/Illuminate/Foundation/Console/ClosureCommand.php +++ b/src/Illuminate/Foundation/Console/ClosureCommand.php @@ -30,7 +30,6 @@ class ClosureCommand extends Command * * @param string $signature * @param \Closure $callback - * @return void */ public function __construct($signature, Closure $callback) { diff --git a/src/Illuminate/Foundation/Console/ComponentMakeCommand.php b/src/Illuminate/Foundation/Console/ComponentMakeCommand.php index 4c2f2eac81ad..221ef95caecb 100644 --- a/src/Illuminate/Foundation/Console/ComponentMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ComponentMakeCommand.php @@ -154,8 +154,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/ConfigCacheCommand.php b/src/Illuminate/Foundation/Console/ConfigCacheCommand.php index 1de90e95839e..6cef3389ea64 100644 --- a/src/Illuminate/Foundation/Console/ConfigCacheCommand.php +++ b/src/Illuminate/Foundation/Console/ConfigCacheCommand.php @@ -37,7 +37,6 @@ class ConfigCacheCommand extends Command * Create a new config cache command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/ConfigClearCommand.php b/src/Illuminate/Foundation/Console/ConfigClearCommand.php index 47a978244a1f..e88e2432c034 100644 --- a/src/Illuminate/Foundation/Console/ConfigClearCommand.php +++ b/src/Illuminate/Foundation/Console/ConfigClearCommand.php @@ -34,7 +34,6 @@ class ConfigClearCommand extends Command * Create a new config clear command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/EnumMakeCommand.php b/src/Illuminate/Foundation/Console/EnumMakeCommand.php index a7cfb87c9318..fab08bb9433a 100644 --- a/src/Illuminate/Foundation/Console/EnumMakeCommand.php +++ b/src/Illuminate/Foundation/Console/EnumMakeCommand.php @@ -57,8 +57,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php b/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php index 37829f0d2571..1146489800c9 100644 --- a/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php +++ b/src/Illuminate/Foundation/Console/EnvironmentDecryptCommand.php @@ -46,7 +46,6 @@ class EnvironmentDecryptCommand extends Command * Create a new command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { @@ -77,8 +76,8 @@ public function handle() $key = $this->parseKey($key); $encryptedFile = ($this->option('env') - ? Str::finish(dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR).'.env.'.$this->option('env') - : $this->laravel->environmentFilePath()).'.encrypted'; + ? Str::finish(dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR).'.env.'.$this->option('env') + : $this->laravel->environmentFilePath()).'.encrypted'; $outputFile = $this->outputFilePath(); diff --git a/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php b/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php index 03cafa97760c..a66d18f058db 100644 --- a/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php +++ b/src/Illuminate/Foundation/Console/EnvironmentEncryptCommand.php @@ -45,7 +45,6 @@ class EnvironmentEncryptCommand extends Command * Create a new command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { @@ -83,8 +82,8 @@ public function handle() $keyPassed = $key !== null; $environmentFile = $this->option('env') - ? Str::finish(dirname($this->laravel->environmentFilePath()), DIRECTORY_SEPARATOR).'.env.'.$this->option('env') - : $this->laravel->environmentFilePath(); + ? 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/EventClearCommand.php b/src/Illuminate/Foundation/Console/EventClearCommand.php index 966a18bcc3b2..61c503308609 100644 --- a/src/Illuminate/Foundation/Console/EventClearCommand.php +++ b/src/Illuminate/Foundation/Console/EventClearCommand.php @@ -34,7 +34,6 @@ class EventClearCommand extends Command * Create a new config clear command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/EventListCommand.php b/src/Illuminate/Foundation/Console/EventListCommand.php index d9e5c8f89b24..b6dfd9f63065 100644 --- a/src/Illuminate/Foundation/Console/EventListCommand.php +++ b/src/Illuminate/Foundation/Console/EventListCommand.php @@ -18,7 +18,9 @@ class EventListCommand extends Command * * @var string */ - protected $signature = 'event:list {--event= : Filter the events by name}'; + protected $signature = 'event:list + {--event= : Filter the events by name} + {--json : Output the events and listeners as JSON}'; /** * The console command description. @@ -44,11 +46,48 @@ public function handle() $events = $this->getEvents()->sortKeys(); if ($events->isEmpty()) { - $this->components->info("Your application doesn't have any events matching the given criteria."); + if ($this->option('json')) { + $this->output->writeln('[]'); + } else { + $this->components->info("Your application doesn't have any events matching the given criteria."); + } return; } + if ($this->option('json')) { + $this->displayJson($events); + } else { + $this->displayForCli($events); + } + } + + /** + * Display events and their listeners in JSON. + * + * @param \Illuminate\Support\Collection $events + * @return void + */ + protected function displayJson(Collection $events) + { + $data = $events->map(function ($listeners, $event) { + return [ + 'event' => strip_tags($this->appendEventInterfaces($event)), + 'listeners' => collect($listeners)->map(fn ($listener) => strip_tags($listener))->values()->all(), + ]; + })->values(); + + $this->output->writeln($data->toJson()); + } + + /** + * Display the events and their listeners for the CLI. + * + * @param \Illuminate\Support\Collection $events + * @return void + */ + protected function displayForCli(Collection $events) + { $this->newLine(); $events->each(function ($listeners, $event) { diff --git a/src/Illuminate/Foundation/Console/EventMakeCommand.php b/src/Illuminate/Foundation/Console/EventMakeCommand.php index cdecc89fb0ad..515c6fbd8c43 100644 --- a/src/Illuminate/Foundation/Console/EventMakeCommand.php +++ b/src/Illuminate/Foundation/Console/EventMakeCommand.php @@ -61,8 +61,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/JobMakeCommand.php b/src/Illuminate/Foundation/Console/JobMakeCommand.php index 39236c8e504e..9f0f1b0e9ffc 100644 --- a/src/Illuminate/Foundation/Console/JobMakeCommand.php +++ b/src/Illuminate/Foundation/Console/JobMakeCommand.php @@ -41,8 +41,8 @@ class JobMakeCommand extends GeneratorCommand protected function getStub() { return $this->option('sync') - ? $this->resolveStubPath('/stubs/job.stub') - : $this->resolveStubPath('/stubs/job.queued.stub'); + ? $this->resolveStubPath('/stubs/job.stub') + : $this->resolveStubPath('/stubs/job.queued.stub'); } /** @@ -54,8 +54,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/JobMiddlewareMakeCommand.php b/src/Illuminate/Foundation/Console/JobMiddlewareMakeCommand.php index b3ccf8e45ce4..5f94b835956f 100644 --- a/src/Illuminate/Foundation/Console/JobMiddlewareMakeCommand.php +++ b/src/Illuminate/Foundation/Console/JobMiddlewareMakeCommand.php @@ -52,8 +52,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/Kernel.php b/src/Illuminate/Foundation/Console/Kernel.php index 0ec8e312e254..55874df4d9b0 100644 --- a/src/Illuminate/Foundation/Console/Kernel.php +++ b/src/Illuminate/Foundation/Console/Kernel.php @@ -131,7 +131,6 @@ class Kernel implements KernelContract * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Contracts\Events\Dispatcher $events - * @return void */ public function __construct(Application $app, Dispatcher $events) { @@ -163,13 +162,13 @@ public function rerouteSymfonyCommandEvents() $this->symfonyDispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { $this->events->dispatch( - new CommandStarting($event->getCommand()->getName(), $event->getInput(), $event->getOutput()) + new CommandStarting($event->getCommand()?->getName() ?? '', $event->getInput(), $event->getOutput()) ); }); $this->symfonyDispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) { $this->events->dispatch( - new CommandFinished($event->getCommand()->getName(), $event->getInput(), $event->getOutput(), $event->getExitCode()) + new CommandFinished($event->getCommand()?->getName() ?? '', $event->getInput(), $event->getOutput(), $event->getExitCode()) ); }); } diff --git a/src/Illuminate/Foundation/Console/ListenerMakeCommand.php b/src/Illuminate/Foundation/Console/ListenerMakeCommand.php index d5589ece59a9..a2d2dfaa5535 100644 --- a/src/Illuminate/Foundation/Console/ListenerMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ListenerMakeCommand.php @@ -74,8 +74,8 @@ protected function buildClass($name) protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** @@ -87,13 +87,13 @@ protected function getStub() { if ($this->option('queued')) { return $this->option('event') - ? $this->resolveStubPath('/stubs/listener.typed.queued.stub') - : $this->resolveStubPath('/stubs/listener.queued.stub'); + ? $this->resolveStubPath('/stubs/listener.typed.queued.stub') + : $this->resolveStubPath('/stubs/listener.queued.stub'); } return $this->option('event') - ? $this->resolveStubPath('/stubs/listener.typed.stub') - : $this->resolveStubPath('/stubs/listener.stub'); + ? $this->resolveStubPath('/stubs/listener.typed.stub') + : $this->resolveStubPath('/stubs/listener.stub'); } /** diff --git a/src/Illuminate/Foundation/Console/ModelMakeCommand.php b/src/Illuminate/Foundation/Console/ModelMakeCommand.php index 743fd170f9fa..5fd029b8cad8 100644 --- a/src/Illuminate/Foundation/Console/ModelMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ModelMakeCommand.php @@ -211,8 +211,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/PolicyMakeCommand.php b/src/Illuminate/Foundation/Console/PolicyMakeCommand.php index a48eefe2c30d..521c135b062b 100644 --- a/src/Illuminate/Foundation/Console/PolicyMakeCommand.php +++ b/src/Illuminate/Foundation/Console/PolicyMakeCommand.php @@ -161,8 +161,8 @@ protected function replaceModel($stub, $model) protected function getStub() { return $this->option('model') - ? $this->resolveStubPath('/stubs/policy.stub') - : $this->resolveStubPath('/stubs/policy.plain.stub'); + ? $this->resolveStubPath('/stubs/policy.stub') + : $this->resolveStubPath('/stubs/policy.plain.stub'); } /** @@ -174,8 +174,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/QueuedCommand.php b/src/Illuminate/Foundation/Console/QueuedCommand.php index 43848cc263e3..eef9cacc8754 100644 --- a/src/Illuminate/Foundation/Console/QueuedCommand.php +++ b/src/Illuminate/Foundation/Console/QueuedCommand.php @@ -22,7 +22,6 @@ class QueuedCommand implements ShouldQueue * Create a new job instance. * * @param array $data - * @return void */ public function __construct($data) { diff --git a/src/Illuminate/Foundation/Console/RequestMakeCommand.php b/src/Illuminate/Foundation/Console/RequestMakeCommand.php index ad02e29f175e..a60b0c69728a 100644 --- a/src/Illuminate/Foundation/Console/RequestMakeCommand.php +++ b/src/Illuminate/Foundation/Console/RequestMakeCommand.php @@ -49,8 +49,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php index e8fd022568eb..51d96120cc00 100644 --- a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php @@ -52,8 +52,8 @@ public function handle() protected function getStub() { return $this->collection() - ? $this->resolveStubPath('/stubs/resource-collection.stub') - : $this->resolveStubPath('/stubs/resource.stub'); + ? $this->resolveStubPath('/stubs/resource-collection.stub') + : $this->resolveStubPath('/stubs/resource.stub'); } /** @@ -76,8 +76,8 @@ protected function collection() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/RouteCacheCommand.php b/src/Illuminate/Foundation/Console/RouteCacheCommand.php index 0aa21941edfa..9b8632af50b2 100644 --- a/src/Illuminate/Foundation/Console/RouteCacheCommand.php +++ b/src/Illuminate/Foundation/Console/RouteCacheCommand.php @@ -36,7 +36,6 @@ class RouteCacheCommand extends Command * Create a new route command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/RouteClearCommand.php b/src/Illuminate/Foundation/Console/RouteClearCommand.php index c496b10e73fc..b077f820076c 100644 --- a/src/Illuminate/Foundation/Console/RouteClearCommand.php +++ b/src/Illuminate/Foundation/Console/RouteClearCommand.php @@ -34,7 +34,6 @@ class RouteClearCommand extends Command * Create a new route clear command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/RouteListCommand.php b/src/Illuminate/Foundation/Console/RouteListCommand.php index 104bff78af28..36813d87d1a5 100644 --- a/src/Illuminate/Foundation/Console/RouteListCommand.php +++ b/src/Illuminate/Foundation/Console/RouteListCommand.php @@ -76,7 +76,6 @@ class RouteListCommand extends Command * Create a new route command instance. * * @param \Illuminate\Routing\Router $router - * @return void */ public function __construct(Router $router) { @@ -114,9 +113,10 @@ public function handle() */ protected function getRoutes() { - $routes = (new Collection($this->router->getRoutes()))->map(function ($route) { - return $this->getRouteInformation($route); - })->filter()->all(); + $routes = (new Collection($this->router->getRoutes())) + ->map(fn ($route) => $this->getRouteInformation($route)) + ->filter() + ->all(); if (($sort = $this->option('sort')) !== null) { $routes = $this->sortRoutes($sort, $routes); @@ -208,9 +208,9 @@ protected function displayRoutes(array $routes) */ protected function getMiddleware($route) { - return (new Collection($this->router->gatherRouteMiddleware($route)))->map(function ($middleware) { - return $middleware instanceof Closure ? 'Closure' : $middleware; - })->implode("\n"); + return (new Collection($this->router->gatherRouteMiddleware($route))) + ->map(fn ($middleware) => $middleware instanceof Closure ? 'Closure' : $middleware) + ->implode("\n"); } /** @@ -301,7 +301,7 @@ protected function getHeaders() */ protected function getColumns() { - return array_map('strtolower', $this->headers); + return array_map(strtolower(...), $this->headers); } /** @@ -322,7 +322,7 @@ protected function parseColumns(array $columns) } } - return array_map('strtolower', $results); + return array_map(strtolower(...), $results); } /** diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index 6b86bb65fa7a..e722046dd2ed 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -335,7 +335,7 @@ protected function flushOutputBuffer() } elseif ((new Stringable($line))->contains(' Closing')) { $requestPort = static::getRequestPortFromLine($line); - if (empty($this->requestsPool[$requestPort])) { + if (empty($this->requestsPool[$requestPort]) || count($this->requestsPool[$requestPort] ?? []) !== 3) { $this->requestsPool[$requestPort] = [ $this->getDateFromLine($line), false, diff --git a/src/Illuminate/Foundation/Console/TestMakeCommand.php b/src/Illuminate/Foundation/Console/TestMakeCommand.php index 85440589f52d..e24766a9021c 100644 --- a/src/Illuminate/Foundation/Console/TestMakeCommand.php +++ b/src/Illuminate/Foundation/Console/TestMakeCommand.php @@ -58,8 +58,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/VendorPublishCommand.php b/src/Illuminate/Foundation/Console/VendorPublishCommand.php index 3921c9ca05f5..1ccab52ec83b 100644 --- a/src/Illuminate/Foundation/Console/VendorPublishCommand.php +++ b/src/Illuminate/Foundation/Console/VendorPublishCommand.php @@ -79,7 +79,6 @@ class VendorPublishCommand extends Command * Create a new command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/ViewClearCommand.php b/src/Illuminate/Foundation/Console/ViewClearCommand.php index 9cb58e23bf99..ec942bddb998 100644 --- a/src/Illuminate/Foundation/Console/ViewClearCommand.php +++ b/src/Illuminate/Foundation/Console/ViewClearCommand.php @@ -35,7 +35,6 @@ class ViewClearCommand extends Command * Create a new config clear command instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/Foundation/Console/ViewMakeCommand.php b/src/Illuminate/Foundation/Console/ViewMakeCommand.php index afd21e9bc456..c06675041373 100644 --- a/src/Illuminate/Foundation/Console/ViewMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ViewMakeCommand.php @@ -104,8 +104,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Foundation/Console/stubs/class.invokable.stub b/src/Illuminate/Foundation/Console/stubs/class.invokable.stub index c55610cfe4a6..b1e93cb728d7 100644 --- a/src/Illuminate/Foundation/Console/stubs/class.invokable.stub +++ b/src/Illuminate/Foundation/Console/stubs/class.invokable.stub @@ -17,6 +17,6 @@ class {{ class }} */ public function __invoke(): void { - + // } } diff --git a/src/Illuminate/Foundation/Console/stubs/notification.stub b/src/Illuminate/Foundation/Console/stubs/notification.stub index e573dcd271e9..ee32826b70a5 100644 --- a/src/Illuminate/Foundation/Console/stubs/notification.stub +++ b/src/Illuminate/Foundation/Console/stubs/notification.stub @@ -35,9 +35,9 @@ class {{ class }} extends Notification public function toMail(object $notifiable): MailMessage { return (new MailMessage) - ->line('The introduction to the notification.') - ->action('Notification Action', url('/')) - ->line('Thank you for using our application!'); + ->line('The introduction to the notification.') + ->action('Notification Action', url('/')) + ->line('Thank you for using our application!'); } /** diff --git a/src/Illuminate/Foundation/Events/LocaleUpdated.php b/src/Illuminate/Foundation/Events/LocaleUpdated.php index 0a932be06ed2..c46d33ccc245 100644 --- a/src/Illuminate/Foundation/Events/LocaleUpdated.php +++ b/src/Illuminate/Foundation/Events/LocaleUpdated.php @@ -15,7 +15,6 @@ class LocaleUpdated * Create a new event instance. * * @param string $locale - * @return void */ public function __construct($locale) { diff --git a/src/Illuminate/Foundation/Events/PublishingStubs.php b/src/Illuminate/Foundation/Events/PublishingStubs.php index 914ff1e40d65..854327982ed4 100644 --- a/src/Illuminate/Foundation/Events/PublishingStubs.php +++ b/src/Illuminate/Foundation/Events/PublishingStubs.php @@ -17,7 +17,6 @@ class PublishingStubs * Create a new event instance. * * @param array $stubs - * @return void */ public function __construct(array $stubs) { diff --git a/src/Illuminate/Foundation/Events/VendorTagPublished.php b/src/Illuminate/Foundation/Events/VendorTagPublished.php index 084c1293fcfd..05acc79cd70a 100644 --- a/src/Illuminate/Foundation/Events/VendorTagPublished.php +++ b/src/Illuminate/Foundation/Events/VendorTagPublished.php @@ -23,7 +23,6 @@ class VendorTagPublished * * @param string $tag * @param array $paths - * @return void */ public function __construct($tag, $paths) { diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 2f381d1ef6b7..00a69266dcc7 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -184,7 +184,6 @@ class Handler implements ExceptionHandlerContract * Create a new exception handler instance. * * @param \Illuminate\Contracts\Container\Container $container - * @return void */ public function __construct(Container $container) { @@ -424,7 +423,7 @@ protected function shouldntReport(Throwable $e) } return ! $this->container->make(RateLimiter::class)->attempt( - with($throttle->key ?: 'illuminate:foundation:exceptions:'.$e::class, fn ($key) => $this->hashThrottleKeys ? md5($key) : $key), + with($throttle->key ?: 'illuminate:foundation:exceptions:'.$e::class, fn ($key) => $this->hashThrottleKeys ? hash('xxh128', $key) : $key), $throttle->maxAttempts, fn () => true, $throttle->decaySeconds @@ -483,10 +482,14 @@ public function stopIgnoring(array|string $exceptions) $exceptions = Arr::wrap($exceptions); $this->dontReport = (new Collection($this->dontReport)) - ->reject(fn ($ignored) => in_array($ignored, $exceptions))->values()->all(); + ->reject(fn ($ignored) => in_array($ignored, $exceptions)) + ->values() + ->all(); $this->internalDontReport = (new Collection($this->internalDontReport)) - ->reject(fn ($ignored) => in_array($ignored, $exceptions))->values()->all(); + ->reject(fn ($ignored) => in_array($ignored, $exceptions)) + ->values() + ->all(); return $this; } @@ -702,8 +705,8 @@ protected function renderViaCallbacks($request, Throwable $e) protected function renderExceptionResponse($request, Throwable $e) { return $this->shouldReturnJson($request, $e) - ? $this->prepareJsonResponse($request, $e) - : $this->prepareResponse($request, $e); + ? $this->prepareJsonResponse($request, $e) + : $this->prepareResponse($request, $e); } /** @@ -716,8 +719,8 @@ protected function renderExceptionResponse($request, Throwable $e) protected function unauthenticated($request, AuthenticationException $exception) { return $this->shouldReturnJson($request, $exception) - ? response()->json(['message' => $exception->getMessage()], 401) - : redirect()->guest($exception->redirectTo($request) ?? route('login')); + ? response()->json(['message' => $exception->getMessage()], 401) + : redirect()->guest($exception->redirectTo($request) ?? route('login')); } /** @@ -734,8 +737,8 @@ protected function convertValidationExceptionToResponse(ValidationException $e, } return $this->shouldReturnJson($request, $e) - ? $this->invalidJson($request, $e) - : $this->invalid($request, $e); + ? $this->invalidJson($request, $e) + : $this->invalid($request, $e); } /** diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php b/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php index 3c562e05578c..eb65929f3150 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Exception.php @@ -47,7 +47,6 @@ class Exception * @param \Illuminate\Http\Request $request * @param \Illuminate\Foundation\Exceptions\Renderer\Listener $listener * @param string $basePath - * @return void */ public function __construct(FlattenException $exception, Request $request, Listener $listener, string $basePath) { diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php b/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php index ac331d7a2ec9..efd6d7a62f63 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Frame.php @@ -44,7 +44,6 @@ class Frame * @param array $classMap * @param array{file: string, line: int, class?: string, type?: string, function?: string} $frame * @param string $basePath - * @return void */ public function __construct(FlattenException $exception, array $classMap, array $frame, string $basePath) { diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php b/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php index 325fad4c20ac..d5a71b7d9178 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php @@ -28,7 +28,7 @@ class Listener */ public function registerListeners(Dispatcher $events) { - $events->listen(QueryExecuted::class, [$this, 'onQueryExecuted']); + $events->listen(QueryExecuted::class, $this->onQueryExecuted(...)); $events->listen([JobProcessing::class, JobProcessed::class], function () { $this->queries = []; diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php b/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php index 25b6b71377ac..a235812cb3fe 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php @@ -63,7 +63,6 @@ class BladeMapper * * @param \Illuminate\Contracts\View\Factory $factory * @param \Illuminate\View\Compilers\BladeCompiler $bladeCompiler - * @return void */ public function __construct(Factory $factory, BladeCompiler $bladeCompiler) { diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php b/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php index 6772ae1d18ca..514be710840e 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Renderer.php @@ -61,7 +61,6 @@ class Renderer * @param \Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer $htmlErrorRenderer * @param \Illuminate\Foundation\Exceptions\Renderer\Mappers\BladeMapper $bladeMapper * @param string $basePath - * @return void */ public function __construct( Factory $viewFactory, diff --git a/src/Illuminate/Foundation/Exceptions/ReportableHandler.php b/src/Illuminate/Foundation/Exceptions/ReportableHandler.php index 06a6172f5c03..f7193654e250 100644 --- a/src/Illuminate/Foundation/Exceptions/ReportableHandler.php +++ b/src/Illuminate/Foundation/Exceptions/ReportableHandler.php @@ -27,7 +27,6 @@ class ReportableHandler * Create a new reportable handler instance. * * @param callable $callback - * @return void */ public function __construct(callable $callback) { diff --git a/src/Illuminate/Foundation/Http/Events/RequestHandled.php b/src/Illuminate/Foundation/Http/Events/RequestHandled.php index d6f71e03fa16..3e99cb8321f3 100644 --- a/src/Illuminate/Foundation/Http/Events/RequestHandled.php +++ b/src/Illuminate/Foundation/Http/Events/RequestHandled.php @@ -23,7 +23,6 @@ class RequestHandled * * @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Response $response - * @return void */ public function __construct($request, $response) { diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index edff3035af98..b5beb3af52f2 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -86,7 +86,7 @@ protected function getValidatorInstance() $factory = $this->container->make(ValidationFactory::class); if (method_exists($this, 'validator')) { - $validator = $this->container->call([$this, 'validator'], compact('factory')); + $validator = $this->container->call($this->validator(...), compact('factory')); } else { $validator = $this->createDefaultValidator($factory); } @@ -229,8 +229,8 @@ protected function failedAuthorization() public function safe(?array $keys = null) { return is_array($keys) - ? $this->validator->safe()->only($keys) - : $this->validator->safe(); + ? $this->validator->safe()->only($keys) + : $this->validator->safe(); } /** diff --git a/src/Illuminate/Foundation/Http/HtmlDumper.php b/src/Illuminate/Foundation/Http/HtmlDumper.php index 2df09013fe65..72663f0ec1b6 100644 --- a/src/Illuminate/Foundation/Http/HtmlDumper.php +++ b/src/Illuminate/Foundation/Http/HtmlDumper.php @@ -53,7 +53,6 @@ class HtmlDumper extends BaseHtmlDumper * * @param string $basePath * @param string $compiledViewPath - * @return void */ public function __construct($basePath, $compiledViewPath) { diff --git a/src/Illuminate/Foundation/Http/Kernel.php b/src/Illuminate/Foundation/Http/Kernel.php index 02c0f3fdbd95..83a5ae42780c 100644 --- a/src/Illuminate/Foundation/Http/Kernel.php +++ b/src/Illuminate/Foundation/Http/Kernel.php @@ -119,7 +119,6 @@ class Kernel implements KernelContract * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Routing\Router $router - * @return void */ public function __construct(Application $app, Router $router) { diff --git a/src/Illuminate/Foundation/Http/Middleware/HandlePrecognitiveRequests.php b/src/Illuminate/Foundation/Http/Middleware/HandlePrecognitiveRequests.php index 32819c45a663..c13e2bf12277 100644 --- a/src/Illuminate/Foundation/Http/Middleware/HandlePrecognitiveRequests.php +++ b/src/Illuminate/Foundation/Http/Middleware/HandlePrecognitiveRequests.php @@ -21,7 +21,6 @@ class HandlePrecognitiveRequests * Create a new middleware instance. * * @param \Illuminate\Container\Container $container - * @return void */ public function __construct(Container $container) { diff --git a/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php b/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php index 6adb87d01161..1c20d22051b1 100644 --- a/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php +++ b/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -39,7 +39,6 @@ class PreventRequestsDuringMaintenance * Create a new middleware instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct(Application $app) { @@ -83,8 +82,8 @@ public function handle($request, Closure $next) if (isset($data['redirect'])) { $path = $data['redirect'] === '/' - ? $data['redirect'] - : trim($data['redirect'], '/'); + ? $data['redirect'] + : trim($data['redirect'], '/'); if ($request->path() !== $path) { return redirect($path); diff --git a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php index 5edc53d38f57..7a47b4fbe471 100644 --- a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php +++ b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -60,7 +60,6 @@ class VerifyCsrfToken * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @return void */ public function __construct(Application $app, Encrypter $encrypter) { diff --git a/src/Illuminate/Foundation/Inspiring.php b/src/Illuminate/Foundation/Inspiring.php index 6b44751e74f3..59891493d9b5 100644 --- a/src/Illuminate/Foundation/Inspiring.php +++ b/src/Illuminate/Foundation/Inspiring.php @@ -56,9 +56,7 @@ class Inspiring */ public static function quote() { - return static::quotes() - ->map(fn ($quote) => static::formatForConsole($quote)) - ->random(); + return static::formatForConsole(static::quotes()->random()); } /** diff --git a/src/Illuminate/Foundation/PackageManifest.php b/src/Illuminate/Foundation/PackageManifest.php index c569b7740fb4..d2494db25993 100644 --- a/src/Illuminate/Foundation/PackageManifest.php +++ b/src/Illuminate/Foundation/PackageManifest.php @@ -50,7 +50,6 @@ class PackageManifest * @param \Illuminate\Filesystem\Filesystem $files * @param string $basePath * @param string $manifestPath - * @return void */ public function __construct(Filesystem $files, $basePath, $manifestPath) { @@ -88,9 +87,10 @@ public function aliases() */ public function config($key) { - return (new Collection($this->getManifest()))->flatMap(function ($configuration) use ($key) { - return (array) ($configuration[$key] ?? []); - })->filter()->all(); + return (new Collection($this->getManifest())) + ->flatMap(fn ($configuration) => (array) ($configuration[$key] ?? [])) + ->filter() + ->all(); } /** diff --git a/src/Illuminate/Foundation/ProviderRepository.php b/src/Illuminate/Foundation/ProviderRepository.php index 75d382d83d29..df76e054bb41 100755 --- a/src/Illuminate/Foundation/ProviderRepository.php +++ b/src/Illuminate/Foundation/ProviderRepository.php @@ -35,7 +35,6 @@ class ProviderRepository * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Filesystem\Filesystem $files * @param string $manifestPath - * @return void */ public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath) { diff --git a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php index 788d6ed54b4b..d5074505302d 100644 --- a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php @@ -124,8 +124,8 @@ public function getEvents() protected function discoveredEvents() { return $this->shouldDiscoverEvents() - ? $this->discoverEvents() - : []; + ? $this->discoverEvents() + : []; } /** diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php index d6bcf8f23530..af1739d1c07a 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php @@ -213,4 +213,31 @@ protected function assertThrows(Closure $test, string|Closure $expectedClass = T return $this; } + + /** + * Assert that the given callback does not throw an exception. + * + * @param \Closure $test + * @return $this + */ + protected function assertDoesntThrow(Closure $test) + { + try { + $test(); + + $thrown = false; + } catch (Throwable $exception) { + $thrown = true; + + $exceptionClass = get_class($exception); + $exceptionMessage = $exception->getMessage(); + } + + Assert::assertTrue( + ! $thrown, + sprintf('Unexpected exception of type %s with message %s was thrown.', $exceptionClass ?? null, $exceptionMessage ?? null) + ); + + return $this; + } } diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTime.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTime.php index b431c9b889cf..4eb10714b1e1 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTime.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTime.php @@ -15,7 +15,9 @@ trait InteractsWithTime */ public function freezeTime($callback = null) { - return $this->travelTo(Carbon::now(), $callback); + $result = $this->travelTo($now = Carbon::now(), $callback); + + return is_null($callback) ? $now : $result; } /** @@ -26,7 +28,9 @@ public function freezeTime($callback = null) */ public function freezeSecond($callback = null) { - return $this->travelTo(Carbon::now()->startOfSecond(), $callback); + $result = $this->travelTo($now = Carbon::now()->startOfSecond(), $callback); + + return is_null($callback) ? $now : $result; } /** diff --git a/src/Illuminate/Foundation/Testing/DatabaseTransactions.php b/src/Illuminate/Foundation/Testing/DatabaseTransactions.php index f84a23fe51d4..0eaa2f079457 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTransactions.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTransactions.php @@ -48,6 +48,7 @@ public function beginDatabaseTransaction() protected function connectionsToTransact() { return property_exists($this, 'connectionsToTransact') - ? $this->connectionsToTransact : [null]; + ? $this->connectionsToTransact + : [null]; } } diff --git a/src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php b/src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php index 7ee71d7b9edf..4fea491d51b1 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php @@ -15,7 +15,6 @@ class DatabaseTransactionsManager extends BaseManager * Create a new database transaction manager instance. * * @param array $connectionsTransacting - * @return void */ public function __construct(array $connectionsTransacting) { diff --git a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index 2ef1701917a0..9ed063241a8f 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -4,7 +4,6 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\ConnectionInterface; -use Illuminate\Database\Schema\PostgresBuilder; use Illuminate\Foundation\Testing\Traits\CanConfigureMigrationCommands; use Illuminate\Support\Collection; @@ -94,14 +93,12 @@ function (Collection $tables, array $tablesToTruncate) { function (Collection $tables) use ($connection, $name) { $exceptTables = $this->exceptTables($connection, $name); - return $tables->filter(fn (array $table) => ! $this->tableExistsIn($table, $exceptTables)); + return $tables->reject(fn (array $table) => $this->tableExistsIn($table, $exceptTables)); } ) ->each(function (array $table) use ($connection) { $connection->withoutTablePrefix(function ($connection) use ($table) { - $table = $connection->table( - $table['schema'] ? $table['schema'].'.'.$table['name'] : $table['name'] - ); + $table = $connection->table($table['schema_qualified_name']); if ($table->exists()) { $table->truncate(); @@ -123,12 +120,7 @@ protected function getAllTablesForConnection(ConnectionInterface $connection, ?s $schema = $connection->getSchemaBuilder(); - return static::$allTables[$name] = (new Collection($schema->getTables()))->when( - $schema instanceof PostgresBuilder ? $schema->getSchemas() : null, - fn (Collection $tables, array $schemas) => $tables->filter( - fn (array $table) => in_array($table['schema'], $schemas) - ) - )->all(); + return static::$allTables[$name] = (new Collection($schema->getTables($schema->getCurrentSchemaListing())))->all(); } /** @@ -137,7 +129,7 @@ protected function getAllTablesForConnection(ConnectionInterface $connection, ?s protected function tableExistsIn(array $table, array $tables): bool { return $table['schema'] - ? ! empty(array_intersect([$table['name'], $table['schema'].'.'.$table['name']], $tables)) + ? ! empty(array_intersect([$table['name'], $table['schema_qualified_name']], $tables)) : in_array($table['name'], $tables); } @@ -149,7 +141,8 @@ protected function tableExistsIn(array $table, array $tables): bool protected function connectionsToTruncate(): array { return property_exists($this, 'connectionsToTruncate') - ? $this->connectionsToTruncate : [null]; + ? $this->connectionsToTruncate + : [null]; } /** diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index 2ef1fd0968b5..f039c510f8c2 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -18,7 +18,7 @@ public function refreshDatabase() { $this->beforeRefreshingDatabase(); - if ($this->usingInMemoryDatabase()) { + if ($this->usingInMemoryDatabases()) { $this->restoreInMemoryDatabase(); } @@ -28,15 +28,33 @@ public function refreshDatabase() } /** - * Determine if an in-memory database is being used. + * Determine if any of the connections transacting is using in-memory databases. * * @return bool */ - protected function usingInMemoryDatabase() + protected function usingInMemoryDatabases() { - $default = config('database.default'); + foreach ($this->connectionsToTransact() as $name) { + if ($this->usingInMemoryDatabase($name)) { + return true; + } + } - return config("database.connections.$default.database") === ':memory:'; + return false; + } + + /** + * Determine if a given database connection is an in-memory database. + * + * @return bool + */ + protected function usingInMemoryDatabase(?string $name = null) + { + if (is_null($name)) { + $name = config('database.default'); + } + + return config("database.connections.{$name}.database") === ':memory:'; } /** @@ -63,7 +81,7 @@ protected function restoreInMemoryDatabase() protected function refreshTestDatabase() { if (! RefreshDatabaseState::$migrated) { - $this->artisan('migrate:fresh', $this->migrateFreshUsing()); + $this->migrateDatabases(); $this->app[Kernel::class]->setArtisan(null); @@ -73,6 +91,16 @@ protected function refreshTestDatabase() $this->beginDatabaseTransaction(); } + /** + * Migrate the database. + * + * @return void + */ + protected function migrateDatabases() + { + $this->artisan('migrate:fresh', $this->migrateFreshUsing()); + } + /** * Begin a database transaction on the testing database. * @@ -91,7 +119,7 @@ public function beginDatabaseTransaction() $connection->setTransactionManager($transactionsManager); - if ($this->usingInMemoryDatabase()) { + if ($this->usingInMemoryDatabase($name)) { RefreshDatabaseState::$inMemoryConnections[$name] ??= $connection->getPdo(); } @@ -128,7 +156,8 @@ public function beginDatabaseTransaction() protected function connectionsToTransact() { return property_exists($this, 'connectionsToTransact') - ? $this->connectionsToTransact : [null]; + ? $this->connectionsToTransact + : [config('database.default')]; } /** diff --git a/src/Illuminate/Foundation/Testing/Wormhole.php b/src/Illuminate/Foundation/Testing/Wormhole.php index beac013ab34c..3bf5d2e89af3 100644 --- a/src/Illuminate/Foundation/Testing/Wormhole.php +++ b/src/Illuminate/Foundation/Testing/Wormhole.php @@ -17,7 +17,6 @@ class Wormhole * Create a new wormhole instance. * * @param int $value - * @return void */ public function __construct($value) { diff --git a/src/Illuminate/Foundation/Vite.php b/src/Illuminate/Foundation/Vite.php index b77ea8ed0e51..7b7de434c27a 100644 --- a/src/Illuminate/Foundation/Vite.php +++ b/src/Illuminate/Foundation/Vite.php @@ -1023,4 +1023,14 @@ public function toHtml() { return $this->__invoke($this->entryPoints)->toHtml(); } + + /** + * Flush state. + * + * @return void + */ + public function flush() + { + $this->preloadedAssets = []; + } } diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index 90fd06f44f0d..8f0523dce09f 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -20,7 +20,10 @@ use Illuminate\Queue\CallQueuedClosure; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Route; use Illuminate\Support\HtmlString; +use Illuminate\Support\Uri; +use League\Uri\Contracts\UriInterface; use Symfony\Component\HttpFoundation\Response; if (! function_exists('abort')) { @@ -424,8 +427,8 @@ function defer(?callable $callback = null, ?string $name = null, bool $always = function dispatch($job) { return $job instanceof Closure - ? new PendingClosureDispatch(CallQueuedClosure::create($job)) - : new PendingDispatch($job); + ? new PendingClosureDispatch(CallQueuedClosure::create($job)) + : new PendingDispatch($job); } } @@ -1001,9 +1004,23 @@ function __($key = null, $replace = [], $locale = null) } } +if (! function_exists('uri')) { + /** + * Generate a URI for the application. + */ + function uri(UriInterface|Stringable|array|string $uri, mixed $parameters = [], bool $absolute = true): Uri + { + return match (true) { + is_array($uri) || str_contains($uri, '\\') => Uri::action($uri, $parameters, $absolute), + str_contains($uri, '.') && Route::has($uri) => Uri::route($uri, $parameters, $absolute), + default => Uri::of($uri), + }; + } +} + if (! function_exists('url')) { /** - * Generate a url for the application. + * Generate a URL for the application. * * @param string|null $path * @param mixed $parameters diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php b/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php index 1930737be2db..3c0b2ddb6760 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php @@ -69,7 +69,7 @@ class="w-full text-left dark:border-gray-900" - @if (! $frame->isFromVendor() && $exception->frames()->slice($loop->index + 1)->filter(fn ($frame) => ! $frame->isFromVendor())->isEmpty()) + @if (! $frame->isFromVendor() && $exception->frames()->slice($loop->index + 1)->reject(fn ($frame) => $frame->isFromVendor())->isEmpty()) @if ($exception->frames()->slice($loop->index + 1)->count())
diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/dist/styles.css b/src/Illuminate/Foundation/resources/exceptions/renderer/dist/styles.css index 0e5a2eeb8677..f0dc676d1bc4 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/dist/styles.css +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/dist/styles.css @@ -1 +1 @@ -.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}.tippy-box[data-theme~=material]{background-color:#505355;font-weight:600}.tippy-box[data-theme~=material][data-placement^=top]>.tippy-arrow:before{border-top-color:#505355}.tippy-box[data-theme~=material][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#505355}.tippy-box[data-theme~=material][data-placement^=left]>.tippy-arrow:before{border-left-color:#505355}.tippy-box[data-theme~=material][data-placement^=right]>.tippy-arrow:before{border-right-color:#505355}.tippy-box[data-theme~=material]>.tippy-backdrop{background-color:#505355}.tippy-box[data-theme~=material]>.tippy-svg-arrow{fill:#505355}.tippy-box[data-animation=scale][data-placement^=top]{transform-origin:bottom}.tippy-box[data-animation=scale][data-placement^=bottom]{transform-origin:top}.tippy-box[data-animation=scale][data-placement^=left]{transform-origin:right}.tippy-box[data-animation=scale][data-placement^=right]{transform-origin:left}.tippy-box[data-animation=scale][data-state=hidden]{transform:scale(.5);opacity:0}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Figtree,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.right-0{right:0}.z-10{z-index:10}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-3{margin-top:.75rem;margin-bottom:.75rem}.-mt-2{margin-top:-.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-1{margin-left:.25rem}.ml-3{margin-left:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[32\.5rem\]{height:32.5rem}.h-\[35\.5rem\]{height:35.5rem}.max-h-32{max-height:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-\[8rem\]{width:8rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-full{max-width:100%}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.origin-top-right{transform-origin:top right}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-6{gap:1.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.overflow-y-hidden{overflow-y:hidden}.overflow-x-scroll{overflow-x:scroll}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.border{border-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-transparent{border-color:transparent}.border-l-red-500{--tw-border-opacity:1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-gray-200\/80{background-color:#e5e7ebcc}.bg-red-500\/20{background-color:#ef444433}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.fill-red-500{fill:#ef4444}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-12{padding-bottom:3rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:Figtree,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.leading-5{line-height:1.25rem}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68 / var(--tw-text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-xl{--tw-shadow:0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-gray-900\/5{--tw-ring-color:rgb(17 24 39 / .05)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}[x-cloak]{display:none}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}table.hljs-ln{color:inherit;font-size:inherit;border-spacing:2px}pre code.hljs{background:none;padding:.5em 0 0;width:100%}.hljs-ln-line{white-space-collapse:preserve;text-wrap:nowrap}.trace{-webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 4rem),transparent)}.scrollbar-hidden{-ms-overflow-style:none;scrollbar-width:none;overflow-x:scroll}.scrollbar-hidden::-webkit-scrollbar{-webkit-appearance:none;width:0;height:0}.hljs-ln .hljs-ln-numbers{padding:5px;border-right-color:transparent;margin-right:5px}.hljs-ln-n{width:50px}.hljs-ln-numbers{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-align:center;border-right:1px solid #ccc;vertical-align:top;padding-right:5px}.hljs-ln-code{width:100%;padding-left:10px;padding-right:10px}.hljs-ln-code:hover{background-color:#ef444433}.default\:col-span-full:default{grid-column:1 / -1}.default\:row-span-1:default{grid-row:span 1 / span 1}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:bg-gray-100\/75:hover{background-color:#f3f4f6bf}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:text-gray-500:focus{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:border:is(.dark *){border-width:1px}.dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.dark\:border-gray-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(31 41 55 / var(--tw-border-opacity))}.dark\:border-gray-900:is(.dark *){--tw-border-opacity:1;border-color:rgb(17 24 39 / var(--tw-border-opacity))}.dark\:border-l-red-500:is(.dark *){--tw-border-opacity:1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:bg-gray-900\/80:is(.dark *){background-color:#111827cc}.dark\:bg-gray-950\/95:is(.dark *){background-color:#030712f2}.dark\:bg-red-500\/20:is(.dark *){background-color:#ef444433}.dark\:text-gray-100:is(.dark *){--tw-text-opacity:1;color:rgb(243 244 246 / var(--tw-text-opacity))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.dark\:text-gray-600:is(.dark *){--tw-text-opacity:1;color:rgb(75 85 99 / var(--tw-text-opacity))}.dark\:text-gray-950:is(.dark *){--tw-text-opacity:1;color:rgb(3 7 18 / var(--tw-text-opacity))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark\:ring-1:is(.dark *){--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark\:ring-gray-800:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(31 41 55 / var(--tw-ring-opacity))}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}.dark\:hover\:bg-gray-800:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:hover\:bg-gray-800\/75:hover:is(.dark *){background-color:#1f2937bf}.dark\:hover\:text-gray-500:hover:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.dark\:focus\:text-gray-500:focus:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:col-span-1{grid-column:span 1 / span 1}.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:mt-10{margin-top:2.5rem}.sm\:gap-6{gap:1.5rem}.sm\:p-12{padding:3rem}.sm\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media (min-width: 768px){.md\:block{display:block}.md\:inline{display:inline}.md\:flex{display:flex}.md\:hidden{display:none}.md\:min-w-64{min-width:16rem}.md\:max-w-80{max-width:20rem}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-2{gap:.5rem}}@media (min-width: 1024px){.lg\:block{display:block}.lg\:inline-block{display:inline-block}.lg\:w-\[12rem\]{width:12rem}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:text-2xl{font-size:1.5rem;line-height:2rem}.lg\:text-base{font-size:1rem;line-height:1.5rem}.lg\:text-sm{font-size:.875rem;line-height:1.25rem}.default\:lg\:col-span-6:default{grid-column:span 6 / span 6}} +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}.tippy-box[data-theme~=material]{background-color:#505355;font-weight:600}.tippy-box[data-theme~=material][data-placement^=top]>.tippy-arrow:before{border-top-color:#505355}.tippy-box[data-theme~=material][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#505355}.tippy-box[data-theme~=material][data-placement^=left]>.tippy-arrow:before{border-left-color:#505355}.tippy-box[data-theme~=material][data-placement^=right]>.tippy-arrow:before{border-right-color:#505355}.tippy-box[data-theme~=material]>.tippy-backdrop{background-color:#505355}.tippy-box[data-theme~=material]>.tippy-svg-arrow{fill:#505355}.tippy-box[data-animation=scale][data-placement^=top]{transform-origin:bottom}.tippy-box[data-animation=scale][data-placement^=bottom]{transform-origin:top}.tippy-box[data-animation=scale][data-placement^=left]{transform-origin:right}.tippy-box[data-animation=scale][data-placement^=right]{transform-origin:left}.tippy-box[data-animation=scale][data-state=hidden]{transform:scale(.5);opacity:0}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Figtree,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.right-0{right:0}.z-10{z-index:10}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-3{margin-top:.75rem;margin-bottom:.75rem}.-mt-2{margin-top:-.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-1{margin-left:.25rem}.ml-3{margin-left:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[32\.5rem\]{height:32.5rem}.h-\[35\.5rem\]{height:35.5rem}.max-h-32{max-height:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-\[8rem\]{width:8rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-full{max-width:100%}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.origin-top-right{transform-origin:top right}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-6{gap:1.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.overflow-y-hidden{overflow-y:hidden}.overflow-x-scroll{overflow-x:scroll}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.border{border-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-transparent{border-color:transparent}.border-l-red-500{--tw-border-opacity:1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-gray-200\/80{background-color:#e5e7ebcc}.bg-red-500\/20{background-color:#ef444433}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.fill-red-500{fill:#ef4444}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-12{padding-bottom:3rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:Figtree,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.leading-5{line-height:1.25rem}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68 / var(--tw-text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-xl{--tw-shadow:0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-gray-900\/5{--tw-ring-color:rgb(17 24 39 / .05)}[x-cloak]{display:none}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}table.hljs-ln{color:inherit;font-size:inherit;border-spacing:2px}pre code.hljs{background:none;padding:.5em 0 0;width:100%}.hljs-ln-line{white-space-collapse:preserve;text-wrap:nowrap}.trace{-webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 4rem),transparent)}.scrollbar-hidden{-ms-overflow-style:none;scrollbar-width:none;overflow-x:scroll}.scrollbar-hidden::-webkit-scrollbar{-webkit-appearance:none;width:0;height:0}.hljs-ln .hljs-ln-numbers{padding:5px;border-right-color:transparent;margin-right:5px}.hljs-ln-n{width:50px}.hljs-ln-numbers{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-align:center;border-right:1px solid #ccc;vertical-align:top;padding-right:5px}.hljs-ln-code{width:100%;padding-left:10px;padding-right:10px}.hljs-ln-code:hover{background-color:#ef444433}.default\:col-span-full:default{grid-column:1 / -1}.default\:row-span-1:default{grid-row:span 1 / span 1}.hover\:rounded-b-md:hover{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.hover\:rounded-t-md:hover{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:bg-gray-100\/75:hover{background-color:#f3f4f6bf}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:text-gray-500:focus{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:border:is(.dark *){border-width:1px}.dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.dark\:border-gray-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(31 41 55 / var(--tw-border-opacity))}.dark\:border-gray-900:is(.dark *){--tw-border-opacity:1;border-color:rgb(17 24 39 / var(--tw-border-opacity))}.dark\:border-l-red-500:is(.dark *){--tw-border-opacity:1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:bg-gray-900\/80:is(.dark *){background-color:#111827cc}.dark\:bg-gray-950\/95:is(.dark *){background-color:#030712f2}.dark\:bg-red-500\/20:is(.dark *){background-color:#ef444433}.dark\:text-gray-100:is(.dark *){--tw-text-opacity:1;color:rgb(243 244 246 / var(--tw-text-opacity))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.dark\:text-gray-600:is(.dark *){--tw-text-opacity:1;color:rgb(75 85 99 / var(--tw-text-opacity))}.dark\:text-gray-950:is(.dark *){--tw-text-opacity:1;color:rgb(3 7 18 / var(--tw-text-opacity))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark\:ring-1:is(.dark *){--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark\:ring-gray-800:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(31 41 55 / var(--tw-ring-opacity))}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}.dark\:hover\:bg-gray-800:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:hover\:bg-gray-800\/75:hover:is(.dark *){background-color:#1f2937bf}.dark\:hover\:text-gray-500:hover:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.dark\:focus\:text-gray-500:focus:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:col-span-1{grid-column:span 1 / span 1}.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:mt-10{margin-top:2.5rem}.sm\:gap-6{gap:1.5rem}.sm\:p-12{padding:3rem}.sm\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media (min-width: 768px){.md\:block{display:block}.md\:inline{display:inline}.md\:flex{display:flex}.md\:hidden{display:none}.md\:min-w-64{min-width:16rem}.md\:max-w-80{max-width:20rem}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-2{gap:.5rem}}@media (min-width: 1024px){.lg\:block{display:block}.lg\:inline-block{display:inline-block}.lg\:w-\[12rem\]{width:12rem}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:text-2xl{font-size:1.5rem;line-height:2rem}.lg\:text-base{font-size:1rem;line-height:1.5rem}.lg\:text-sm{font-size:.875rem;line-height:1.25rem}.default\:lg\:col-span-6:default{grid-column:span 6 / span 6}} diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json index 5ffbe5243951..45eee2341a84 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json @@ -11,7 +11,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.4.12", + "vite": "^5.4.18", "vite-require": "^0.2.3" } }, @@ -2106,9 +2106,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.12.tgz", - "integrity": "sha512-KwUaKB27TvWwDJr1GjjWthLMATbGEbeWYZIbGZ5qFIsgPP3vWzLu4cVooqhm5/Z2SPDUMjyPVjTztm5tYKwQxA==", + "version": "5.4.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json index 588c6aed9d86..efa13c77fc74 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json @@ -12,7 +12,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.4.12", + "vite": "^5.4.18", "vite-require": "^0.2.3" } } diff --git a/src/Illuminate/Hashing/ArgonHasher.php b/src/Illuminate/Hashing/ArgonHasher.php index 233480ea5fb5..74ff9301ed7b 100644 --- a/src/Illuminate/Hashing/ArgonHasher.php +++ b/src/Illuminate/Hashing/ArgonHasher.php @@ -40,7 +40,6 @@ class ArgonHasher extends AbstractHasher implements HasherContract * Create a new hasher instance. * * @param array $options - * @return void */ public function __construct(array $options = []) { diff --git a/src/Illuminate/Hashing/BcryptHasher.php b/src/Illuminate/Hashing/BcryptHasher.php index 237d588f7a29..efe0c50aa44b 100755 --- a/src/Illuminate/Hashing/BcryptHasher.php +++ b/src/Illuminate/Hashing/BcryptHasher.php @@ -4,6 +4,7 @@ use Error; use Illuminate\Contracts\Hashing\Hasher as HasherContract; +use InvalidArgumentException; use RuntimeException; class BcryptHasher extends AbstractHasher implements HasherContract @@ -22,16 +23,23 @@ class BcryptHasher extends AbstractHasher implements HasherContract */ protected $verifyAlgorithm = false; + /** + * The maximum allowed length of strings that can be hashed. + * + * @var int|null + */ + protected $limit; + /** * Create a new hasher instance. * * @param array $options - * @return void */ public function __construct(array $options = []) { $this->rounds = $options['rounds'] ?? $this->rounds; $this->verifyAlgorithm = $options['verify'] ?? $this->verifyAlgorithm; + $this->limit = $options['limit'] ?? $this->limit; } /** @@ -46,6 +54,10 @@ public function __construct(array $options = []) public function make(#[\SensitiveParameter] $value, array $options = []) { try { + if ($this->limit && strlen($value) > $this->limit) { + throw new InvalidArgumentException('Value is too long to hash. Value must be less than '.$this->limit.' bytes.'); + } + $hash = password_hash($value, PASSWORD_BCRYPT, [ 'cost' => $this->cost($options), ]); diff --git a/src/Illuminate/Hashing/HashManager.php b/src/Illuminate/Hashing/HashManager.php index f4c5b9f38e40..f1d46998a1ac 100644 --- a/src/Illuminate/Hashing/HashManager.php +++ b/src/Illuminate/Hashing/HashManager.php @@ -119,6 +119,10 @@ public function getDefaultDriver() */ public function verifyConfiguration($value) { - return $this->driver()->verifyConfiguration($value); + if (method_exists($driver = $this->driver(), 'verifyConfiguration')) { + return $driver->verifyConfiguration($value); + } + + return true; } } diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 79443fb9d020..623485f95dfc 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.2", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0" + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Http/Client/Events/ConnectionFailed.php b/src/Illuminate/Http/Client/Events/ConnectionFailed.php index 0906ad40f91a..94c829e56452 100644 --- a/src/Illuminate/Http/Client/Events/ConnectionFailed.php +++ b/src/Illuminate/Http/Client/Events/ConnectionFailed.php @@ -26,7 +26,6 @@ class ConnectionFailed * * @param \Illuminate\Http\Client\Request $request * @param \Illuminate\Http\Client\ConnectionException $exception - * @return void */ public function __construct(Request $request, ConnectionException $exception) { diff --git a/src/Illuminate/Http/Client/Events/RequestSending.php b/src/Illuminate/Http/Client/Events/RequestSending.php index 1b363fb751b3..f92c5c1438c0 100644 --- a/src/Illuminate/Http/Client/Events/RequestSending.php +++ b/src/Illuminate/Http/Client/Events/RequestSending.php @@ -17,7 +17,6 @@ class RequestSending * Create a new event instance. * * @param \Illuminate\Http\Client\Request $request - * @return void */ public function __construct(Request $request) { diff --git a/src/Illuminate/Http/Client/Events/ResponseReceived.php b/src/Illuminate/Http/Client/Events/ResponseReceived.php index 77be7aba7662..82db448371ff 100644 --- a/src/Illuminate/Http/Client/Events/ResponseReceived.php +++ b/src/Illuminate/Http/Client/Events/ResponseReceived.php @@ -26,7 +26,6 @@ class ResponseReceived * * @param \Illuminate\Http\Client\Request $request * @param \Illuminate\Http\Client\Response $response - * @return void */ public function __construct(Request $request, Response $response) { diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 84b8fc61a15d..c38932ef33d5 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -84,7 +84,6 @@ class Factory * Create a new factory instance. * * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher - * @return void */ public function __construct(?Dispatcher $dispatcher = null) { @@ -154,6 +153,21 @@ public function globalOptions($options) * @return \GuzzleHttp\Promise\PromiseInterface */ public static function response($body = null, $status = 200, $headers = []) + { + return Create::promiseFor( + static::psr7Response($body, $status, $headers) + ); + } + + /** + * Create a new PSR-7 response instance for use during stubbing. + * + * @param array|string|null $body + * @param int $status + * @param array $headers + * @return \GuzzleHttp\Psr7\Response + */ + public static function psr7Response($body = null, $status = 200, $headers = []) { if (is_array($body)) { $body = json_encode($body); @@ -161,9 +175,20 @@ public static function response($body = null, $status = 200, $headers = []) $headers['Content-Type'] = 'application/json'; } - $response = new Psr7Response($status, $headers, $body); + return new Psr7Response($status, $headers, $body); + } - return Create::promiseFor($response); + /** + * Create a new RequestException instance for use during stubbing. + * + * @param array|string|null $body + * @param int $status + * @param array $headers + * @return \Illuminate\Http\Client\RequestException + */ + public static function failedRequest($body = null, $status = 200, $headers = []) + { + return new RequestException(new Response(static::psr7Response($body, $status, $headers))); } /** @@ -322,7 +347,7 @@ public function allowStrayRequests() * * @return $this */ - protected function record() + public function record() { $this->recording = true; diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 839c6e6debd5..117319de5029 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -220,7 +220,6 @@ class PendingRequest * * @param \Illuminate\Http\Client\Factory|null $factory * @param array $middleware - * @return void */ public function __construct(?Factory $factory = null, $middleware = []) { @@ -794,7 +793,7 @@ public function head(string $url, $query = null) * Issue a POST request to the given URL. * * @param string $url - * @param array $data + * @param array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data * @return \Illuminate\Http\Client\Response * * @throws \Illuminate\Http\Client\ConnectionException @@ -810,7 +809,7 @@ public function post(string $url, $data = []) * Issue a PATCH request to the given URL. * * @param string $url - * @param array $data + * @param array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data * @return \Illuminate\Http\Client\Response * * @throws \Illuminate\Http\Client\ConnectionException @@ -826,7 +825,7 @@ public function patch(string $url, $data = []) * Issue a PUT request to the given URL. * * @param string $url - * @param array $data + * @param array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data * @return \Illuminate\Http\Client\Response * * @throws \Illuminate\Http\Client\ConnectionException @@ -842,7 +841,7 @@ public function put(string $url, $data = []) * Issue a DELETE request to the given URL. * * @param string $url - * @param array $data + * @param array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data * @return \Illuminate\Http\Client\Response * * @throws \Illuminate\Http\Client\ConnectionException @@ -911,7 +910,7 @@ public function send(string $method, string $url, array $options = []) if (! $response->successful()) { try { - $shouldRetry = $this->retryWhenCallback ? call_user_func($this->retryWhenCallback, $response->toException(), $this) : true; + $shouldRetry = $this->retryWhenCallback ? call_user_func($this->retryWhenCallback, $response->toException(), $this, $this->request->toPsrRequest()->getMethod()) : true; } catch (Exception $exception) { $shouldRetry = false; @@ -948,7 +947,7 @@ public function send(string $method, string $url, array $options = []) throw $exception; } }, $this->retryDelay ?? 100, function ($exception) use (&$shouldRetry) { - $result = $shouldRetry ?? ($this->retryWhenCallback ? call_user_func($this->retryWhenCallback, $exception, $this) : true); + $result = $shouldRetry ?? ($this->retryWhenCallback ? call_user_func($this->retryWhenCallback, $exception, $this, $this->request?->toPsrRequest()->getMethod()) : true); $shouldRetry = null; @@ -991,13 +990,15 @@ protected function parseHttpOptions(array $options) $options[$this->bodyFormat] = $this->pendingBody; } - return (new Collection($options))->map(function ($value, $key) { - if ($key === 'json' && $value instanceof JsonSerializable) { - return $value; - } + return (new Collection($options)) + ->map(function ($value, $key) { + if ($key === 'json' && $value instanceof JsonSerializable) { + return $value; + } - return $value instanceof Arrayable ? $value->toArray() : $value; - })->all(); + return $value instanceof Arrayable ? $value->toArray() : $value; + }) + ->all(); } /** diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php index b5f00258fbab..aba741f4bf75 100644 --- a/src/Illuminate/Http/Client/Pool.php +++ b/src/Illuminate/Http/Client/Pool.php @@ -34,7 +34,6 @@ class Pool * Create a new requests pool. * * @param \Illuminate\Http\Client\Factory|null $factory - * @return void */ public function __construct(?Factory $factory = null) { diff --git a/src/Illuminate/Http/Client/Request.php b/src/Illuminate/Http/Client/Request.php index 7c0132a35f85..7e6891221864 100644 --- a/src/Illuminate/Http/Client/Request.php +++ b/src/Illuminate/Http/Client/Request.php @@ -30,7 +30,6 @@ class Request implements ArrayAccess * Create a new request instance. * * @param \Psr\Http\Message\RequestInterface $request - * @return void */ public function __construct($request) { diff --git a/src/Illuminate/Http/Client/RequestException.php b/src/Illuminate/Http/Client/RequestException.php index 0fccaee9563c..a72f12873594 100644 --- a/src/Illuminate/Http/Client/RequestException.php +++ b/src/Illuminate/Http/Client/RequestException.php @@ -24,7 +24,6 @@ class RequestException extends HttpClientException * Create a new exception instance. * * @param \Illuminate\Http\Client\Response $response - * @return void */ public function __construct(Response $response) { diff --git a/src/Illuminate/Http/Client/Response.php b/src/Illuminate/Http/Client/Response.php index ac51d9c7ade8..c2352bb01f81 100644 --- a/src/Illuminate/Http/Client/Response.php +++ b/src/Illuminate/Http/Client/Response.php @@ -51,7 +51,6 @@ class Response implements ArrayAccess, Stringable * Create a new response instance. * * @param \Psr\Http\Message\MessageInterface $response - * @return void */ public function __construct($response) { @@ -527,7 +526,7 @@ public function __toString() public function __call($method, $parameters) { return static::hasMacro($method) - ? $this->macroCall($method, $parameters) - : $this->response->{$method}(...$parameters); + ? $this->macroCall($method, $parameters) + : $this->response->{$method}(...$parameters); } } diff --git a/src/Illuminate/Http/Client/ResponseSequence.php b/src/Illuminate/Http/Client/ResponseSequence.php index e35736b05d99..23ac08511d4c 100644 --- a/src/Illuminate/Http/Client/ResponseSequence.php +++ b/src/Illuminate/Http/Client/ResponseSequence.php @@ -35,7 +35,6 @@ class ResponseSequence * Create a new response sequence. * * @param array $responses - * @return void */ public function __construct(array $responses) { diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index 1435a8ad541e..b10714ea2d99 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -175,7 +175,7 @@ public function cookie($key = null, $default = null) /** * Get an array of all of the files on the request. * - * @return array + * @return array */ public function allFiles() { @@ -187,8 +187,8 @@ public function allFiles() /** * Convert the given array of Symfony UploadedFiles to custom Laravel UploadedFiles. * - * @param array $files - * @return array + * @param array $files + * @return array */ protected function convertUploadedFiles(array $files) { @@ -198,8 +198,8 @@ protected function convertUploadedFiles(array $files) } return is_array($file) - ? $this->convertUploadedFiles($file) - : UploadedFile::createFromBase($file); + ? $this->convertUploadedFiles($file) + : UploadedFile::createFromBase($file); }, $files); } @@ -240,7 +240,7 @@ protected function isValidFile($file) * * @param string|null $key * @param mixed $default - * @return \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null + * @return ($key is null ? array : \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null) */ public function file($key = null, $default = null) { diff --git a/src/Illuminate/Http/Exceptions/HttpResponseException.php b/src/Illuminate/Http/Exceptions/HttpResponseException.php index c45268680aeb..eeafe3205d60 100644 --- a/src/Illuminate/Http/Exceptions/HttpResponseException.php +++ b/src/Illuminate/Http/Exceptions/HttpResponseException.php @@ -20,7 +20,6 @@ class HttpResponseException extends RuntimeException * * @param \Symfony\Component\HttpFoundation\Response $response * @param \Throwable $previous - * @return void */ public function __construct(Response $response, ?Throwable $previous = null) { diff --git a/src/Illuminate/Http/Exceptions/MalformedUrlException.php b/src/Illuminate/Http/Exceptions/MalformedUrlException.php new file mode 100644 index 000000000000..c720997d18b0 --- /dev/null +++ b/src/Illuminate/Http/Exceptions/MalformedUrlException.php @@ -0,0 +1,16 @@ +header('Link', (new Collection(Vite::preloadedAssets())) + ->when($limit, fn ($assets, $limit) => $assets->take($limit)) ->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes)) ->join(', '), false); } diff --git a/src/Illuminate/Http/Middleware/HandleCors.php b/src/Illuminate/Http/Middleware/HandleCors.php index eee030511e39..a417deb2b305 100644 --- a/src/Illuminate/Http/Middleware/HandleCors.php +++ b/src/Illuminate/Http/Middleware/HandleCors.php @@ -28,7 +28,6 @@ class HandleCors * * @param \Illuminate\Contracts\Container\Container $container * @param \Fruitcake\Cors\CorsService $cors - * @return void */ public function __construct(Container $container, CorsService $cors) { diff --git a/src/Illuminate/Http/Middleware/SetCacheHeaders.php b/src/Illuminate/Http/Middleware/SetCacheHeaders.php index 59034f8d0eaf..379abb71a9b8 100644 --- a/src/Illuminate/Http/Middleware/SetCacheHeaders.php +++ b/src/Illuminate/Http/Middleware/SetCacheHeaders.php @@ -63,7 +63,7 @@ public function handle($request, Closure $next, $options = []) } if (isset($options['etag']) && $options['etag'] === true) { - $options['etag'] = $response->getEtag() ?? ($response->getContent() ? md5($response->getContent()) : null); + $options['etag'] = $response->getEtag() ?? ($response->getContent() ? hash('xxh128', $response->getContent()) : null); } if (isset($options['last_modified'])) { diff --git a/src/Illuminate/Http/Middleware/TrustHosts.php b/src/Illuminate/Http/Middleware/TrustHosts.php index 8eae16d1cec0..b0bb3b5c06a8 100644 --- a/src/Illuminate/Http/Middleware/TrustHosts.php +++ b/src/Illuminate/Http/Middleware/TrustHosts.php @@ -32,7 +32,6 @@ class TrustHosts * Create a new middleware instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct(Application $app) { diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 0a13ff79a361..0e6936d56a54 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -20,11 +20,11 @@ class TrustProxies * @var int */ protected $headers = Request::HEADER_X_FORWARDED_FOR | - Request::HEADER_X_FORWARDED_HOST | - Request::HEADER_X_FORWARDED_PORT | - Request::HEADER_X_FORWARDED_PROTO | - Request::HEADER_X_FORWARDED_PREFIX | - Request::HEADER_X_FORWARDED_AWS_ELB; + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_PREFIX | + Request::HEADER_X_FORWARDED_AWS_ELB; /** * The proxies that have been configured to always be trusted. @@ -77,8 +77,8 @@ protected function setTrustedProxyIpAddresses(Request $request) } $trustedIps = is_string($trustedIps) - ? array_map('trim', explode(',', $trustedIps)) - : $trustedIps; + ? array_map(trim(...), explode(',', $trustedIps)) + : $trustedIps; if (is_array($trustedIps)) { return $this->setTrustedProxyIpAddressesToSpecificIps($request, $trustedIps); diff --git a/src/Illuminate/Http/Middleware/ValidatePathEncoding.php b/src/Illuminate/Http/Middleware/ValidatePathEncoding.php new file mode 100644 index 000000000000..cd67581fe050 --- /dev/null +++ b/src/Illuminate/Http/Middleware/ValidatePathEncoding.php @@ -0,0 +1,28 @@ +path()); + + if (! mb_check_encoding($decodedPath, 'UTF-8')) { + throw new MalformedUrlException; + } + + return $next($request); + } +} diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index 5f2bb54dd511..bee09601ef03 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -42,7 +42,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess /** * All of the converted files for the request. * - * @var array + * @var array */ protected $convertedFiles; @@ -361,9 +361,13 @@ public function userAgent() */ public function merge(array $input) { - $this->getInputSource()->add($input); - - return $this; + return tap($this, function (Request $request) use ($input) { + $request->getInputSource() + ->replace((new Collection($input))->reduce( + fn ($requestInput, $value, $key) => data_set($requestInput, $key, $value), + $this->getInputSource()->all() + )); + }); } /** @@ -560,8 +564,8 @@ public function hasSession(bool $skipIfUninitialized = false): bool public function getSession(): SessionInterface { return $this->hasSession() - ? $this->session - : throw new SessionNotFoundException; + ? $this->session + : throw new SessionNotFoundException; } /** diff --git a/src/Illuminate/Http/Resources/CollectsResources.php b/src/Illuminate/Http/Resources/CollectsResources.php index c1bad66733c8..08ec46b52e39 100644 --- a/src/Illuminate/Http/Resources/CollectsResources.php +++ b/src/Illuminate/Http/Resources/CollectsResources.php @@ -36,8 +36,8 @@ protected function collectResource($resource) : $resource->toBase(); return ($resource instanceof AbstractPaginator || $resource instanceof AbstractCursorPaginator) - ? $resource->setCollection($this->collection) - : $this->collection; + ? $resource->setCollection($this->collection) + : $this->collection; } /** diff --git a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php index 0fc456870e67..16e026986484 100644 --- a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php +++ b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php @@ -199,8 +199,8 @@ public function whenHas($attribute, $value = null, $default = null) } return func_num_args() === 1 - ? $this->resource->{$attribute} - : value($value, $this->resource->{$attribute}); + ? $this->resource->{$attribute} + : value($value, $this->resource->{$attribute}); } /** diff --git a/src/Illuminate/Http/Resources/Json/AnonymousResourceCollection.php b/src/Illuminate/Http/Resources/Json/AnonymousResourceCollection.php index 26f5c460ce63..ba8c087f1194 100644 --- a/src/Illuminate/Http/Resources/Json/AnonymousResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/AnonymousResourceCollection.php @@ -23,7 +23,6 @@ class AnonymousResourceCollection extends ResourceCollection * * @param mixed $resource * @param string $collects - * @return void */ public function __construct($resource, $collects) { diff --git a/src/Illuminate/Http/Resources/Json/JsonResource.php b/src/Illuminate/Http/Resources/Json/JsonResource.php index 30b9425b08fb..7007507bbe45 100644 --- a/src/Illuminate/Http/Resources/Json/JsonResource.php +++ b/src/Illuminate/Http/Resources/Json/JsonResource.php @@ -53,7 +53,6 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou * Create a new resource instance. * * @param mixed $resource - * @return void */ public function __construct($resource) { diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index af86849fec1d..81cfc1bd3181 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -45,7 +45,6 @@ class ResourceCollection extends JsonResource implements Countable, IteratorAggr * Create a new resource instance. * * @param mixed $resource - * @return void */ public function __construct($resource) { diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 430e41a72950..4e4f9d3e1313 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -19,7 +19,6 @@ class ResourceResponse implements Responsable * Create a new resource response. * * @param mixed $resource - * @return void */ public function __construct($resource) { diff --git a/src/Illuminate/Http/Resources/MergeValue.php b/src/Illuminate/Http/Resources/MergeValue.php index fb6880fb725c..2cd963ef6ffb 100644 --- a/src/Illuminate/Http/Resources/MergeValue.php +++ b/src/Illuminate/Http/Resources/MergeValue.php @@ -18,7 +18,6 @@ class MergeValue * Create a new merge value instance. * * @param \Illuminate\Support\Collection|\JsonSerializable|array $data - * @return void */ public function __construct($data) { diff --git a/src/Illuminate/Http/Response.php b/src/Illuminate/Http/Response.php index b1661063dd63..6576f47f6a14 100755 --- a/src/Illuminate/Http/Response.php +++ b/src/Illuminate/Http/Response.php @@ -24,7 +24,6 @@ class Response extends SymfonyResponse * @param mixed $content * @param int $status * @param array $headers - * @return void * * @throws \InvalidArgumentException */ diff --git a/src/Illuminate/Http/StreamedEvent.php b/src/Illuminate/Http/StreamedEvent.php new file mode 100644 index 000000000000..5dd3c5707113 --- /dev/null +++ b/src/Illuminate/Http/StreamedEvent.php @@ -0,0 +1,25 @@ +event = $event; + $this->data = $data; + } +} diff --git a/src/Illuminate/Http/Testing/File.php b/src/Illuminate/Http/Testing/File.php index aaba539cfbb5..fbdc7f3ed253 100644 --- a/src/Illuminate/Http/Testing/File.php +++ b/src/Illuminate/Http/Testing/File.php @@ -39,7 +39,6 @@ class File extends UploadedFile * * @param string $name * @param resource $tempFile - * @return void */ public function __construct($name, $tempFile) { diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 4cb56872d30a..0ab2371d7c50 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -19,14 +19,14 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "illuminate/collections": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/session": "^11.0", - "illuminate/support": "^11.0", - "symfony/http-foundation": "^7.0.3", - "symfony/http-kernel": "^7.0.3", + "illuminate/collections": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/session": "^12.0", + "illuminate/support": "^12.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", "symfony/polyfill-php83": "^1.31", - "symfony/mime": "^7.0.3" + "symfony/mime": "^7.2.0" }, "autoload": { "psr-4": { @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Log/Context/ContextLogProcessor.php b/src/Illuminate/Log/Context/ContextLogProcessor.php new file mode 100644 index 000000000000..9ac3e97a77dd --- /dev/null +++ b/src/Illuminate/Log/Context/ContextLogProcessor.php @@ -0,0 +1,31 @@ +bound(ContextRepository::class)) { + return $record; + } + + return $record->with(extra: [ + ...$record->extra, + ...$app->get(ContextRepository::class)->all(), + ]); + } +} diff --git a/src/Illuminate/Log/Context/ContextServiceProvider.php b/src/Illuminate/Log/Context/ContextServiceProvider.php index 7c00e256ae26..7167518a1b19 100644 --- a/src/Illuminate/Log/Context/ContextServiceProvider.php +++ b/src/Illuminate/Log/Context/ContextServiceProvider.php @@ -2,6 +2,7 @@ namespace Illuminate\Log\Context; +use Illuminate\Contracts\Log\ContextLogProcessor as ContextLogProcessorContract; use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Queue; use Illuminate\Support\Facades\Context; @@ -17,6 +18,8 @@ class ContextServiceProvider extends ServiceProvider public function register() { $this->app->scoped(Repository::class); + + $this->app->bind(ContextLogProcessorContract::class, fn () => new ContextLogProcessor()); } /** diff --git a/src/Illuminate/Log/Context/Repository.php b/src/Illuminate/Log/Context/Repository.php index c46224d14594..01e861925020 100644 --- a/src/Illuminate/Log/Context/Repository.php +++ b/src/Illuminate/Log/Context/Repository.php @@ -369,6 +369,35 @@ public function popHidden($key) return array_pop($this->hidden[$key]); } + /** + * Increment a context counter. + * + * @param string $key + * @param int $amount + * @return $this + */ + public function increment(string $key, int $amount = 1) + { + $this->add( + $key, + (int) $this->get($key, 0) + $amount, + ); + + return $this; + } + + /** + * Decrement a context counter. + * + * @param string $key + * @param int $amount + * @return $this + */ + public function decrement(string $key, int $amount = 1) + { + return $this->increment($key, $amount * -1); + } + /** * Determine if the given value is in the given stack. * @@ -447,6 +476,35 @@ protected function isHiddenStackable($key) (is_array($this->hidden[$key]) && array_is_list($this->hidden[$key])); } + /** + * Run the callback function with the given context values and restore the original context state when complete. + * + * @param callable $callback + * @param array $data + * @param array $hidden + * @return mixed + */ + public function scope(callable $callback, array $data = [], array $hidden = []) + { + $dataBefore = $this->data; + $hiddenBefore = $this->hidden; + + if ($data !== []) { + $this->add($data); + } + + if ($hidden !== []) { + $this->addHidden($hidden); + } + + try { + return $callback(); + } finally { + $this->data = $dataBefore; + $this->hidden = $hiddenBefore; + } + } + /** * Determine if the repository is empty. * diff --git a/src/Illuminate/Log/Events/MessageLogged.php b/src/Illuminate/Log/Events/MessageLogged.php index 312b343a356d..b3458815af5d 100644 --- a/src/Illuminate/Log/Events/MessageLogged.php +++ b/src/Illuminate/Log/Events/MessageLogged.php @@ -31,7 +31,6 @@ class MessageLogged * @param string $level * @param string $message * @param array $context - * @return void */ public function __construct($level, $message, array $context = []) { diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index 7563e35cb837..da69039adcb1 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -3,7 +3,7 @@ namespace Illuminate\Log; use Closure; -use Illuminate\Log\Context\Repository as ContextRepository; +use Illuminate\Contracts\Log\ContextLogProcessor; use Illuminate\Support\Collection; use Illuminate\Support\Str; use InvalidArgumentException; @@ -69,7 +69,6 @@ class LogManager implements LoggerInterface * Create a new Log manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -143,16 +142,7 @@ protected function get($name, ?array $config = null) )->withContext($this->sharedContext); if (method_exists($loggerWithContext->getLogger(), 'pushProcessor')) { - $loggerWithContext->pushProcessor(function ($record) { - if (! $this->app->bound(ContextRepository::class)) { - return $record; - } - - return $record->with(extra: [ - ...$record->extra, - ...$this->app[ContextRepository::class]->all(), - ]); - }); + $loggerWithContext->pushProcessor($this->app->make(ContextLogProcessor::class)); } return $this->channels[$name] = $loggerWithContext; @@ -524,13 +514,14 @@ public function sharedContext() /** * Flush the log context on all currently resolved channels. * + * @param string[]|null $keys * @return $this */ - public function withoutContext() + public function withoutContext(?array $keys = null) { foreach ($this->channels as $channel) { if (method_exists($channel, 'withoutContext')) { - $channel->withoutContext(); + $channel->withoutContext($keys); } } @@ -563,7 +554,7 @@ protected function getFallbackChannelName() * Get the log connection configuration. * * @param string $name - * @return array + * @return array|null */ protected function configurationFor($name) { @@ -596,6 +587,9 @@ public function setDefaultDriver($name) * * @param string $driver * @param \Closure $callback + * + * @param-closure-this $this $callback + * * @return $this */ public function extend($driver, Closure $callback) diff --git a/src/Illuminate/Log/Logger.php b/src/Illuminate/Log/Logger.php index c94bf5bae249..c378d9dbbc69 100755 --- a/src/Illuminate/Log/Logger.php +++ b/src/Illuminate/Log/Logger.php @@ -41,7 +41,6 @@ class Logger implements LoggerInterface * * @param \Psr\Log\LoggerInterface $logger * @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher - * @return void */ public function __construct(LoggerInterface $logger, ?Dispatcher $dispatcher = null) { @@ -203,13 +202,18 @@ public function withContext(array $context = []) } /** - * Flush the existing context array. + * Flush the log context on all currently resolved channels. * + * @param string[]|null $keys * @return $this */ - public function withoutContext() + public function withoutContext(?array $keys = null) { - $this->context = []; + if (is_array($keys)) { + $this->context = array_diff_key($this->context, array_flip($keys)); + } else { + $this->context = []; + } return $this; } diff --git a/src/Illuminate/Log/composer.json b/src/Illuminate/Log/composer.json index 4bb196fd58a3..201883d80002 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.2", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0", + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0", "monolog/monolog": "^3.0", "psr/log": "^1.0|^2.0|^3.0" }, @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Macroable/composer.json b/src/Illuminate/Macroable/composer.json index 08417d06884b..38dc6f161a42 100644 --- a/src/Illuminate/Macroable/composer.json +++ b/src/Illuminate/Macroable/composer.json @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Mail/Attachment.php b/src/Illuminate/Mail/Attachment.php index cdf601e8c02f..f49d32cc1e78 100644 --- a/src/Illuminate/Mail/Attachment.php +++ b/src/Illuminate/Mail/Attachment.php @@ -37,7 +37,6 @@ class Attachment * Create a mail attachment. * * @param \Closure $resolver - * @return void */ private function __construct(Closure $resolver) { diff --git a/src/Illuminate/Mail/Events/MessageSending.php b/src/Illuminate/Mail/Events/MessageSending.php index 31435fb6e0e7..7215b14a265a 100644 --- a/src/Illuminate/Mail/Events/MessageSending.php +++ b/src/Illuminate/Mail/Events/MessageSending.php @@ -6,30 +6,15 @@ class MessageSending { - /** - * The Symfony Email instance. - * - * @var \Symfony\Component\Mime\Email - */ - public $message; - - /** - * The message data. - * - * @var array - */ - public $data; - /** * Create a new event instance. * - * @param \Symfony\Component\Mime\Email $message - * @param array $data - * @return void + * @param \Symfony\Component\Mime\Email $message The Symfony Email instance. + * @param array $data The message data. */ - public function __construct(Email $message, array $data = []) - { - $this->data = $data; - $this->message = $message; + public function __construct( + public Email $message, + public array $data = [], + ) { } } diff --git a/src/Illuminate/Mail/Events/MessageSent.php b/src/Illuminate/Mail/Events/MessageSent.php index 954704c14557..8a5ae558cb36 100644 --- a/src/Illuminate/Mail/Events/MessageSent.php +++ b/src/Illuminate/Mail/Events/MessageSent.php @@ -11,31 +11,16 @@ */ class MessageSent { - /** - * The message that was sent. - * - * @var \Illuminate\Mail\SentMessage - */ - public $sent; - - /** - * The message data. - * - * @var array - */ - public $data; - /** * Create a new event instance. * - * @param \Illuminate\Mail\SentMessage $message - * @param array $data - * @return void + * @param \Illuminate\Mail\SentMessage $sent The message that was sent. + * @param array $data The message data. */ - public function __construct(SentMessage $message, array $data = []) - { - $this->sent = $message; - $this->data = $data; + public function __construct( + public SentMessage $sent, + public array $data = [], + ) { } /** diff --git a/src/Illuminate/Mail/MailManager.php b/src/Illuminate/Mail/MailManager.php index 330a13ecaf42..078630fa0968 100644 --- a/src/Illuminate/Mail/MailManager.php +++ b/src/Illuminate/Mail/MailManager.php @@ -59,7 +59,6 @@ class MailManager implements FactoryContract * Create a new Mail manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -365,8 +364,8 @@ protected function createPostmarkTransport(array $config) $factory = new PostmarkTransportFactory(null, $this->getHttpClient($config)); $options = isset($config['message_stream_id']) - ? ['message_stream' => $config['message_stream_id']] - : []; + ? ['message_stream' => $config['message_stream_id']] + : []; return $factory->create(new Dsn( 'postmark+api', @@ -386,24 +385,7 @@ protected function createPostmarkTransport(array $config) */ protected function createFailoverTransport(array $config) { - $transports = []; - - foreach ($config['mailers'] as $name) { - $config = $this->getConfig($name); - - if (is_null($config)) { - throw new InvalidArgumentException("Mailer [{$name}] is not defined."); - } - - // Now, we will check if the "driver" key exists and if it does we will set - // the transport configuration parameter in order to offer compatibility - // with any Laravel <= 6.x application style mail configuration files. - $transports[] = $this->app['config']['mail.driver'] - ? $this->createSymfonyTransport(array_merge($config, ['transport' => $name])) - : $this->createSymfonyTransport($config); - } - - return new FailoverTransport($transports); + return $this->createRoundrobinTransportOfClass($config, FailoverTransport::class); } /** @@ -413,6 +395,20 @@ protected function createFailoverTransport(array $config) * @return \Symfony\Component\Mailer\Transport\RoundRobinTransport */ protected function createRoundrobinTransport(array $config) + { + return $this->createRoundrobinTransportOfClass($config, RoundRobinTransport::class); + } + + /** + * Create an instance of supplied class extending the Symfony Roundrobin Transport driver. + * + * @template TClass of \Symfony\Component\Mailer\Transport\RoundRobinTransport + * + * @param array $config + * @param class-string $class + * @return TClass + */ + protected function createRoundrobinTransportOfClass(array $config, string $class) { $transports = []; @@ -431,7 +427,7 @@ protected function createRoundrobinTransport(array $config) : $this->createSymfonyTransport($config); } - return new RoundRobinTransport($transports); + return new $class($transports, $config['retry_after'] ?? 60); } /** diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index f517b803dce6..afac565ec7a8 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -200,8 +200,8 @@ public function send($mailer) $this->prepareMailableForDelivery(); $mailer = $mailer instanceof MailFactory - ? $mailer->mailer($this->mailer) - : $mailer; + ? $mailer->mailer($this->mailer) + : $mailer; return $mailer->send($this->buildView(), $this->buildViewData(), function ($message) { $this->buildFrom($message) @@ -1415,7 +1415,7 @@ public function assertDontSeeInHtml($string, $escape = true) */ public function assertSeeInOrderInHtml($strings, $escape = true) { - $strings = $escape ? array_map('e', $strings) : $strings; + $strings = $escape ? array_map(e(...), $strings) : $strings; [$html, $text] = $this->renderForAssertions(); diff --git a/src/Illuminate/Mail/Mailables/Address.php b/src/Illuminate/Mail/Mailables/Address.php index 7a9ed2aa66cd..6a03e920e78d 100644 --- a/src/Illuminate/Mail/Mailables/Address.php +++ b/src/Illuminate/Mail/Mailables/Address.php @@ -23,7 +23,6 @@ class Address * * @param string $address * @param string|null $name - * @return void */ public function __construct(string $address, ?string $name = null) { diff --git a/src/Illuminate/Mail/Mailables/Envelope.php b/src/Illuminate/Mail/Mailables/Envelope.php index 945e63f57990..727942d665ff 100644 --- a/src/Illuminate/Mail/Mailables/Envelope.php +++ b/src/Illuminate/Mail/Mailables/Envelope.php @@ -86,7 +86,6 @@ class Envelope * @param array $tags * @param array $metadata * @param \Closure|array $using - * @return void * * @named-arguments-supported */ diff --git a/src/Illuminate/Mail/Mailables/Headers.php b/src/Illuminate/Mail/Mailables/Headers.php index 166c344b2f90..26678f608bec 100644 --- a/src/Illuminate/Mail/Mailables/Headers.php +++ b/src/Illuminate/Mail/Mailables/Headers.php @@ -37,7 +37,6 @@ class Headers * @param string|null $messageId * @param array $references * @param array $text - * @return void * * @named-arguments-supported */ diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php index bd6484f3ea67..b0d3b75bfc80 100755 --- a/src/Illuminate/Mail/Mailer.php +++ b/src/Illuminate/Mail/Mailer.php @@ -95,7 +95,6 @@ class Mailer implements MailerContract, MailQueueContract * @param \Illuminate\Contracts\View\Factory $views * @param \Symfony\Component\Mailer\Transport\TransportInterface $transport * @param \Illuminate\Contracts\Events\Dispatcher|null $events - * @return void */ public function __construct(string $name, Factory $views, TransportInterface $transport, ?Dispatcher $events = null) { @@ -350,8 +349,8 @@ public function send($view, array $data = [], $callback = null) protected function sendMailable(MailableContract $mailable) { return $mailable instanceof ShouldQueue - ? $mailable->mailer($this->name)->queue($this->queue) - : $mailable->mailer($this->name)->send($this); + ? $mailable->mailer($this->name)->queue($this->queue) + : $mailable->mailer($this->name)->send($this); } /** @@ -441,8 +440,8 @@ protected function renderView($view, $data) $view = value($view, $data); return $view instanceof Htmlable - ? $view->toHtml() - : $this->views->make($view, $data)->render(); + ? $view->toHtml() + : $this->views->make($view, $data)->render(); } /** diff --git a/src/Illuminate/Mail/Markdown.php b/src/Illuminate/Mail/Markdown.php index 8faf739eb393..06d123ed3858 100644 --- a/src/Illuminate/Mail/Markdown.php +++ b/src/Illuminate/Mail/Markdown.php @@ -39,7 +39,6 @@ class Markdown * * @param \Illuminate\Contracts\View\Factory $view * @param array $options - * @return void */ public function __construct(ViewFactory $view, array $options = []) { diff --git a/src/Illuminate/Mail/Message.php b/src/Illuminate/Mail/Message.php index e5392dd52f8f..b9a4eb5352ca 100755 --- a/src/Illuminate/Mail/Message.php +++ b/src/Illuminate/Mail/Message.php @@ -38,7 +38,6 @@ class Message * Create a new message instance. * * @param \Symfony\Component\Mime\Email $message - * @return void */ public function __construct(Email $message) { diff --git a/src/Illuminate/Mail/PendingMail.php b/src/Illuminate/Mail/PendingMail.php index 1aa3d9a6cc2f..0523cac3db95 100644 --- a/src/Illuminate/Mail/PendingMail.php +++ b/src/Illuminate/Mail/PendingMail.php @@ -50,7 +50,6 @@ class PendingMail * Create a new mailable mailer instance. * * @param \Illuminate\Contracts\Mail\Mailer $mailer - * @return void */ public function __construct(MailerContract $mailer) { diff --git a/src/Illuminate/Mail/SendQueuedMailable.php b/src/Illuminate/Mail/SendQueuedMailable.php index b9fec9d03849..a5342da41f50 100644 --- a/src/Illuminate/Mail/SendQueuedMailable.php +++ b/src/Illuminate/Mail/SendQueuedMailable.php @@ -52,7 +52,6 @@ class SendQueuedMailable * Create a new job instance. * * @param \Illuminate\Contracts\Mail\Mailable $mailable - * @return void */ public function __construct(MailableContract $mailable) { diff --git a/src/Illuminate/Mail/SentMessage.php b/src/Illuminate/Mail/SentMessage.php index c33f87edc724..036b695e7f5a 100644 --- a/src/Illuminate/Mail/SentMessage.php +++ b/src/Illuminate/Mail/SentMessage.php @@ -24,7 +24,6 @@ class SentMessage * Create a new SentMessage instance. * * @param \Symfony\Component\Mailer\SentMessage $sentMessage - * @return void */ public function __construct(SymfonySentMessage $sentMessage) { diff --git a/src/Illuminate/Mail/TextMessage.php b/src/Illuminate/Mail/TextMessage.php index 5c615d2cfe58..ba81e3421968 100644 --- a/src/Illuminate/Mail/TextMessage.php +++ b/src/Illuminate/Mail/TextMessage.php @@ -22,7 +22,6 @@ class TextMessage * Create a new text message instance. * * @param \Illuminate\Mail\Message $message - * @return void */ public function __construct($message) { diff --git a/src/Illuminate/Mail/Transport/ArrayTransport.php b/src/Illuminate/Mail/Transport/ArrayTransport.php index 67da10780663..ae690f8b5a0e 100644 --- a/src/Illuminate/Mail/Transport/ArrayTransport.php +++ b/src/Illuminate/Mail/Transport/ArrayTransport.php @@ -20,8 +20,6 @@ class ArrayTransport implements Stringable, TransportInterface /** * Create a new array transport instance. - * - * @return void */ public function __construct() { diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index 2dff1490fea5..90ba275bb202 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -23,7 +23,6 @@ class LogTransport implements Stringable, TransportInterface * Create a new log transport instance. * * @param \Psr\Log\LoggerInterface $logger - * @return void */ public function __construct(LoggerInterface $logger) { diff --git a/src/Illuminate/Mail/Transport/SesTransport.php b/src/Illuminate/Mail/Transport/SesTransport.php index daa3be18991e..f5dbd69e0a94 100644 --- a/src/Illuminate/Mail/Transport/SesTransport.php +++ b/src/Illuminate/Mail/Transport/SesTransport.php @@ -33,7 +33,6 @@ class SesTransport extends AbstractTransport implements Stringable * * @param \Aws\Ses\SesClient $ses * @param array $options - * @return void */ public function __construct(SesClient $ses, $options = []) { diff --git a/src/Illuminate/Mail/Transport/SesV2Transport.php b/src/Illuminate/Mail/Transport/SesV2Transport.php index ab47b44b3ea8..ff918dc3bad9 100644 --- a/src/Illuminate/Mail/Transport/SesV2Transport.php +++ b/src/Illuminate/Mail/Transport/SesV2Transport.php @@ -33,7 +33,6 @@ class SesV2Transport extends AbstractTransport implements Stringable * * @param \Aws\SesV2\SesV2Client $ses * @param array $options - * @return void */ public function __construct(SesV2Client $ses, $options = []) { diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 23f0cd246676..cf28958fdcad 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -15,14 +15,14 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "league/commonmark": "^2.6", "psr/log": "^1.0|^2.0|^3.0", - "symfony/mailer": "^7.0.3", + "symfony/mailer": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5" }, "autoload": { @@ -32,15 +32,15 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { "aws/aws-sdk-php": "Required to use the SES mail driver (^3.322.9).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", - "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)." + "symfony/http-client": "Required to use the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Notifications/Action.php b/src/Illuminate/Notifications/Action.php index 071db2d9efdc..f59f86836d1a 100644 --- a/src/Illuminate/Notifications/Action.php +++ b/src/Illuminate/Notifications/Action.php @@ -23,7 +23,6 @@ class Action * * @param string $text * @param string $url - * @return void */ public function __construct($text, $url) { diff --git a/src/Illuminate/Notifications/Channels/BroadcastChannel.php b/src/Illuminate/Notifications/Channels/BroadcastChannel.php index 14739d8eb9d8..cc51f7188b46 100644 --- a/src/Illuminate/Notifications/Channels/BroadcastChannel.php +++ b/src/Illuminate/Notifications/Channels/BroadcastChannel.php @@ -21,7 +21,6 @@ class BroadcastChannel * Create a new broadcast channel. * * @param \Illuminate\Contracts\Events\Dispatcher $events - * @return void */ public function __construct(Dispatcher $events) { diff --git a/src/Illuminate/Notifications/Channels/DatabaseChannel.php b/src/Illuminate/Notifications/Channels/DatabaseChannel.php index 8e341c217683..dcfb891382b6 100644 --- a/src/Illuminate/Notifications/Channels/DatabaseChannel.php +++ b/src/Illuminate/Notifications/Channels/DatabaseChannel.php @@ -33,12 +33,12 @@ protected function buildPayload($notifiable, Notification $notification) return [ 'id' => $notification->id, 'type' => method_exists($notification, 'databaseType') - ? $notification->databaseType($notifiable) - : get_class($notification), + ? $notification->databaseType($notifiable) + : get_class($notification), 'data' => $this->getData($notifiable, $notification), 'read_at' => method_exists($notification, 'initialDatabaseReadAtValue') - ? $notification->initialDatabaseReadAtValue($notifiable) - : null, + ? $notification->initialDatabaseReadAtValue($notifiable) + : null, ]; } @@ -55,7 +55,8 @@ protected function getData($notifiable, Notification $notification) { if (method_exists($notification, 'toDatabase')) { return is_array($data = $notification->toDatabase($notifiable)) - ? $data : $data->data; + ? $data + : $data->data; } if (method_exists($notification, 'toArray')) { diff --git a/src/Illuminate/Notifications/Channels/MailChannel.php b/src/Illuminate/Notifications/Channels/MailChannel.php index fcfa5af02c66..ffc23b9008ec 100644 --- a/src/Illuminate/Notifications/Channels/MailChannel.php +++ b/src/Illuminate/Notifications/Channels/MailChannel.php @@ -36,7 +36,6 @@ class MailChannel * * @param \Illuminate\Contracts\Mail\Factory $mailer * @param \Illuminate\Mail\Markdown $markdown - * @return void */ public function __construct(MailFactory $mailer, Markdown $markdown) { @@ -263,11 +262,13 @@ protected function getRecipients($notifiable, $notification, $message) $recipients = [$recipients]; } - return (new Collection($recipients))->mapWithKeys(function ($recipient, $email) { - return is_numeric($email) + return (new Collection($recipients)) + ->mapWithKeys(function ($recipient, $email) { + return is_numeric($email) ? [$email => (is_string($recipient) ? $recipient : $recipient->email)] : [$email => $recipient]; - })->all(); + }) + ->all(); } /** diff --git a/src/Illuminate/Notifications/Events/BroadcastNotificationCreated.php b/src/Illuminate/Notifications/Events/BroadcastNotificationCreated.php index 44cb41019b79..ad4e730e1221 100644 --- a/src/Illuminate/Notifications/Events/BroadcastNotificationCreated.php +++ b/src/Illuminate/Notifications/Events/BroadcastNotificationCreated.php @@ -14,40 +14,18 @@ class BroadcastNotificationCreated implements ShouldBroadcast { use Queueable, SerializesModels; - /** - * The notifiable entity who received the notification. - * - * @var mixed - */ - public $notifiable; - - /** - * The notification instance. - * - * @var \Illuminate\Notifications\Notification - */ - public $notification; - - /** - * The notification data. - * - * @var array - */ - public $data = []; - /** * Create a new event instance. * - * @param mixed $notifiable - * @param \Illuminate\Notifications\Notification $notification - * @param array $data - * @return void + * @param mixed $notifiable The notifiable entity who received the notification. + * @param \Illuminate\Notifications\Notification $notification The notification instance. + * @param array $data The notification data. */ - public function __construct($notifiable, $notification, $data) - { - $this->data = $data; - $this->notifiable = $notifiable; - $this->notification = $notification; + public function __construct( + public $notifiable, + public $notification, + public $data = [], + ) { } /** @@ -118,8 +96,8 @@ public function broadcastWith() public function broadcastType() { return method_exists($this->notification, 'broadcastType') - ? $this->notification->broadcastType() - : get_class($this->notification); + ? $this->notification->broadcastType() + : get_class($this->notification); } /** @@ -130,7 +108,7 @@ public function broadcastType() public function broadcastAs() { return method_exists($this->notification, 'broadcastAs') - ? $this->notification->broadcastAs() - : __CLASS__; + ? $this->notification->broadcastAs() + : __CLASS__; } } diff --git a/src/Illuminate/Notifications/Events/NotificationFailed.php b/src/Illuminate/Notifications/Events/NotificationFailed.php index b69e1c5485af..544c3b53b152 100644 --- a/src/Illuminate/Notifications/Events/NotificationFailed.php +++ b/src/Illuminate/Notifications/Events/NotificationFailed.php @@ -9,48 +9,19 @@ class NotificationFailed { use Queueable, SerializesModels; - /** - * The notifiable entity who received the notification. - * - * @var mixed - */ - public $notifiable; - - /** - * The notification instance. - * - * @var \Illuminate\Notifications\Notification - */ - public $notification; - - /** - * The channel name. - * - * @var string - */ - public $channel; - - /** - * The data needed to process this failure. - * - * @var array - */ - public $data = []; - /** * Create a new event instance. * - * @param mixed $notifiable - * @param \Illuminate\Notifications\Notification $notification - * @param string $channel - * @param array $data - * @return void + * @param mixed $notifiable The notifiable entity who received the notification. + * @param \Illuminate\Notifications\Notification $notification The notification instance. + * @param string $channel The channel name. + * @param array $data The data needed to process this failure. */ - public function __construct($notifiable, $notification, $channel, $data = []) - { - $this->data = $data; - $this->channel = $channel; - $this->notifiable = $notifiable; - $this->notification = $notification; + public function __construct( + public $notifiable, + public $notification, + public $channel, + public $data = [], + ) { } } diff --git a/src/Illuminate/Notifications/Events/NotificationSending.php b/src/Illuminate/Notifications/Events/NotificationSending.php index 6efd1d06de93..42a7aeb8ac35 100644 --- a/src/Illuminate/Notifications/Events/NotificationSending.php +++ b/src/Illuminate/Notifications/Events/NotificationSending.php @@ -9,39 +9,17 @@ class NotificationSending { use Queueable, SerializesModels; - /** - * The notifiable entity who received the notification. - * - * @var mixed - */ - public $notifiable; - - /** - * The notification instance. - * - * @var \Illuminate\Notifications\Notification - */ - public $notification; - - /** - * The channel name. - * - * @var string - */ - public $channel; - /** * Create a new event instance. * - * @param mixed $notifiable - * @param \Illuminate\Notifications\Notification $notification - * @param string $channel - * @return void + * @param mixed $notifiable The notifiable entity who received the notification. + * @param \Illuminate\Notifications\Notification $notification The notification instance. + * @param string $channel The channel name. */ - public function __construct($notifiable, $notification, $channel) - { - $this->channel = $channel; - $this->notifiable = $notifiable; - $this->notification = $notification; + public function __construct( + public $notifiable, + public $notification, + public $channel, + ) { } } diff --git a/src/Illuminate/Notifications/Events/NotificationSent.php b/src/Illuminate/Notifications/Events/NotificationSent.php index 4f09069148eb..8868b393154d 100644 --- a/src/Illuminate/Notifications/Events/NotificationSent.php +++ b/src/Illuminate/Notifications/Events/NotificationSent.php @@ -9,48 +9,19 @@ class NotificationSent { use Queueable, SerializesModels; - /** - * The notifiable entity who received the notification. - * - * @var mixed - */ - public $notifiable; - - /** - * The notification instance. - * - * @var \Illuminate\Notifications\Notification - */ - public $notification; - - /** - * The channel name. - * - * @var string - */ - public $channel; - - /** - * The channel's response. - * - * @var mixed - */ - public $response; - /** * Create a new event instance. * - * @param mixed $notifiable - * @param \Illuminate\Notifications\Notification $notification - * @param string $channel - * @param mixed $response - * @return void + * @param mixed $notifiable The notifiable entity who received the notification. + * @param \Illuminate\Notifications\Notification $notification The notification instance. + * @param string $channel The channel name. + * @param mixed $response The channel's response. */ - public function __construct($notifiable, $notification, $channel, $response = null) - { - $this->channel = $channel; - $this->response = $response; - $this->notifiable = $notifiable; - $this->notification = $notification; + public function __construct( + public $notifiable, + public $notification, + public $channel, + public $response = null, + ) { } } diff --git a/src/Illuminate/Notifications/Messages/BroadcastMessage.php b/src/Illuminate/Notifications/Messages/BroadcastMessage.php index 9884a8fbb382..810984296273 100644 --- a/src/Illuminate/Notifications/Messages/BroadcastMessage.php +++ b/src/Illuminate/Notifications/Messages/BroadcastMessage.php @@ -19,7 +19,6 @@ class BroadcastMessage * Create a new message instance. * * @param array $data - * @return void */ public function __construct(array $data) { diff --git a/src/Illuminate/Notifications/Messages/DatabaseMessage.php b/src/Illuminate/Notifications/Messages/DatabaseMessage.php index 55707a7c065f..d0ef936d6c1c 100644 --- a/src/Illuminate/Notifications/Messages/DatabaseMessage.php +++ b/src/Illuminate/Notifications/Messages/DatabaseMessage.php @@ -15,7 +15,6 @@ class DatabaseMessage * Create a new database message. * * @param array $data - * @return void */ public function __construct(array $data = []) { diff --git a/src/Illuminate/Notifications/Messages/MailMessage.php b/src/Illuminate/Notifications/Messages/MailMessage.php index a80117a50178..f65622863d76 100644 --- a/src/Illuminate/Notifications/Messages/MailMessage.php +++ b/src/Illuminate/Notifications/Messages/MailMessage.php @@ -212,7 +212,7 @@ public function from($address, $name = null) public function replyTo($address, $name = null) { if ($this->arrayOfAddresses($address)) { - $this->replyTo += $this->parseAddresses($address); + $this->replyTo = array_merge($this->replyTo, $this->parseAddresses($address)); } else { $this->replyTo[] = [$address, $name]; } @@ -230,7 +230,7 @@ public function replyTo($address, $name = null) public function cc($address, $name = null) { if ($this->arrayOfAddresses($address)) { - $this->cc += $this->parseAddresses($address); + $this->cc = array_merge($this->cc, $this->parseAddresses($address)); } else { $this->cc[] = [$address, $name]; } @@ -248,7 +248,7 @@ public function cc($address, $name = null) public function bcc($address, $name = null) { if ($this->arrayOfAddresses($address)) { - $this->bcc += $this->parseAddresses($address); + $this->bcc = array_merge($this->bcc, $this->parseAddresses($address)); } else { $this->bcc[] = [$address, $name]; } @@ -372,9 +372,10 @@ public function data() */ protected function parseAddresses($value) { - return (new Collection($value))->map(function ($address, $name) { - return [$address, is_numeric($name) ? null : $name]; - })->values()->all(); + return (new Collection($value)) + ->map(fn ($address, $name) => [$address, is_numeric($name) ? null : $name]) + ->values() + ->all(); } /** diff --git a/src/Illuminate/Notifications/Messages/SimpleMessage.php b/src/Illuminate/Notifications/Messages/SimpleMessage.php index 254fd78ee137..82985aab0ecb 100644 --- a/src/Illuminate/Notifications/Messages/SimpleMessage.php +++ b/src/Illuminate/Notifications/Messages/SimpleMessage.php @@ -236,10 +236,10 @@ protected function formatLine($line) } if (is_array($line)) { - return implode(' ', array_map('trim', $line)); + return implode(' ', array_map(trim(...), $line)); } - return trim(implode(' ', array_map('trim', preg_split('/\\r\\n|\\r|\\n/', $line ?? '')))); + return trim(implode(' ', array_map(trim(...), preg_split('/\\r\\n|\\r|\\n/', $line ?? '')))); } /** diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index cea407f70b9a..46ef9e88cf15 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -6,11 +6,13 @@ use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Events\NotificationFailed; use Illuminate\Notifications\Events\NotificationSending; use Illuminate\Notifications\Events\NotificationSent; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Localizable; +use Throwable; class NotificationSender { @@ -44,6 +46,13 @@ class NotificationSender */ protected $locale; + /** + * Indicates whether a NotificationFailed event has been dispatched. + * + * @var bool + */ + protected $failedEventWasDispatched = false; + /** * Create a new notification sender instance. * @@ -51,7 +60,6 @@ class NotificationSender * @param \Illuminate\Contracts\Bus\Dispatcher $bus * @param \Illuminate\Contracts\Events\Dispatcher $events * @param string|null $locale - * @return void */ public function __construct($manager, $bus, $events, $locale = null) { @@ -59,6 +67,8 @@ public function __construct($manager, $bus, $events, $locale = null) $this->events = $events; $this->locale = $locale; $this->manager = $manager; + + $this->events->listen(NotificationFailed::class, fn () => $this->failedEventWasDispatched = true); } /** @@ -145,7 +155,19 @@ protected function sendToNotifiable($notifiable, $id, $notification, $channel) return; } - $response = $this->manager->driver($channel)->send($notifiable, $notification); + try { + $response = $this->manager->driver($channel)->send($notifiable, $notification); + } catch (Throwable $exception) { + if (! $this->failedEventWasDispatched) { + $this->events->dispatch( + new NotificationFailed($notifiable, $notification, $channel, ['exception' => $exception]) + ); + } + + $this->failedEventWasDispatched = false; + + throw $exception; + } $this->events->dispatch( new NotificationSent($notifiable, $notification, $channel, $response) @@ -247,7 +269,8 @@ protected function formatNotifiables($notifiables) { if (! $notifiables instanceof Collection && ! is_array($notifiables)) { return $notifiables instanceof Model - ? new EloquentCollection([$notifiables]) : [$notifiables]; + ? new EloquentCollection([$notifiables]) + : [$notifiables]; } return $notifiables; diff --git a/src/Illuminate/Notifications/SendQueuedNotifications.php b/src/Illuminate/Notifications/SendQueuedNotifications.php index 3eca3cccea90..bdd6fc8b4729 100644 --- a/src/Illuminate/Notifications/SendQueuedNotifications.php +++ b/src/Illuminate/Notifications/SendQueuedNotifications.php @@ -71,7 +71,6 @@ class SendQueuedNotifications implements ShouldQueue * @param \Illuminate\Notifications\Notifiable|\Illuminate\Support\Collection $notifiables * @param \Illuminate\Notifications\Notification $notification * @param array|null $channels - * @return void */ public function __construct($notifiables, $notification, ?array $channels = null) { diff --git a/src/Illuminate/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index 18321766911a..4041897464c5 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -15,15 +15,15 @@ ], "require": { "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" + "illuminate/broadcasting": "^12.0", + "illuminate/bus": "^12.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/mail": "^12.0", + "illuminate/queue": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -32,11 +32,11 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/database": "Required to use the database transport (^11.0)." + "illuminate/database": "Required to use the database transport (^12.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Pagination/AbstractCursorPaginator.php b/src/Illuminate/Pagination/AbstractCursorPaginator.php index 87a087983aad..850f8b7fe0f9 100644 --- a/src/Illuminate/Pagination/AbstractCursorPaginator.php +++ b/src/Illuminate/Pagination/AbstractCursorPaginator.php @@ -265,8 +265,8 @@ protected function getPivotParameterForItem($item, $parameterName) protected function ensureParameterIsPrimitive($parameter) { return is_object($parameter) && method_exists($parameter, '__toString') - ? (string) $parameter - : $parameter; + ? (string) $parameter + : $parameter; } /** diff --git a/src/Illuminate/Pagination/AbstractPaginator.php b/src/Illuminate/Pagination/AbstractPaginator.php index dc6c9d0adc25..b27830f84ecc 100644 --- a/src/Illuminate/Pagination/AbstractPaginator.php +++ b/src/Illuminate/Pagination/AbstractPaginator.php @@ -3,11 +3,13 @@ namespace Illuminate\Pagination; use Closure; +use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Tappable; +use Illuminate\Support\Traits\TransformsToResourceCollection; use Stringable; use Traversable; @@ -18,9 +20,9 @@ * * @mixin \Illuminate\Support\Collection */ -abstract class AbstractPaginator implements Htmlable, Stringable +abstract class AbstractPaginator implements CanBeEscapedWhenCastToString, Htmlable, Stringable { - use ForwardsCalls, Tappable; + use ForwardsCalls, Tappable, TransformsToResourceCollection; /** * All of the items being paginated. @@ -71,6 +73,13 @@ abstract class AbstractPaginator implements Htmlable, Stringable */ protected $pageName = 'page'; + /** + * Indicates that the paginator's string representation should be escaped when __toString is invoked. + * + * @var bool + */ + protected $escapeWhenCastingToString = false; + /** * The number of links to display on each side of current page link. * @@ -797,6 +806,21 @@ public function __call($method, $parameters) */ public function __toString() { - return (string) $this->render(); + return $this->escapeWhenCastingToString + ? e((string) $this->render()) + : (string) $this->render(); + } + + /** + * Indicate that the paginator's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true) + { + $this->escapeWhenCastingToString = $escape; + + return $this; } } diff --git a/src/Illuminate/Pagination/CursorPaginator.php b/src/Illuminate/Pagination/CursorPaginator.php index 1a5e08e92ec9..e68281086ab8 100644 --- a/src/Illuminate/Pagination/CursorPaginator.php +++ b/src/Illuminate/Pagination/CursorPaginator.php @@ -39,7 +39,6 @@ class CursorPaginator extends AbstractCursorPaginator implements Arrayable, Arra * @param int $perPage * @param \Illuminate\Pagination\Cursor|null $cursor * @param array $options (path, query, fragment, pageName) - * @return void */ public function __construct($items, $perPage, $cursor = null, array $options = []) { diff --git a/src/Illuminate/Pagination/LengthAwarePaginator.php b/src/Illuminate/Pagination/LengthAwarePaginator.php index 6cde4aa4571c..2a6fd8d2149c 100644 --- a/src/Illuminate/Pagination/LengthAwarePaginator.php +++ b/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -47,7 +47,6 @@ class LengthAwarePaginator extends AbstractPaginator implements Arrayable, Array * @param int $perPage * @param int|null $currentPage * @param array $options (path, query, fragment, pageName) - * @return void */ public function __construct($items, $total, $perPage, $currentPage = null, array $options = []) { diff --git a/src/Illuminate/Pagination/Paginator.php b/src/Illuminate/Pagination/Paginator.php index c2bc470be576..489b58fc2a8f 100644 --- a/src/Illuminate/Pagination/Paginator.php +++ b/src/Illuminate/Pagination/Paginator.php @@ -39,7 +39,6 @@ class Paginator extends AbstractPaginator implements Arrayable, ArrayAccess, Cou * @param int $perPage * @param int|null $currentPage * @param array $options (path, query, fragment, pageName) - * @return void */ public function __construct($items, $perPage, $currentPage = null, array $options = []) { diff --git a/src/Illuminate/Pagination/UrlWindow.php b/src/Illuminate/Pagination/UrlWindow.php index 99286f7b3b8e..863d4c598c48 100644 --- a/src/Illuminate/Pagination/UrlWindow.php +++ b/src/Illuminate/Pagination/UrlWindow.php @@ -17,7 +17,6 @@ class UrlWindow * Create a new URL window instance. * * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator - * @return void */ public function __construct(PaginatorContract $paginator) { diff --git a/src/Illuminate/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index 908762c245bd..b848b4a3b08d 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -16,9 +16,9 @@ "require": { "php": "^8.2", "ext-filter": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -27,7 +27,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Pipeline/Hub.php b/src/Illuminate/Pipeline/Hub.php index 6f284fe7ae19..0f738a62ae4a 100644 --- a/src/Illuminate/Pipeline/Hub.php +++ b/src/Illuminate/Pipeline/Hub.php @@ -26,7 +26,6 @@ class Hub implements HubContract * Create a new Hub instance. * * @param \Illuminate\Contracts\Container\Container|null $container - * @return void */ public function __construct(?Container $container = null) { diff --git a/src/Illuminate/Pipeline/Pipeline.php b/src/Illuminate/Pipeline/Pipeline.php index 6a9f0029735e..294d0d456161 100644 --- a/src/Illuminate/Pipeline/Pipeline.php +++ b/src/Illuminate/Pipeline/Pipeline.php @@ -52,7 +52,6 @@ class Pipeline implements PipelineContract * Create a new class instance. * * @param \Illuminate\Contracts\Container\Container|null $container - * @return void */ public function __construct(?Container $container = null) { @@ -206,8 +205,8 @@ protected function carry() } $carry = method_exists($pipe, $this->method) - ? $pipe->{$this->method}(...$parameters) - : $pipe(...$parameters); + ? $pipe->{$this->method}(...$parameters) + : $pipe(...$parameters); return $this->handleCarry($carry); } catch (Throwable $e) { diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index 9cd62e83f78c..fbc26da597d0 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -15,8 +15,8 @@ ], "require": { "php": "^8.2", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0" + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Process/Exceptions/ProcessFailedException.php b/src/Illuminate/Process/Exceptions/ProcessFailedException.php index 6747c11867b3..c4a311864976 100644 --- a/src/Illuminate/Process/Exceptions/ProcessFailedException.php +++ b/src/Illuminate/Process/Exceptions/ProcessFailedException.php @@ -18,7 +18,6 @@ class ProcessFailedException extends RuntimeException * Create a new exception instance. * * @param \Illuminate\Contracts\Process\ProcessResult $result - * @return void */ public function __construct(ProcessResult $result) { diff --git a/src/Illuminate/Process/Exceptions/ProcessTimedOutException.php b/src/Illuminate/Process/Exceptions/ProcessTimedOutException.php index 5f939f8356a9..9c3c8988a7cb 100644 --- a/src/Illuminate/Process/Exceptions/ProcessTimedOutException.php +++ b/src/Illuminate/Process/Exceptions/ProcessTimedOutException.php @@ -20,7 +20,6 @@ class ProcessTimedOutException extends RuntimeException * * @param \Symfony\Component\Process\Exception\ProcessTimedOutException $original * @param \Illuminate\Contracts\Process\ProcessResult $result - * @return void */ public function __construct(SymfonyTimeoutException $original, ProcessResult $result) { diff --git a/src/Illuminate/Process/Factory.php b/src/Illuminate/Process/Factory.php index b37246a8d75d..e30fa8c6c99f 100644 --- a/src/Illuminate/Process/Factory.php +++ b/src/Illuminate/Process/Factory.php @@ -104,8 +104,8 @@ public function fake(Closure|array|null $callback = null) foreach ($callback as $command => $handler) { $this->fakeHandlers[is_numeric($command) ? '*' : $command] = $handler instanceof Closure - ? $handler - : fn () => $handler; + ? $handler + : fn () => $handler; } return $this; diff --git a/src/Illuminate/Process/FakeInvokedProcess.php b/src/Illuminate/Process/FakeInvokedProcess.php index c82f53c869d1..461511e433b2 100644 --- a/src/Illuminate/Process/FakeInvokedProcess.php +++ b/src/Illuminate/Process/FakeInvokedProcess.php @@ -60,7 +60,6 @@ class FakeInvokedProcess implements InvokedProcessContract * * @param string $command * @param \Illuminate\Process\FakeProcessDescription $process - * @return void */ public function __construct(string $command, FakeProcessDescription $process) { @@ -116,8 +115,8 @@ public function running() $this->invokeOutputHandlerWithNextLineOfOutput(); $this->remainingRunIterations = is_null($this->remainingRunIterations) - ? $this->process->runIterations - : $this->remainingRunIterations; + ? $this->process->runIterations + : $this->remainingRunIterations; if ($this->remainingRunIterations === 0) { while ($this->invokeOutputHandlerWithNextLineOfOutput()) { diff --git a/src/Illuminate/Process/FakeProcessDescription.php b/src/Illuminate/Process/FakeProcessDescription.php index b6a7f7a16a8d..1c397176eafb 100644 --- a/src/Illuminate/Process/FakeProcessDescription.php +++ b/src/Illuminate/Process/FakeProcessDescription.php @@ -94,9 +94,10 @@ public function errorOutput(array|string $output) */ public function replaceOutput(string $output) { - $this->output = (new Collection($this->output))->reject(function ($output) { - return $output['type'] === 'out'; - })->values()->all(); + $this->output = (new Collection($this->output)) + ->reject(fn ($output) => $output['type'] === 'out') + ->values() + ->all(); if (strlen($output) > 0) { $this->output[] = [ @@ -116,9 +117,10 @@ public function replaceOutput(string $output) */ public function replaceErrorOutput(string $output) { - $this->output = (new Collection($this->output))->reject(function ($output) { - return $output['type'] === 'err'; - })->values()->all(); + $this->output = (new Collection($this->output)) + ->reject(fn ($output) => $output['type'] === 'err') + ->values() + ->all(); if (strlen($output) > 0) { $this->output[] = [ @@ -205,8 +207,8 @@ protected function resolveOutput() ->filter(fn ($output) => $output['type'] === 'out'); return $output->isNotEmpty() - ? rtrim($output->map->buffer->implode(''), "\n")."\n" - : ''; + ? rtrim($output->map->buffer->implode(''), "\n")."\n" + : ''; } /** @@ -220,7 +222,7 @@ protected function resolveErrorOutput() ->filter(fn ($output) => $output['type'] === 'err'); return $output->isNotEmpty() - ? rtrim($output->map->buffer->implode(''), "\n")."\n" - : ''; + ? rtrim($output->map->buffer->implode(''), "\n")."\n" + : ''; } } diff --git a/src/Illuminate/Process/FakeProcessResult.php b/src/Illuminate/Process/FakeProcessResult.php index 665f77865dd2..abb6a34755fa 100644 --- a/src/Illuminate/Process/FakeProcessResult.php +++ b/src/Illuminate/Process/FakeProcessResult.php @@ -43,7 +43,6 @@ class FakeProcessResult implements ProcessResultContract * @param int $exitCode * @param array|string $output * @param array|string $errorOutput - * @return void */ public function __construct(string $command = '', int $exitCode = 0, array|string $output = '', array|string $errorOutput = '') { diff --git a/src/Illuminate/Process/FakeProcessSequence.php b/src/Illuminate/Process/FakeProcessSequence.php index f039ec37d4dc..6015309aa733 100644 --- a/src/Illuminate/Process/FakeProcessSequence.php +++ b/src/Illuminate/Process/FakeProcessSequence.php @@ -32,7 +32,6 @@ class FakeProcessSequence * Create a new fake process sequence instance. * * @param array $processes - * @return void */ public function __construct(array $processes = []) { @@ -75,8 +74,8 @@ public function whenEmpty(ProcessResultContract|FakeProcessDescription|array|str protected function toProcessResult(ProcessResultContract|FakeProcessDescription|array|string $process) { return is_array($process) || is_string($process) - ? new FakeProcessResult(output: $process) - : $process; + ? new FakeProcessResult(output: $process) + : $process; } /** diff --git a/src/Illuminate/Process/InvokedProcess.php b/src/Illuminate/Process/InvokedProcess.php index d19230ce8a44..6e2e9a84612b 100644 --- a/src/Illuminate/Process/InvokedProcess.php +++ b/src/Illuminate/Process/InvokedProcess.php @@ -20,7 +20,6 @@ class InvokedProcess implements InvokedProcessContract * Create a new invoked process instance. * * @param \Symfony\Component\Process\Process $process - * @return void */ public function __construct(Process $process) { @@ -112,6 +111,22 @@ public function latestErrorOutput() return $this->process->getIncrementalErrorOutput(); } + /** + * Ensure that the process has not timed out. + * + * @return void + * + * @throws \Illuminate\Process\Exceptions\ProcessTimedOutException + */ + public function ensureNotTimedOut() + { + try { + $this->process->checkTimeout(); + } catch (SymfonyTimeoutException $e) { + throw new ProcessTimedOutException($e, new ProcessResult($this->process)); + } + } + /** * Wait for the process to finish. * diff --git a/src/Illuminate/Process/InvokedProcessPool.php b/src/Illuminate/Process/InvokedProcessPool.php index eb3df2130194..67b0d3d90277 100644 --- a/src/Illuminate/Process/InvokedProcessPool.php +++ b/src/Illuminate/Process/InvokedProcessPool.php @@ -18,7 +18,6 @@ class InvokedProcessPool implements Countable * Create a new invoked process pool. * * @param array $invokedProcesses - * @return void */ public function __construct(array $invokedProcesses) { diff --git a/src/Illuminate/Process/PendingProcess.php b/src/Illuminate/Process/PendingProcess.php index 454513fd1996..90d324e7c74e 100644 --- a/src/Illuminate/Process/PendingProcess.php +++ b/src/Illuminate/Process/PendingProcess.php @@ -97,7 +97,6 @@ class PendingProcess * Create a new pending process instance. * * @param \Illuminate\Process\Factory $factory - * @return void */ public function __construct(Factory $factory) { @@ -299,8 +298,8 @@ protected function toSymfonyProcess(array|string|null $command) $command = $command ?? $this->command; $process = is_iterable($command) - ? new Process($command, null, $this->environment) - : Process::fromShellCommandline((string) $command, null, $this->environment); + ? new Process($command, null, $this->environment) + : Process::fromShellCommandline((string) $command, null, $this->environment); $process->setWorkingDirectory((string) ($this->path ?? getcwd())); $process->setTimeout($this->timeout); diff --git a/src/Illuminate/Process/Pipe.php b/src/Illuminate/Process/Pipe.php index 06e7e16598e8..043ddb77e776 100644 --- a/src/Illuminate/Process/Pipe.php +++ b/src/Illuminate/Process/Pipe.php @@ -37,7 +37,6 @@ class Pipe * * @param \Illuminate\Process\Factory $factory * @param callable $callback - * @return void */ public function __construct(Factory $factory, callable $callback) { diff --git a/src/Illuminate/Process/Pool.php b/src/Illuminate/Process/Pool.php index 1a98a8541a57..77d4b249ef01 100644 --- a/src/Illuminate/Process/Pool.php +++ b/src/Illuminate/Process/Pool.php @@ -37,7 +37,6 @@ class Pool * * @param \Illuminate\Process\Factory $factory * @param callable $callback - * @return void */ public function __construct(Factory $factory, callable $callback) { @@ -74,7 +73,8 @@ public function start(?callable $output = null) if (! $pendingProcess instanceof PendingProcess) { throw new InvalidArgumentException('Process pool must only contain pending processes.'); } - })->mapWithKeys(function ($pendingProcess, $key) use ($output) { + }) + ->mapWithKeys(function ($pendingProcess, $key) use ($output) { return [$key => $pendingProcess->start(output: $output ? function ($type, $buffer) use ($key, $output) { $output($type, $buffer, $key); } : null)]; diff --git a/src/Illuminate/Process/ProcessPoolResults.php b/src/Illuminate/Process/ProcessPoolResults.php index debd3e09db6e..106aa94d54fc 100644 --- a/src/Illuminate/Process/ProcessPoolResults.php +++ b/src/Illuminate/Process/ProcessPoolResults.php @@ -18,7 +18,6 @@ class ProcessPoolResults implements ArrayAccess * Create a new process pool result set. * * @param array $results - * @return void */ public function __construct(array $results) { diff --git a/src/Illuminate/Process/ProcessResult.php b/src/Illuminate/Process/ProcessResult.php index 9bbf9c4aacc5..49900f537fab 100644 --- a/src/Illuminate/Process/ProcessResult.php +++ b/src/Illuminate/Process/ProcessResult.php @@ -19,7 +19,6 @@ class ProcessResult implements ProcessResultContract * Create a new process result instance. * * @param \Symfony\Component\Process\Process $process - * @return void */ public function __construct(Process $process) { diff --git a/src/Illuminate/Process/composer.json b/src/Illuminate/Process/composer.json index 0425904ef047..63c4afad444f 100644 --- a/src/Illuminate/Process/composer.json +++ b/src/Illuminate/Process/composer.json @@ -15,11 +15,11 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", - "symfony/process": "^7.0.3" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "symfony/process": "^7.2.0" }, "autoload": { "psr-4": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Queue/BeanstalkdQueue.php b/src/Illuminate/Queue/BeanstalkdQueue.php index c13dc1c97e76..56e9c4e0664b 100755 --- a/src/Illuminate/Queue/BeanstalkdQueue.php +++ b/src/Illuminate/Queue/BeanstalkdQueue.php @@ -48,7 +48,6 @@ class BeanstalkdQueue extends Queue implements QueueContract * @param int $timeToRun * @param int $blockFor * @param bool $dispatchAfterCommit - * @return void */ public function __construct( $pheanstalk, @@ -126,7 +125,7 @@ public function later($delay, $job, $data = '', $queue = null) { return $this->enqueueUsing( $job, - $this->createPayload($job, $this->getQueue($queue), $data), + $this->createPayload($job, $this->getQueue($queue), $data, $delay), $queue, $delay, function ($payload, $queue, $delay) { diff --git a/src/Illuminate/Queue/CallQueuedClosure.php b/src/Illuminate/Queue/CallQueuedClosure.php index 58155523b283..732600ccfea1 100644 --- a/src/Illuminate/Queue/CallQueuedClosure.php +++ b/src/Illuminate/Queue/CallQueuedClosure.php @@ -40,7 +40,6 @@ class CallQueuedClosure implements ShouldQueue * Create a new job instance. * * @param \Laravel\SerializableClosure\SerializableClosure $closure - * @return void */ public function __construct($closure) { @@ -78,8 +77,8 @@ public function handle(Container $container) public function onFailure($callback) { $this->failureCallbacks[] = $callback instanceof Closure - ? new SerializableClosure($callback) - : $callback; + ? new SerializableClosure($callback) + : $callback; return $this; } diff --git a/src/Illuminate/Queue/CallQueuedHandler.php b/src/Illuminate/Queue/CallQueuedHandler.php index 12f49b495152..c7b887a4d953 100644 --- a/src/Illuminate/Queue/CallQueuedHandler.php +++ b/src/Illuminate/Queue/CallQueuedHandler.php @@ -41,7 +41,6 @@ class CallQueuedHandler * * @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher * @param \Illuminate\Contracts\Container\Container $container - * @return void */ public function __construct(Dispatcher $dispatcher, Container $container) { @@ -218,7 +217,7 @@ protected function ensureUniqueJobLockIsReleased($command) */ protected function handleModelNotFound(Job $job, $e) { - $class = $job->resolveName(); + $class = $job->resolveQueuedJobClass(); try { $reflectionClass = new ReflectionClass($class); @@ -275,12 +274,17 @@ protected function ensureUniqueJobLockIsReleasedViaContext() * @param array $data * @param \Throwable|null $e * @param string $uuid + * @param \Illuminate\Contracts\Queue\Job|null $job * @return void */ - public function failed(array $data, $e, string $uuid) + public function failed(array $data, $e, string $uuid, ?Job $job = null) { $command = $this->getCommand($data); + if (! is_null($job)) { + $command = $this->setJobInstanceIfNecessary($job, $command); + } + if (! $command instanceof ShouldBeUniqueUntilProcessing) { $this->ensureUniqueJobLockIsReleased($command); } diff --git a/src/Illuminate/Queue/Capsule/Manager.php b/src/Illuminate/Queue/Capsule/Manager.php index f6c263d17c6b..d8927032b861 100644 --- a/src/Illuminate/Queue/Capsule/Manager.php +++ b/src/Illuminate/Queue/Capsule/Manager.php @@ -26,7 +26,6 @@ class Manager * Create a new queue capsule manager. * * @param \Illuminate\Container\Container|null $container - * @return void */ public function __construct(?Container $container = null) { diff --git a/src/Illuminate/Queue/Connectors/DatabaseConnector.php b/src/Illuminate/Queue/Connectors/DatabaseConnector.php index eeabc8ee7f7f..9dc970b02bef 100644 --- a/src/Illuminate/Queue/Connectors/DatabaseConnector.php +++ b/src/Illuminate/Queue/Connectors/DatabaseConnector.php @@ -18,7 +18,6 @@ class DatabaseConnector implements ConnectorInterface * Create a new connector instance. * * @param \Illuminate\Database\ConnectionResolverInterface $connections - * @return void */ public function __construct(ConnectionResolverInterface $connections) { diff --git a/src/Illuminate/Queue/Connectors/RedisConnector.php b/src/Illuminate/Queue/Connectors/RedisConnector.php index d442eea99f11..ed5235fa3047 100644 --- a/src/Illuminate/Queue/Connectors/RedisConnector.php +++ b/src/Illuminate/Queue/Connectors/RedisConnector.php @@ -26,7 +26,6 @@ class RedisConnector implements ConnectorInterface * * @param \Illuminate\Contracts\Redis\Factory $redis * @param string|null $connection - * @return void */ public function __construct(Redis $redis, $connection = null) { diff --git a/src/Illuminate/Queue/Console/ClearCommand.php b/src/Illuminate/Queue/Console/ClearCommand.php index 8f4187bcac77..2ed23ffff590 100644 --- a/src/Illuminate/Queue/Console/ClearCommand.php +++ b/src/Illuminate/Queue/Console/ClearCommand.php @@ -42,7 +42,7 @@ public function handle() } $connection = $this->argument('connection') - ?: $this->laravel['config']['queue.default']; + ?: $this->laravel['config']['queue.default']; // We need to get the right queue for the connection which is set in the queue // configuration file for the application. We will pull it based on the set diff --git a/src/Illuminate/Queue/Console/ListenCommand.php b/src/Illuminate/Queue/Console/ListenCommand.php index 54bbfd5dca49..eb545a1d7234 100755 --- a/src/Illuminate/Queue/Console/ListenCommand.php +++ b/src/Illuminate/Queue/Console/ListenCommand.php @@ -47,7 +47,6 @@ class ListenCommand extends Command * Create a new queue listen command. * * @param \Illuminate\Queue\Listener $listener - * @return void */ public function __construct(Listener $listener) { @@ -100,8 +99,8 @@ protected function getQueue($connection) protected function gatherOptions() { $backoff = $this->hasOption('backoff') - ? $this->option('backoff') - : $this->option('delay'); + ? $this->option('backoff') + : $this->option('delay'); return new ListenerOptions( name: $this->option('name'), diff --git a/src/Illuminate/Queue/Console/MonitorCommand.php b/src/Illuminate/Queue/Console/MonitorCommand.php index c3df987a4c4e..466a09501bc0 100644 --- a/src/Illuminate/Queue/Console/MonitorCommand.php +++ b/src/Illuminate/Queue/Console/MonitorCommand.php @@ -47,7 +47,6 @@ class MonitorCommand extends Command * * @param \Illuminate\Contracts\Queue\Factory $manager * @param \Illuminate\Contracts\Events\Dispatcher $events - * @return void */ public function __construct(Factory $manager, Dispatcher $events) { diff --git a/src/Illuminate/Queue/Console/RestartCommand.php b/src/Illuminate/Queue/Console/RestartCommand.php index 7ade21a02eeb..892380682528 100644 --- a/src/Illuminate/Queue/Console/RestartCommand.php +++ b/src/Illuminate/Queue/Console/RestartCommand.php @@ -37,7 +37,6 @@ class RestartCommand extends Command * Create a new queue restart command. * * @param \Illuminate\Contracts\Cache\Repository $cache - * @return void */ public function __construct(Cache $cache) { diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index 8e0cc2fe5044..d67553803704 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -194,8 +194,8 @@ protected function refreshRetryUntil($payload) $retryUntil = $instance->retryUntil(); $payload['retryUntil'] = $retryUntil instanceof DateTimeInterface - ? $retryUntil->getTimestamp() - : $retryUntil; + ? $retryUntil->getTimestamp() + : $retryUntil; } return json_encode($payload); diff --git a/src/Illuminate/Queue/Console/WorkCommand.php b/src/Illuminate/Queue/Console/WorkCommand.php index ace855f789a8..a03e63e91468 100644 --- a/src/Illuminate/Queue/Console/WorkCommand.php +++ b/src/Illuminate/Queue/Console/WorkCommand.php @@ -89,7 +89,6 @@ class WorkCommand extends Command * * @param \Illuminate\Queue\Worker $worker * @param \Illuminate\Contracts\Cache\Repository $cache - * @return void */ public function __construct(Worker $worker, Cache $cache) { @@ -116,7 +115,7 @@ public function handle() $this->listenForEvents(); $connection = $this->argument('connection') - ?: $this->laravel['config']['queue.default']; + ?: $this->laravel['config']['queue.default']; // We need to get the right queue for the connection which is set in the queue // configuration file for the application. We will pull it based on the set diff --git a/src/Illuminate/Queue/DatabaseQueue.php b/src/Illuminate/Queue/DatabaseQueue.php index 41533f084217..41d04e2b001c 100644 --- a/src/Illuminate/Queue/DatabaseQueue.php +++ b/src/Illuminate/Queue/DatabaseQueue.php @@ -51,7 +51,6 @@ class DatabaseQueue extends Queue implements QueueContract, ClearableQueue * @param string $default * @param int $retryAfter * @param bool $dispatchAfterCommit - * @return void */ public function __construct( Connection $database, @@ -127,7 +126,7 @@ public function later($delay, $job, $data = '', $queue = null) { return $this->enqueueUsing( $job, - $this->createPayload($job, $this->getQueue($queue), $data), + $this->createPayload($job, $this->getQueue($queue), $data, $delay), $queue, $delay, function ($payload, $queue, $delay) { diff --git a/src/Illuminate/Queue/Events/JobAttempted.php b/src/Illuminate/Queue/Events/JobAttempted.php index 6dfa2df148ab..b10c62903e77 100644 --- a/src/Illuminate/Queue/Events/JobAttempted.php +++ b/src/Illuminate/Queue/Events/JobAttempted.php @@ -4,40 +4,18 @@ class JobAttempted { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - - /** - * Indicates if an exception occurred while processing the job. - * - * @var bool - */ - public $exceptionOccurred; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @param bool $exceptionOccurred - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. + * @param bool $exceptionOccurred Indicates if an exception occurred while processing the job. */ - public function __construct($connectionName, $job, $exceptionOccurred = false) - { - $this->job = $job; - $this->connectionName = $connectionName; - $this->exceptionOccurred = $exceptionOccurred; + public function __construct( + public $connectionName, + public $job, + public $exceptionOccurred = false, + ) { } /** diff --git a/src/Illuminate/Queue/Events/JobExceptionOccurred.php b/src/Illuminate/Queue/Events/JobExceptionOccurred.php index 4bdf39226bf7..3d4661710033 100644 --- a/src/Illuminate/Queue/Events/JobExceptionOccurred.php +++ b/src/Illuminate/Queue/Events/JobExceptionOccurred.php @@ -4,39 +4,17 @@ class JobExceptionOccurred { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - - /** - * The exception instance. - * - * @var \Throwable - */ - public $exception; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @param \Throwable $exception - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. + * @param \Throwable $exception The exception instance. */ - public function __construct($connectionName, $job, $exception) - { - $this->job = $job; - $this->exception = $exception; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + public $exception, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobFailed.php b/src/Illuminate/Queue/Events/JobFailed.php index d973a5039ed8..0c616fdcf95a 100644 --- a/src/Illuminate/Queue/Events/JobFailed.php +++ b/src/Illuminate/Queue/Events/JobFailed.php @@ -4,39 +4,17 @@ class JobFailed { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - - /** - * The exception that caused the job to fail. - * - * @var \Throwable - */ - public $exception; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @param \Throwable $exception - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. + * @param \Throwable $exception The exception that caused the job to fail. */ - public function __construct($connectionName, $job, $exception) - { - $this->job = $job; - $this->exception = $exception; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + public $exception, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobPopped.php b/src/Illuminate/Queue/Events/JobPopped.php index e56e11d30899..eeda0c8cd9a8 100644 --- a/src/Illuminate/Queue/Events/JobPopped.php +++ b/src/Illuminate/Queue/Events/JobPopped.php @@ -4,30 +4,15 @@ class JobPopped { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job|null - */ - public $job; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job|null $job - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job|null $job The job instance. */ - public function __construct($connectionName, $job) - { - $this->connectionName = $connectionName; - $this->job = $job; + public function __construct( + public $connectionName, + public $job, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobPopping.php b/src/Illuminate/Queue/Events/JobPopping.php index abb0bb41da7f..6ac966695dd2 100644 --- a/src/Illuminate/Queue/Events/JobPopping.php +++ b/src/Illuminate/Queue/Events/JobPopping.php @@ -4,21 +4,13 @@ class JobPopping { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - /** * Create a new event instance. * - * @param string $connectionName - * @return void + * @param string $connectionName The connection name. */ - public function __construct($connectionName) - { - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobProcessed.php b/src/Illuminate/Queue/Events/JobProcessed.php index f8abefb67f57..0c3db8264ccd 100644 --- a/src/Illuminate/Queue/Events/JobProcessed.php +++ b/src/Illuminate/Queue/Events/JobProcessed.php @@ -4,30 +4,15 @@ class JobProcessed { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. */ - public function __construct($connectionName, $job) - { - $this->job = $job; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobProcessing.php b/src/Illuminate/Queue/Events/JobProcessing.php index 3dd97248b003..4c1bf8bbee8f 100644 --- a/src/Illuminate/Queue/Events/JobProcessing.php +++ b/src/Illuminate/Queue/Events/JobProcessing.php @@ -4,30 +4,15 @@ class JobProcessing { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. */ - public function __construct($connectionName, $job) - { - $this->job = $job; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobQueued.php b/src/Illuminate/Queue/Events/JobQueued.php index 5e992238aa85..2418171ac9f8 100644 --- a/src/Illuminate/Queue/Events/JobQueued.php +++ b/src/Illuminate/Queue/Events/JobQueued.php @@ -4,67 +4,24 @@ class JobQueued { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The queue name. - * - * @var string|null - */ - public $queue; - - /** - * The job ID. - * - * @var string|int|null - */ - public $id; - - /** - * The job instance. - * - * @var \Closure|string|object - */ - public $job; - - /** - * The job payload. - * - * @var string - */ - public $payload; - - /** - * The amount of time the job was delayed. - * - * @var int|null - */ - public $delay; - /** * Create a new event instance. * - * @param string $connectionName - * @param string $queue - * @param string|int|null $id - * @param \Closure|string|object $job - * @param string $payload - * @param int|null $delay - * @return void - */ - public function __construct($connectionName, $queue, $id, $job, $payload, $delay) - { - $this->connectionName = $connectionName; - $this->queue = $queue; - $this->id = $id; - $this->job = $job; - $this->payload = $payload; - $this->delay = $delay; + * @param string $connectionName The connection name. + * @param string|null $queue The queue name. + * @param string|int|null $id The job ID. + * @param \Closure|string|object $job The job instance. + * @param string $payload The job payload. + * @param int|null $delay The amount of time the job was delayed. + */ + public function __construct( + public $connectionName, + public $queue, + public $id, + public $job, + public $payload, + public $delay, + ) { } /** diff --git a/src/Illuminate/Queue/Events/JobQueueing.php b/src/Illuminate/Queue/Events/JobQueueing.php index 6a35a64b8cca..5cc3e449d0cf 100644 --- a/src/Illuminate/Queue/Events/JobQueueing.php +++ b/src/Illuminate/Queue/Events/JobQueueing.php @@ -4,58 +4,22 @@ class JobQueueing { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The queue name. - * - * @var string - */ - public $queue; - - /** - * The job instance. - * - * @var \Closure|string|object - */ - public $job; - - /** - * The job payload. - * - * @var string - */ - public $payload; - - /** - * The number of seconds the job was delayed. - * - * @var int|null - */ - public $delay; - /** * Create a new event instance. * - * @param string $connectionName - * @param string $queue - * @param \Closure|string|object $job - * @param string $payload - * @param int|null $delay - * @return void + * @param string $connectionName The connection name. + * @param string|null $queue The queue name. + * @param \Closure|string|object $job The job instance. + * @param string $payload The job payload. + * @param int|null $delay The number of seconds the job was delayed. */ - public function __construct($connectionName, $queue, $job, $payload, $delay) - { - $this->connectionName = $connectionName; - $this->queue = $queue; - $this->job = $job; - $this->payload = $payload; - $this->delay = $delay; + public function __construct( + public $connectionName, + public $queue, + public $job, + public $payload, + public $delay, + ) { } /** diff --git a/src/Illuminate/Queue/Events/JobReleasedAfterException.php b/src/Illuminate/Queue/Events/JobReleasedAfterException.php index 4600c0b14bd3..7dc248084832 100644 --- a/src/Illuminate/Queue/Events/JobReleasedAfterException.php +++ b/src/Illuminate/Queue/Events/JobReleasedAfterException.php @@ -4,30 +4,15 @@ class JobReleasedAfterException { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. */ - public function __construct($connectionName, $job) - { - $this->job = $job; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + ) { } } diff --git a/src/Illuminate/Queue/Events/JobRetryRequested.php b/src/Illuminate/Queue/Events/JobRetryRequested.php index 9b9809f63950..929b4a89e4d8 100644 --- a/src/Illuminate/Queue/Events/JobRetryRequested.php +++ b/src/Illuminate/Queue/Events/JobRetryRequested.php @@ -4,13 +4,6 @@ class JobRetryRequested { - /** - * The job instance. - * - * @var \stdClass - */ - public $job; - /** * The decoded job payload. * @@ -21,12 +14,11 @@ class JobRetryRequested /** * Create a new event instance. * - * @param \stdClass $job - * @return void + * @param \stdClass $job The job instance. */ - public function __construct($job) - { - $this->job = $job; + public function __construct( + public $job, + ) { } /** diff --git a/src/Illuminate/Queue/Events/JobTimedOut.php b/src/Illuminate/Queue/Events/JobTimedOut.php index c36dea5352e3..4ef5f2ad5cc9 100644 --- a/src/Illuminate/Queue/Events/JobTimedOut.php +++ b/src/Illuminate/Queue/Events/JobTimedOut.php @@ -4,30 +4,15 @@ class JobTimedOut { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The job instance. - * - * @var \Illuminate\Contracts\Queue\Job - */ - public $job; - /** * Create a new event instance. * - * @param string $connectionName - * @param \Illuminate\Contracts\Queue\Job $job - * @return void + * @param string $connectionName The connection name. + * @param \Illuminate\Contracts\Queue\Job $job The job instance. */ - public function __construct($connectionName, $job) - { - $this->job = $job; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $job, + ) { } } diff --git a/src/Illuminate/Queue/Events/Looping.php b/src/Illuminate/Queue/Events/Looping.php index f9538e4c504f..84088c3107b2 100644 --- a/src/Illuminate/Queue/Events/Looping.php +++ b/src/Illuminate/Queue/Events/Looping.php @@ -4,30 +4,15 @@ class Looping { - /** - * The connection name. - * - * @var string - */ - public $connectionName; - - /** - * The queue name. - * - * @var string - */ - public $queue; - /** * Create a new event instance. * - * @param string $connectionName - * @param string $queue - * @return void + * @param string $connectionName The connection name. + * @param string $queue The queue name. */ - public function __construct($connectionName, $queue) - { - $this->queue = $queue; - $this->connectionName = $connectionName; + public function __construct( + public $connectionName, + public $queue, + ) { } } diff --git a/src/Illuminate/Queue/Events/QueueBusy.php b/src/Illuminate/Queue/Events/QueueBusy.php index 684dec4ea08a..7219c2f9702d 100644 --- a/src/Illuminate/Queue/Events/QueueBusy.php +++ b/src/Illuminate/Queue/Events/QueueBusy.php @@ -4,39 +4,17 @@ class QueueBusy { - /** - * The connection name. - * - * @var string - */ - public $connection; - - /** - * The queue name. - * - * @var string - */ - public $queue; - - /** - * The size of the queue. - * - * @var int - */ - public $size; - /** * Create a new event instance. * - * @param string $connection - * @param string $queue - * @param int $size - * @return void + * @param string $connection The connection name. + * @param string $queue The queue name. + * @param int $size The size of the queue. */ - public function __construct($connection, $queue, $size) - { - $this->connection = $connection; - $this->queue = $queue; - $this->size = $size; + public function __construct( + public $connection, + public $queue, + public $size, + ) { } } diff --git a/src/Illuminate/Queue/Events/WorkerStopping.php b/src/Illuminate/Queue/Events/WorkerStopping.php index ccebb3cbb01a..ae38a3d2c786 100644 --- a/src/Illuminate/Queue/Events/WorkerStopping.php +++ b/src/Illuminate/Queue/Events/WorkerStopping.php @@ -4,30 +4,15 @@ class WorkerStopping { - /** - * The worker exit status. - * - * @var int - */ - public $status; - - /** - * The worker options. - * - * @var \Illuminate\Queue\WorkerOptions|null - */ - public $workerOptions; - /** * Create a new event instance. * - * @param int $status - * @param \Illuminate\Queue\WorkerOptions|null $workerOptions - * @return void + * @param int $status The worker exit status. + * @param \Illuminate\Queue\WorkerOptions|null $workerOptions The worker options. */ - public function __construct($status = 0, $workerOptions = null) - { - $this->status = $status; - $this->workerOptions = $workerOptions; + public function __construct( + public $status = 0, + public $workerOptions = null + ) { } } diff --git a/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php b/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php index 49cb3b98ae9a..be34d642d0b3 100644 --- a/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php +++ b/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php @@ -35,7 +35,6 @@ class DatabaseFailedJobProvider implements CountableFailedJobProvider, FailedJob * @param \Illuminate\Database\ConnectionResolverInterface $resolver * @param string $database * @param string $table - * @return void */ public function __construct(ConnectionResolverInterface $resolver, $database, $table) { diff --git a/src/Illuminate/Queue/Failed/DatabaseUuidFailedJobProvider.php b/src/Illuminate/Queue/Failed/DatabaseUuidFailedJobProvider.php index b3192f246beb..2eb47255d3f4 100644 --- a/src/Illuminate/Queue/Failed/DatabaseUuidFailedJobProvider.php +++ b/src/Illuminate/Queue/Failed/DatabaseUuidFailedJobProvider.php @@ -35,7 +35,6 @@ class DatabaseUuidFailedJobProvider implements CountableFailedJobProvider, Faile * @param \Illuminate\Database\ConnectionResolverInterface $resolver * @param string $database * @param string $table - * @return void */ public function __construct(ConnectionResolverInterface $resolver, $database, $table) { diff --git a/src/Illuminate/Queue/Failed/DynamoDbFailedJobProvider.php b/src/Illuminate/Queue/Failed/DynamoDbFailedJobProvider.php index c76a55ca8b1c..86c1635ab477 100644 --- a/src/Illuminate/Queue/Failed/DynamoDbFailedJobProvider.php +++ b/src/Illuminate/Queue/Failed/DynamoDbFailedJobProvider.php @@ -38,7 +38,6 @@ class DynamoDbFailedJobProvider implements FailedJobProviderInterface * @param \Aws\DynamoDb\DynamoDbClient $dynamo * @param string $applicationName * @param string $table - * @return void */ public function __construct(DynamoDbClient $dynamo, $applicationName, $table) { diff --git a/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php b/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php index bb52d1749ada..b7ce69fca8b4 100644 --- a/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php +++ b/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php @@ -2,9 +2,6 @@ namespace Illuminate\Queue\Failed; -/** - * @method array ids(string $queue = null) - */ interface FailedJobProviderInterface { /** @@ -18,6 +15,14 @@ interface FailedJobProviderInterface */ public function log($connection, $queue, $payload, $exception); + /** + * Get the IDs of all of the failed jobs. + * + * @param string|null $queue + * @return array + */ + public function ids($queue = null); + /** * Get a list of all of the failed jobs. * diff --git a/src/Illuminate/Queue/Failed/FileFailedJobProvider.php b/src/Illuminate/Queue/Failed/FileFailedJobProvider.php index dff5f44a6082..20faf2f90530 100644 --- a/src/Illuminate/Queue/Failed/FileFailedJobProvider.php +++ b/src/Illuminate/Queue/Failed/FileFailedJobProvider.php @@ -36,7 +36,6 @@ class FileFailedJobProvider implements CountableFailedJobProvider, FailedJobProv * @param string $path * @param int $limit * @param \Closure|null $lockProviderResolver - * @return void */ public function __construct($path, $limit = 100, ?Closure $lockProviderResolver = null) { @@ -155,9 +154,11 @@ public function prune(DateTimeInterface $before) return $this->lock(function () use ($before) { $jobs = $this->read(); - $this->write($prunedJobs = (new Collection($jobs))->reject(function ($job) use ($before) { - return $job->failed_at_timestamp <= $before->getTimestamp(); - })->values()->all()); + $this->write($prunedJobs = (new Collection($jobs)) + ->reject(fn ($job) => $job->failed_at_timestamp <= $before->getTimestamp()) + ->values() + ->all() + ); return count($jobs) - count($prunedJobs); }); diff --git a/src/Illuminate/Queue/InteractsWithQueue.php b/src/Illuminate/Queue/InteractsWithQueue.php index 4caea95c9c81..850c7c1c54bc 100644 --- a/src/Illuminate/Queue/InteractsWithQueue.php +++ b/src/Illuminate/Queue/InteractsWithQueue.php @@ -185,7 +185,7 @@ public function assertFailedWith($exception) PHPUnit::assertEquals( $exception->getMessage(), $this->job->failedWith->getMessage(), - 'Expected exceptoin message ['.$exception->getMessage().'] but job failed with exception message ['.$this->job->failedWith->getMessage().'].'); + 'Expected exception message ['.$exception->getMessage().'] but job failed with exception message ['.$this->job->failedWith->getMessage().'].'); } return $this; diff --git a/src/Illuminate/Queue/InvalidPayloadException.php b/src/Illuminate/Queue/InvalidPayloadException.php index abaf21f508f1..52cc4a2b9dfd 100644 --- a/src/Illuminate/Queue/InvalidPayloadException.php +++ b/src/Illuminate/Queue/InvalidPayloadException.php @@ -18,7 +18,6 @@ class InvalidPayloadException extends InvalidArgumentException * * @param string|null $message * @param mixed $value - * @return void */ public function __construct($message = null, $value = null) { diff --git a/src/Illuminate/Queue/Jobs/BeanstalkdJob.php b/src/Illuminate/Queue/Jobs/BeanstalkdJob.php index 10d0b3627c1d..52da72b41bca 100755 --- a/src/Illuminate/Queue/Jobs/BeanstalkdJob.php +++ b/src/Illuminate/Queue/Jobs/BeanstalkdJob.php @@ -31,7 +31,6 @@ class BeanstalkdJob extends Job implements JobContract * @param \Pheanstalk\Contract\JobIdInterface $job * @param string $connectionName * @param string $queue - * @return void */ public function __construct(Container $container, $pheanstalk, JobIdInterface $job, $connectionName, $queue) { diff --git a/src/Illuminate/Queue/Jobs/DatabaseJob.php b/src/Illuminate/Queue/Jobs/DatabaseJob.php index 77f50427d5eb..5a5644c499e7 100644 --- a/src/Illuminate/Queue/Jobs/DatabaseJob.php +++ b/src/Illuminate/Queue/Jobs/DatabaseJob.php @@ -30,7 +30,6 @@ class DatabaseJob extends Job implements JobContract * @param \stdClass $job * @param string $connectionName * @param string $queue - * @return void */ public function __construct(Container $container, DatabaseQueue $database, $job, $connectionName, $queue) { @@ -79,7 +78,7 @@ public function attempts() /** * Get the job identifier. * - * @return string + * @return string|int */ public function getJobId() { diff --git a/src/Illuminate/Queue/Jobs/DatabaseJobRecord.php b/src/Illuminate/Queue/Jobs/DatabaseJobRecord.php index b4b5725467ef..207f2b529c82 100644 --- a/src/Illuminate/Queue/Jobs/DatabaseJobRecord.php +++ b/src/Illuminate/Queue/Jobs/DatabaseJobRecord.php @@ -19,7 +19,6 @@ class DatabaseJobRecord * Create a new job record instance. * * @param \stdClass $record - * @return void */ public function __construct($record) { diff --git a/src/Illuminate/Queue/Jobs/FakeJob.php b/src/Illuminate/Queue/Jobs/FakeJob.php index ef1d4e8bc04a..e3567d1f70e2 100644 --- a/src/Illuminate/Queue/Jobs/FakeJob.php +++ b/src/Illuminate/Queue/Jobs/FakeJob.php @@ -2,9 +2,10 @@ namespace Illuminate\Queue\Jobs; +use Illuminate\Contracts\Queue\Job as JobContract; use Illuminate\Support\Str; -class FakeJob extends Job +class FakeJob extends Job implements JobContract { /** * The number of seconds the released job was delayed. diff --git a/src/Illuminate/Queue/Jobs/Job.php b/src/Illuminate/Queue/Jobs/Job.php index f690b64963be..112501b26580 100755 --- a/src/Illuminate/Queue/Jobs/Job.php +++ b/src/Illuminate/Queue/Jobs/Job.php @@ -67,7 +67,7 @@ abstract class Job /** * Get the job identifier. * - * @return string + * @return string|int|null */ abstract public function getJobId(); @@ -251,7 +251,7 @@ protected function failed($e) [$class, $method] = JobName::parse($payload['job']); if (method_exists($this->instance = $this->resolve($class), 'failed')) { - $this->instance->failed($payload['data'], $e, $payload['uuid'] ?? ''); + $this->instance->failed($payload['data'], $e, $payload['uuid'] ?? '', $this); } } @@ -357,7 +357,7 @@ public function getName() } /** - * Get the resolved name of the queued job class. + * Get the resolved display name of the queued job class. * * Resolves the name of "wrapped" jobs such as class-based handlers. * @@ -368,6 +368,18 @@ public function resolveName() return JobName::resolve($this->getName(), $this->payload()); } + /** + * Get the class of the queued job. + * + * Resolves the class of "wrapped" jobs such as class-based handlers. + * + * @return string + */ + public function resolveQueuedJobClass() + { + return JobName::resolveClassName($this->getName(), $this->payload()); + } + /** * Get the name of the connection the job belongs to. * diff --git a/src/Illuminate/Queue/Jobs/JobName.php b/src/Illuminate/Queue/Jobs/JobName.php index 0db53bb47e9c..fca1eebe9403 100644 --- a/src/Illuminate/Queue/Jobs/JobName.php +++ b/src/Illuminate/Queue/Jobs/JobName.php @@ -32,4 +32,20 @@ public static function resolve($name, $payload) return $name; } + + /** + * Get the class name for queued job class. + * + * @param string $name + * @param array $payload + * @return string + */ + public static function resolveClassName($name, $payload) + { + if (is_string($payload['data']['commandName'] ?? null)) { + return $payload['data']['commandName']; + } + + return $name; + } } diff --git a/src/Illuminate/Queue/Jobs/RedisJob.php b/src/Illuminate/Queue/Jobs/RedisJob.php index 1486ebcf9d9c..8fe55b03dbd4 100644 --- a/src/Illuminate/Queue/Jobs/RedisJob.php +++ b/src/Illuminate/Queue/Jobs/RedisJob.php @@ -45,7 +45,6 @@ class RedisJob extends Job implements JobContract * @param string $reserved * @param string $connectionName * @param string $queue - * @return void */ public function __construct(Container $container, RedisQueue $redis, $job, $reserved, $connectionName, $queue) { diff --git a/src/Illuminate/Queue/Jobs/SqsJob.php b/src/Illuminate/Queue/Jobs/SqsJob.php index 87b722e207b9..227c1b7b0ac1 100755 --- a/src/Illuminate/Queue/Jobs/SqsJob.php +++ b/src/Illuminate/Queue/Jobs/SqsJob.php @@ -30,7 +30,6 @@ class SqsJob extends Job implements JobContract * @param array $job * @param string $connectionName * @param string $queue - * @return void */ public function __construct(Container $container, SqsClient $sqs, array $job, $connectionName, $queue) { diff --git a/src/Illuminate/Queue/Jobs/SyncJob.php b/src/Illuminate/Queue/Jobs/SyncJob.php index fc9c7030fb59..a682fc962ae2 100755 --- a/src/Illuminate/Queue/Jobs/SyncJob.php +++ b/src/Illuminate/Queue/Jobs/SyncJob.php @@ -28,7 +28,6 @@ class SyncJob extends Job implements JobContract * @param string $payload * @param string $connectionName * @param string $queue - * @return void */ public function __construct(Container $container, $payload, $connectionName, $queue) { diff --git a/src/Illuminate/Queue/Listener.php b/src/Illuminate/Queue/Listener.php index de037d324297..fc56a569ebaa 100755 --- a/src/Illuminate/Queue/Listener.php +++ b/src/Illuminate/Queue/Listener.php @@ -49,7 +49,6 @@ class Listener * Create a new queue listener. * * @param string $commandPath - * @return void */ public function __construct($commandPath) { diff --git a/src/Illuminate/Queue/ListenerOptions.php b/src/Illuminate/Queue/ListenerOptions.php index d71989d90294..218a49851dea 100644 --- a/src/Illuminate/Queue/ListenerOptions.php +++ b/src/Illuminate/Queue/ListenerOptions.php @@ -23,7 +23,6 @@ class ListenerOptions extends WorkerOptions * @param int $maxTries * @param bool $force * @param int $rest - * @return void */ public function __construct($name = 'default', $environment = null, $backoff = 0, $memory = 128, $timeout = 60, $sleep = 3, $maxTries = 1, $force = false, $rest = 0) { diff --git a/src/Illuminate/Queue/Middleware/RateLimited.php b/src/Illuminate/Queue/Middleware/RateLimited.php index e6309ba4317c..1b4b2b78d941 100644 --- a/src/Illuminate/Queue/Middleware/RateLimited.php +++ b/src/Illuminate/Queue/Middleware/RateLimited.php @@ -36,7 +36,6 @@ class RateLimited * Create a new middleware instance. * * @param \BackedEnum|\UnitEnum|string $limiterName - * @return void */ public function __construct($limiterName) { @@ -90,8 +89,8 @@ protected function handleJob($job, $next, array $limits) foreach ($limits as $limit) { if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { return $this->shouldRelease - ? $job->release($this->getTimeUntilNextRetry($limit->key)) - : false; + ? $job->release($this->getTimeUntilNextRetry($limit->key)) + : false; } $this->limiter->hit($limit->key, $limit->decaySeconds); diff --git a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php index cbc809549333..bb87b101d404 100644 --- a/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php +++ b/src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php @@ -29,7 +29,6 @@ class RateLimitedWithRedis extends RateLimited * Create a new middleware instance. * * @param string $limiterName - * @return void */ public function __construct($limiterName) { diff --git a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php index 83d789616649..68017795655c 100644 --- a/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php +++ b/src/Illuminate/Queue/Middleware/ThrottlesExceptions.php @@ -76,7 +76,6 @@ class ThrottlesExceptions * * @param int $maxAttempts * @param int $decaySeconds - * @return void */ public function __construct($maxAttempts = 10, $decaySeconds = 600) { @@ -171,7 +170,7 @@ protected function getKey($job) return $this->prefix.$job->job->uuid(); } - return $this->prefix.md5(get_class($job)); + return $this->prefix.hash('xxh128', get_class($job)); } /** diff --git a/src/Illuminate/Queue/Middleware/WithoutOverlapping.php b/src/Illuminate/Queue/Middleware/WithoutOverlapping.php index 3b773adf43b9..42fabdaa3303 100644 --- a/src/Illuminate/Queue/Middleware/WithoutOverlapping.php +++ b/src/Illuminate/Queue/Middleware/WithoutOverlapping.php @@ -51,7 +51,6 @@ class WithoutOverlapping * @param string $key * @param \DateTimeInterface|int|null $releaseAfter * @param \DateTimeInterface|int $expiresAfter - * @return void */ public function __construct($key = '', $releaseAfter = 0, $expiresAfter = 0) { diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index a9d59e95ff27..49b3cdda6f2c 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -2,11 +2,15 @@ namespace Illuminate\Queue; +use Carbon\Carbon; use Closure; use DateTimeInterface; +use Illuminate\Bus\UniqueLock; use Illuminate\Container\Container; +use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Queue\ShouldBeEncrypted; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; use Illuminate\Queue\Events\JobQueued; use Illuminate\Queue\Events\JobQueueing; @@ -94,17 +98,24 @@ public function bulk($jobs, $data = '', $queue = null) * @param \Closure|string|object $job * @param string $queue * @param mixed $data + * @param \DateTimeInterface|\DateInterval|int|null $delay * @return string * * @throws \Illuminate\Queue\InvalidPayloadException */ - protected function createPayload($job, $queue, $data = '') + protected function createPayload($job, $queue, $data = '', $delay = null) { if ($job instanceof Closure) { $job = CallQueuedClosure::create($job); } - $payload = json_encode($value = $this->createPayloadArray($job, $queue, $data), \JSON_UNESCAPED_UNICODE); + $value = $this->createPayloadArray($job, $queue, $data); + + $value['delay'] = isset($delay) + ? $this->secondsUntil($delay) + : null; + + $payload = json_encode($value, \JSON_UNESCAPED_UNICODE); if (json_last_error() !== JSON_ERROR_NONE) { throw new InvalidPayloadException( @@ -126,8 +137,8 @@ protected function createPayload($job, $queue, $data = '') protected function createPayloadArray($job, $queue, $data = '') { return is_object($job) - ? $this->createObjectPayload($job, $queue) - : $this->createStringPayload($job, $queue, $data); + ? $this->createObjectPayload($job, $queue) + : $this->createStringPayload($job, $queue, $data); } /** @@ -153,11 +164,12 @@ protected function createObjectPayload($job, $queue) 'commandName' => $job, 'command' => $job, ], + 'createdAt' => Carbon::now()->getTimestamp(), ]); $command = $this->jobShouldBeEncrypted($job) && $this->container->bound(Encrypter::class) - ? $this->container[Encrypter::class]->encrypt(serialize(clone $job)) - : serialize(clone $job); + ? $this->container[Encrypter::class]->encrypt(serialize(clone $job)) + : serialize(clone $job); return array_merge($payload, [ 'data' => array_merge($payload['data'], [ @@ -176,7 +188,8 @@ protected function createObjectPayload($job, $queue) protected function getDisplayName($job) { return method_exists($job, 'displayName') - ? $job->displayName() : get_class($job); + ? $job->displayName() + : get_class($job); } /** @@ -234,7 +247,8 @@ public function getJobExpiration($job) $expiration = $job->retryUntil ?? $job->retryUntil(); return $expiration instanceof DateTimeInterface - ? $expiration->getTimestamp() : $expiration; + ? $expiration->getTimestamp() + : $expiration; } /** @@ -272,6 +286,7 @@ protected function createStringPayload($job, $queue, $data) 'backoff' => null, 'timeout' => null, 'data' => $data, + 'createdAt' => Carbon::now()->getTimestamp(), ]); } @@ -322,6 +337,14 @@ protected function enqueueUsing($job, $payload, $queue, $delay, $callback) { if ($this->shouldDispatchAfterCommit($job) && $this->container->bound('db.transactions')) { + if ($job instanceof ShouldBeUnique) { + $this->container->make('db.transactions')->addCallbackForRollback( + function () use ($job) { + (new UniqueLock($this->container->make(Cache::class)))->release($job); + } + ); + } + return $this->container->make('db.transactions')->addCallback( function () use ($queue, $job, $payload, $delay, $callback) { $this->raiseJobQueueingEvent($queue, $job, $payload, $delay); diff --git a/src/Illuminate/Queue/QueueManager.php b/src/Illuminate/Queue/QueueManager.php index 399b66bc8729..a809a736df00 100755 --- a/src/Illuminate/Queue/QueueManager.php +++ b/src/Illuminate/Queue/QueueManager.php @@ -37,7 +37,6 @@ class QueueManager implements FactoryContract, MonitorContract * Create a new queue manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { diff --git a/src/Illuminate/Queue/QueueServiceProvider.php b/src/Illuminate/Queue/QueueServiceProvider.php index c249d3842d7c..3b1d97208da0 100755 --- a/src/Illuminate/Queue/QueueServiceProvider.php +++ b/src/Illuminate/Queue/QueueServiceProvider.php @@ -216,6 +216,8 @@ protected function registerWorker() $app->forgetScopedInstances(); Facade::clearResolvedInstances(); + + memory_reset_peak_usage(); }; return new Worker( diff --git a/src/Illuminate/Queue/RedisQueue.php b/src/Illuminate/Queue/RedisQueue.php index 9e7c1c908886..84cfbde358cf 100644 --- a/src/Illuminate/Queue/RedisQueue.php +++ b/src/Illuminate/Queue/RedisQueue.php @@ -75,7 +75,6 @@ class RedisQueue extends Queue implements QueueContract, ClearableQueue * @param int|null $blockFor * @param bool $dispatchAfterCommit * @param int $migrationBatchSize - * @return void */ public function __construct( Redis $redis, @@ -193,7 +192,7 @@ public function later($delay, $job, $data = '', $queue = null) { return $this->enqueueUsing( $job, - $this->createPayload($job, $this->getQueue($queue), $data), + $this->createPayload($job, $this->getQueue($queue), $data, $delay), $queue, $delay, function ($payload, $queue, $delay) { diff --git a/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php b/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php index d46ff5255dac..25549425b7c0 100644 --- a/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php +++ b/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php @@ -59,8 +59,8 @@ protected function getRestoredPropertyValue($value) } return is_array($value->id) - ? $this->restoreCollection($value) - : $this->restoreModel($value); + ? $this->restoreCollection($value) + : $this->restoreModel($value); } /** @@ -107,7 +107,7 @@ public function restoreModel($value) { return $this->getQueryForModelRestoration( (new $value->class)->setConnection($value->connection), $value->id - )->useWritePdo()->firstOrFail()->load($value->relations ?? []); + )->useWritePdo()->firstOrFail()->loadMissing($value->relations ?? []); } /** diff --git a/src/Illuminate/Queue/SqsQueue.php b/src/Illuminate/Queue/SqsQueue.php index f840f7cf0e38..a128be81109f 100755 --- a/src/Illuminate/Queue/SqsQueue.php +++ b/src/Illuminate/Queue/SqsQueue.php @@ -46,7 +46,6 @@ class SqsQueue extends Queue implements QueueContract, ClearableQueue * @param string $prefix * @param string $suffix * @param bool $dispatchAfterCommit - * @return void */ public function __construct( SqsClient $sqs, @@ -129,7 +128,7 @@ public function later($delay, $job, $data = '', $queue = null) { return $this->enqueueUsing( $job, - $this->createPayload($job, $queue ?: $this->default, $data), + $this->createPayload($job, $queue ?: $this->default, $data, $delay), $queue, $delay, function ($payload, $queue, $delay) { diff --git a/src/Illuminate/Queue/SyncQueue.php b/src/Illuminate/Queue/SyncQueue.php index 57974960d4bb..b3413d6a5821 100755 --- a/src/Illuminate/Queue/SyncQueue.php +++ b/src/Illuminate/Queue/SyncQueue.php @@ -2,8 +2,11 @@ namespace Illuminate\Queue; +use Illuminate\Bus\UniqueLock; +use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Queue\Job; use Illuminate\Contracts\Queue\Queue as QueueContract; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Queue\Events\JobExceptionOccurred; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; @@ -16,7 +19,6 @@ class SyncQueue extends Queue implements QueueContract * Create a new sync queue instance. * * @param bool $dispatchAfterCommit - * @return void */ public function __construct($dispatchAfterCommit = false) { @@ -48,6 +50,14 @@ public function push($job, $data = '', $queue = null) { if ($this->shouldDispatchAfterCommit($job) && $this->container->bound('db.transactions')) { + if ($job instanceof ShouldBeUnique) { + $this->container->make('db.transactions')->addCallbackForRollback( + function () use ($job) { + (new UniqueLock($this->container->make(Cache::class)))->release($job); + } + ); + } + return $this->container->make('db.transactions')->addCallback( fn () => $this->executeJob($job, $data, $queue) ); diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index 49ad0aa3bce6..6ac69dcf5ec8 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -106,7 +106,6 @@ class Worker * @param \Illuminate\Contracts\Debug\ExceptionHandler $exceptions * @param callable $isDownForMaintenance * @param callable|null $resetScope - * @return void */ public function __construct( QueueManager $manager, @@ -617,8 +616,8 @@ protected function calculateBackoff($job, WorkerOptions $options) $backoff = explode( ',', method_exists($job, 'backoff') && ! is_null($job->backoff()) - ? $job->backoff() - : $options->backoff + ? $job->backoff() + : $options->backoff ); return (int) ($backoff[$job->attempts() - 1] ?? last($backoff)); @@ -748,7 +747,7 @@ protected function supportsAsyncSignals() */ public function memoryExceeded($memoryLimit) { - return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit; + return $memoryLimit > 0 && (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit; } /** diff --git a/src/Illuminate/Queue/WorkerOptions.php b/src/Illuminate/Queue/WorkerOptions.php index e3206bd7d9e9..036168b39e70 100644 --- a/src/Illuminate/Queue/WorkerOptions.php +++ b/src/Illuminate/Queue/WorkerOptions.php @@ -95,7 +95,6 @@ class WorkerOptions * @param int $maxJobs * @param int $maxTime * @param int $rest - * @return void */ public function __construct( $name = 'default', diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index 21e7db1787ec..197737e2d1b3 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -15,17 +15,17 @@ ], "require": { "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", + "illuminate/collections": "^12.0", + "illuminate/console": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/database": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/pipeline": "^12.0", + "illuminate/support": "^12.0", "laravel/serializable-closure": "^1.3|^2.0", "ramsey/uuid": "^4.7", - "symfony/process": "^7.0.3" + "symfony/process": "^7.2.0" }, "autoload": { "psr-4": { @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { @@ -44,8 +44,8 @@ "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.322.9).", - "illuminate/redis": "Required to use the Redis queue driver (^11.0).", - "pda/pheanstalk": "Required to use the Beanstalk queue driver (^5.0)." + "illuminate/redis": "Required to use the Redis queue driver (^12.0).", + "pda/pheanstalk": "Required to use the Beanstalk queue driver (^5.0.6|^7.0.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Redis/Connections/PacksPhpRedisValues.php b/src/Illuminate/Redis/Connections/PacksPhpRedisValues.php index 526a764ede89..a64c4f4ce3dc 100644 --- a/src/Illuminate/Redis/Connections/PacksPhpRedisValues.php +++ b/src/Illuminate/Redis/Connections/PacksPhpRedisValues.php @@ -42,7 +42,7 @@ public function pack(array $values): array } if ($this->supportsPacking()) { - return array_map([$this->client, '_pack'], $values); + return array_map($this->client->_pack(...), $values); } if ($this->compressed()) { @@ -95,26 +95,26 @@ public function withoutSerializationOrCompression(callable $callback) $oldSerializer = null; if ($this->serialized()) { - $oldSerializer = $client->getOption($client::OPT_SERIALIZER); - $client->setOption($client::OPT_SERIALIZER, $client::SERIALIZER_NONE); + $oldSerializer = $client->getOption(Redis::OPT_SERIALIZER); + $client->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); } $oldCompressor = null; if ($this->compressed()) { - $oldCompressor = $client->getOption($client::OPT_COMPRESSION); - $client->setOption($client::OPT_COMPRESSION, $client::COMPRESSION_NONE); + $oldCompressor = $client->getOption(Redis::OPT_COMPRESSION); + $client->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); } try { return $callback(); } finally { if ($oldSerializer !== null) { - $client->setOption($client::OPT_SERIALIZER, $oldSerializer); + $client->setOption(Redis::OPT_SERIALIZER, $oldSerializer); } if ($oldCompressor !== null) { - $client->setOption($client::OPT_COMPRESSION, $oldCompressor); + $client->setOption(Redis::OPT_COMPRESSION, $oldCompressor); } } } diff --git a/src/Illuminate/Redis/Connections/PhpRedisConnection.php b/src/Illuminate/Redis/Connections/PhpRedisConnection.php index bf7c9e2949c5..a0448f2e270b 100644 --- a/src/Illuminate/Redis/Connections/PhpRedisConnection.php +++ b/src/Illuminate/Redis/Connections/PhpRedisConnection.php @@ -35,7 +35,6 @@ class PhpRedisConnection extends Connection implements ConnectionContract * @param \Redis $client * @param callable|null $connector * @param array $config - * @return void */ public function __construct($client, ?callable $connector = null, array $config = []) { diff --git a/src/Illuminate/Redis/Connections/PredisConnection.php b/src/Illuminate/Redis/Connections/PredisConnection.php index 2a44c19fb4f0..1f6ec698627f 100644 --- a/src/Illuminate/Redis/Connections/PredisConnection.php +++ b/src/Illuminate/Redis/Connections/PredisConnection.php @@ -23,7 +23,6 @@ class PredisConnection extends Connection implements ConnectionContract * Create a new Predis connection. * * @param \Predis\Client $client - * @return void */ public function __construct($client) { diff --git a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php index eb854279fe71..df5512c9b986 100644 --- a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php +++ b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php @@ -51,7 +51,7 @@ public function connectToCluster(array $config, array $clusterOptions, array $op $options = array_merge($options, $clusterOptions, Arr::pull($config, 'options', [])); return new PhpRedisClusterConnection($this->createRedisClusterInstance( - array_map([$this, 'buildClusterConnectionString'], $config), $options + array_map($this->buildClusterConnectionString(...), $config), $options )); } @@ -85,6 +85,8 @@ protected function createClient(array $config) ); } + $this->establishConnection($client, $config); + if (array_key_exists('max_retries', $config)) { $client->setOption(Redis::OPT_MAX_RETRIES, $config['max_retries']); } @@ -101,8 +103,6 @@ protected function createClient(array $config) $client->setOption(Redis::OPT_BACKOFF_CAP, $config['backoff_cap']); } - $this->establishConnection($client, $config); - if (! empty($config['password'])) { if (isset($config['username']) && $config['username'] !== '' && is_string($config['password'])) { $client->auth([$config['username'], $config['password']]); diff --git a/src/Illuminate/Redis/Events/CommandExecuted.php b/src/Illuminate/Redis/Events/CommandExecuted.php index fa65719af02a..dacd7c6914f4 100644 --- a/src/Illuminate/Redis/Events/CommandExecuted.php +++ b/src/Illuminate/Redis/Events/CommandExecuted.php @@ -46,7 +46,6 @@ class CommandExecuted * @param array $parameters * @param float|null $time * @param \Illuminate\Redis\Connections\Connection $connection - * @return void */ public function __construct($command, $parameters, $time, $connection) { diff --git a/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php b/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php index 62e50b01aad1..02c17870862a 100644 --- a/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php +++ b/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php @@ -44,7 +44,6 @@ class ConcurrencyLimiter * @param string $name * @param int $maxLocks * @param int $releaseAfter - * @return void */ public function __construct($redis, $name, $maxLocks, $releaseAfter) { diff --git a/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php b/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php index 8ff02768297f..aee3df80f281 100644 --- a/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php +++ b/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php @@ -56,7 +56,6 @@ class ConcurrencyLimiterBuilder * * @param \Illuminate\Redis\Connections\Connection $connection * @param string $name - * @return void */ public function __construct($connection, $name) { diff --git a/src/Illuminate/Redis/Limiters/DurationLimiter.php b/src/Illuminate/Redis/Limiters/DurationLimiter.php index b0ecdaf9f4b4..e6c66c30957c 100644 --- a/src/Illuminate/Redis/Limiters/DurationLimiter.php +++ b/src/Illuminate/Redis/Limiters/DurationLimiter.php @@ -56,7 +56,6 @@ class DurationLimiter * @param string $name * @param int $maxLocks * @param int $decay - * @return void */ public function __construct($redis, $name, $maxLocks, $decay) { diff --git a/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php b/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php index 8eedc1177c58..013469079b44 100644 --- a/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php +++ b/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php @@ -56,7 +56,6 @@ class DurationLimiterBuilder * * @param \Illuminate\Redis\Connections\Connection $connection * @param string $name - * @return void */ public function __construct($connection, $name) { diff --git a/src/Illuminate/Redis/RedisManager.php b/src/Illuminate/Redis/RedisManager.php index f1c7d4e917c3..7910c9468bbe 100644 --- a/src/Illuminate/Redis/RedisManager.php +++ b/src/Illuminate/Redis/RedisManager.php @@ -64,7 +64,6 @@ class RedisManager implements Factory * @param \Illuminate\Contracts\Foundation\Application $app * @param string $driver * @param array $config - * @return void */ public function __construct($app, $driver, array $config) { @@ -255,6 +254,9 @@ public function purge($name = null) * * @param string $driver * @param \Closure $callback + * + * @param-closure-this $this $callback + * * @return $this */ public function extend($driver, Closure $callback) diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index 1be0dfe0b9f2..3addbb07ff68 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -15,10 +15,10 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Routing/CallableDispatcher.php b/src/Illuminate/Routing/CallableDispatcher.php index 737e76dfed7a..7e63e54dd9f4 100644 --- a/src/Illuminate/Routing/CallableDispatcher.php +++ b/src/Illuminate/Routing/CallableDispatcher.php @@ -21,7 +21,6 @@ class CallableDispatcher implements CallableDispatcherContract * Create a new callable dispatcher instance. * * @param \Illuminate\Container\Container $container - * @return void */ public function __construct(Container $container) { diff --git a/src/Illuminate/Routing/CompiledRouteCollection.php b/src/Illuminate/Routing/CompiledRouteCollection.php index 0178f783ee42..a20979bf4e41 100644 --- a/src/Illuminate/Routing/CompiledRouteCollection.php +++ b/src/Illuminate/Routing/CompiledRouteCollection.php @@ -54,7 +54,6 @@ class CompiledRouteCollection extends AbstractRouteCollection * * @param array $compiled * @param array $attributes - * @return void */ public function __construct(array $compiled, array $attributes) { diff --git a/src/Illuminate/Routing/Console/ControllerMakeCommand.php b/src/Illuminate/Routing/Console/ControllerMakeCommand.php index b02733b38e52..dcf855c3f806 100755 --- a/src/Illuminate/Routing/Console/ControllerMakeCommand.php +++ b/src/Illuminate/Routing/Console/ControllerMakeCommand.php @@ -53,8 +53,8 @@ protected function getStub() $stub = "/stubs/controller.{$type}.stub"; } elseif ($this->option('parent')) { $stub = $this->option('singleton') - ? '/stubs/controller.nested.singleton.stub' - : '/stubs/controller.nested.stub'; + ? '/stubs/controller.nested.singleton.stub' + : '/stubs/controller.nested.stub'; } elseif ($this->option('model')) { $stub = '/stubs/controller.model.stub'; } elseif ($this->option('invokable')) { @@ -85,8 +85,8 @@ protected function getStub() protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) - ? $customPath - : __DIR__.$stub; + ? $customPath + : __DIR__.$stub; } /** diff --git a/src/Illuminate/Routing/ControllerDispatcher.php b/src/Illuminate/Routing/ControllerDispatcher.php index 9f96eeb3ec89..f82e768d0f7b 100644 --- a/src/Illuminate/Routing/ControllerDispatcher.php +++ b/src/Illuminate/Routing/ControllerDispatcher.php @@ -21,7 +21,6 @@ class ControllerDispatcher implements ControllerDispatcherContract * Create a new controller dispatcher instance. * * @param \Illuminate\Container\Container $container - * @return void */ public function __construct(Container $container) { diff --git a/src/Illuminate/Routing/ControllerMiddlewareOptions.php b/src/Illuminate/Routing/ControllerMiddlewareOptions.php index dfb9f473f71d..9fb468f22018 100644 --- a/src/Illuminate/Routing/ControllerMiddlewareOptions.php +++ b/src/Illuminate/Routing/ControllerMiddlewareOptions.php @@ -15,7 +15,6 @@ class ControllerMiddlewareOptions * Create a new middleware option instance. * * @param array $options - * @return void */ public function __construct(array &$options) { diff --git a/src/Illuminate/Routing/Controllers/Middleware.php b/src/Illuminate/Routing/Controllers/Middleware.php index ebe9389e0b2e..330d9871ee17 100644 --- a/src/Illuminate/Routing/Controllers/Middleware.php +++ b/src/Illuminate/Routing/Controllers/Middleware.php @@ -11,7 +11,6 @@ class Middleware * Create a new controller middleware definition. * * @param \Closure|string|array $middleware - * @return void */ public function __construct(public Closure|string|array $middleware, public ?array $only = null, public ?array $except = null) { diff --git a/src/Illuminate/Routing/Events/PreparingResponse.php b/src/Illuminate/Routing/Events/PreparingResponse.php index 2c060a02ac93..f99367ba9a41 100644 --- a/src/Illuminate/Routing/Events/PreparingResponse.php +++ b/src/Illuminate/Routing/Events/PreparingResponse.php @@ -4,30 +4,15 @@ class PreparingResponse { - /** - * The request instance. - * - * @var \Symfony\Component\HttpFoundation\Request - */ - public $request; - - /** - * The response instance. - * - * @var mixed - */ - public $response; - /** * Create a new event instance. * - * @param \Symfony\Component\HttpFoundation\Request $request - * @param mixed $response - * @return void + * @param \Symfony\Component\HttpFoundation\Request $request The request instance. + * @param mixed $response The response instance. */ - public function __construct($request, $response) - { - $this->request = $request; - $this->response = $response; + public function __construct( + public $request, + public $response, + ) { } } diff --git a/src/Illuminate/Routing/Events/ResponsePrepared.php b/src/Illuminate/Routing/Events/ResponsePrepared.php index 5fb866d289f4..940d2cc4c520 100644 --- a/src/Illuminate/Routing/Events/ResponsePrepared.php +++ b/src/Illuminate/Routing/Events/ResponsePrepared.php @@ -4,30 +4,15 @@ class ResponsePrepared { - /** - * The request instance. - * - * @var \Symfony\Component\HttpFoundation\Request - */ - public $request; - - /** - * The response instance. - * - * @var \Symfony\Component\HttpFoundation\Response - */ - public $response; - /** * Create a new event instance. * - * @param \Symfony\Component\HttpFoundation\Request $request - * @param \Symfony\Component\HttpFoundation\Response $response - * @return void + * @param \Symfony\Component\HttpFoundation\Request $request The request instance. + * @param \Symfony\Component\HttpFoundation\Response $response The response instance. */ - public function __construct($request, $response) - { - $this->request = $request; - $this->response = $response; + public function __construct( + public $request, + public $response, + ) { } } diff --git a/src/Illuminate/Routing/Events/RouteMatched.php b/src/Illuminate/Routing/Events/RouteMatched.php index c8486071737d..503ddb438ae1 100644 --- a/src/Illuminate/Routing/Events/RouteMatched.php +++ b/src/Illuminate/Routing/Events/RouteMatched.php @@ -4,30 +4,15 @@ class RouteMatched { - /** - * The route instance. - * - * @var \Illuminate\Routing\Route - */ - public $route; - - /** - * The request instance. - * - * @var \Illuminate\Http\Request - */ - public $request; - /** * Create a new event instance. * - * @param \Illuminate\Routing\Route $route - * @param \Illuminate\Http\Request $request - * @return void + * @param \Illuminate\Routing\Route $route The route instance. + * @param \Illuminate\Http\Request $request The request instance. */ - public function __construct($route, $request) - { - $this->route = $route; - $this->request = $request; + public function __construct( + public $route, + public $request, + ) { } } diff --git a/src/Illuminate/Routing/Events/Routing.php b/src/Illuminate/Routing/Events/Routing.php index af1bb48d32bd..a97a817cfb31 100644 --- a/src/Illuminate/Routing/Events/Routing.php +++ b/src/Illuminate/Routing/Events/Routing.php @@ -4,21 +4,13 @@ class Routing { - /** - * The request instance. - * - * @var \Illuminate\Http\Request - */ - public $request; - /** * Create a new event instance. * - * @param \Illuminate\Http\Request $request - * @return void + * @param \Illuminate\Http\Request $request The request instance. */ - public function __construct($request) - { - $this->request = $request; + public function __construct( + public $request, + ) { } } diff --git a/src/Illuminate/Routing/Exceptions/BackedEnumCaseNotFoundException.php b/src/Illuminate/Routing/Exceptions/BackedEnumCaseNotFoundException.php index 468583d2a583..d637551f401b 100644 --- a/src/Illuminate/Routing/Exceptions/BackedEnumCaseNotFoundException.php +++ b/src/Illuminate/Routing/Exceptions/BackedEnumCaseNotFoundException.php @@ -11,7 +11,6 @@ class BackedEnumCaseNotFoundException extends RuntimeException * * @param string $backedEnumClass * @param string $case - * @return void */ public function __construct($backedEnumClass, $case) { diff --git a/src/Illuminate/Routing/Exceptions/InvalidSignatureException.php b/src/Illuminate/Routing/Exceptions/InvalidSignatureException.php index 06a35c5e04ef..e7c9c899d60e 100644 --- a/src/Illuminate/Routing/Exceptions/InvalidSignatureException.php +++ b/src/Illuminate/Routing/Exceptions/InvalidSignatureException.php @@ -8,8 +8,6 @@ class InvalidSignatureException extends HttpException { /** * Create a new exception instance. - * - * @return void */ public function __construct() { diff --git a/src/Illuminate/Routing/Exceptions/StreamedResponseException.php b/src/Illuminate/Routing/Exceptions/StreamedResponseException.php index fa6eb7602b4c..6173aed01421 100644 --- a/src/Illuminate/Routing/Exceptions/StreamedResponseException.php +++ b/src/Illuminate/Routing/Exceptions/StreamedResponseException.php @@ -19,7 +19,6 @@ class StreamedResponseException extends RuntimeException * Create a new exception instance. * * @param \Throwable $originalException - * @return void */ public function __construct(Throwable $originalException) { diff --git a/src/Illuminate/Routing/ImplicitRouteBinding.php b/src/Illuminate/Routing/ImplicitRouteBinding.php index 7d92ec9757f9..642bc3151410 100644 --- a/src/Illuminate/Routing/ImplicitRouteBinding.php +++ b/src/Illuminate/Routing/ImplicitRouteBinding.php @@ -43,15 +43,15 @@ public static function resolveForRoute($container, $route) $parent = $route->parentOfParameter($parameterName); $routeBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance)) - ? 'resolveSoftDeletableRouteBinding' - : 'resolveRouteBinding'; + ? 'resolveSoftDeletableRouteBinding' + : 'resolveRouteBinding'; if ($parent instanceof UrlRoutable && ! $route->preventsScopedBindings() && ($route->enforcesScopedBindings() || array_key_exists($parameterName, $route->bindingFields()))) { $childRouteBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance)) - ? 'resolveSoftDeletableChildRouteBinding' - : 'resolveChildRouteBinding'; + ? 'resolveSoftDeletableChildRouteBinding' + : 'resolveChildRouteBinding'; if (! $model = $parent->{$childRouteBindingMethod}( $parameterName, $parameterValue, $route->bindingFieldFor($parameterName) diff --git a/src/Illuminate/Routing/Middleware/SubstituteBindings.php b/src/Illuminate/Routing/Middleware/SubstituteBindings.php index eb1438d0fdd7..68fe621e4dd7 100644 --- a/src/Illuminate/Routing/Middleware/SubstituteBindings.php +++ b/src/Illuminate/Routing/Middleware/SubstituteBindings.php @@ -19,7 +19,6 @@ class SubstituteBindings * Create a new bindings substitutor. * * @param \Illuminate\Contracts\Routing\Registrar $router - * @return void */ public function __construct(Registrar $router) { diff --git a/src/Illuminate/Routing/Middleware/ThrottleRequests.php b/src/Illuminate/Routing/Middleware/ThrottleRequests.php index d9859558c30c..f6a21dd4098b 100644 --- a/src/Illuminate/Routing/Middleware/ThrottleRequests.php +++ b/src/Illuminate/Routing/Middleware/ThrottleRequests.php @@ -35,7 +35,6 @@ class ThrottleRequests * Create a new request throttler. * * @param \Illuminate\Cache\RateLimiter $limiter - * @return void */ public function __construct(RateLimiter $limiter) { @@ -241,8 +240,8 @@ protected function buildException($request, $key, $maxAttempts, $responseCallbac ); return is_callable($responseCallback) - ? new HttpResponseException($responseCallback($request, $headers)) - : new ThrottleRequestsException('Too Many Attempts.', null, $headers); + ? new HttpResponseException($responseCallback($request, $headers)) + : new ThrottleRequestsException('Too Many Attempts.', null, $headers); } /** diff --git a/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php b/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php index 20afd95dd9b8..547f082c0f91 100644 --- a/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php +++ b/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php @@ -35,7 +35,6 @@ class ThrottleRequestsWithRedis extends ThrottleRequests * * @param \Illuminate\Cache\RateLimiter $limiter * @param \Illuminate\Contracts\Redis\Factory $redis - * @return void */ public function __construct(RateLimiter $limiter, Redis $redis) { diff --git a/src/Illuminate/Routing/PendingResourceRegistration.php b/src/Illuminate/Routing/PendingResourceRegistration.php index 587edb3a5e5f..81e25b4b16aa 100644 --- a/src/Illuminate/Routing/PendingResourceRegistration.php +++ b/src/Illuminate/Routing/PendingResourceRegistration.php @@ -51,7 +51,6 @@ class PendingResourceRegistration * @param string $name * @param string $controller * @param array $options - * @return void */ public function __construct(ResourceRegistrar $registrar, $name, $controller, array $options) { diff --git a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php index 84ad1a9aea0d..6b01de6f0a2e 100644 --- a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php +++ b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php @@ -51,7 +51,6 @@ class PendingSingletonResourceRegistration * @param string $name * @param string $controller * @param array $options - * @return void */ public function __construct(ResourceRegistrar $registrar, $name, $controller, array $options) { diff --git a/src/Illuminate/Routing/Redirector.php b/src/Illuminate/Routing/Redirector.php index 24de72d1fe7d..1b9a62a57aa9 100755 --- a/src/Illuminate/Routing/Redirector.php +++ b/src/Illuminate/Routing/Redirector.php @@ -28,7 +28,6 @@ class Redirector * Create a new Redirector instance. * * @param \Illuminate\Routing\UrlGenerator $generator - * @return void */ public function __construct(UrlGenerator $generator) { @@ -74,8 +73,8 @@ public function guest($path, $status = 302, $headers = [], $secure = null) $request = $this->generator->getRequest(); $intended = $request->isMethod('GET') && $request->route() && ! $request->expectsJson() - ? $this->generator->full() - : $this->generator->previous(); + ? $this->generator->full() + : $this->generator->previous(); if ($intended) { $this->setIntendedUrl($intended); diff --git a/src/Illuminate/Routing/ResourceRegistrar.php b/src/Illuminate/Routing/ResourceRegistrar.php index f0668499389e..82ccc1620b83 100644 --- a/src/Illuminate/Routing/ResourceRegistrar.php +++ b/src/Illuminate/Routing/ResourceRegistrar.php @@ -62,7 +62,6 @@ class ResourceRegistrar * Create a new resource registrar instance. * * @param \Illuminate\Routing\Router $router - * @return void */ public function __construct(Router $router) { @@ -547,8 +546,8 @@ protected function addSingletonDestroy($name, $controller, $options) protected function getShallowName($name, $options) { return isset($options['shallow']) && $options['shallow'] - ? last(explode('.', $name)) - : $name; + ? last(explode('.', $name)) + : $name; } /** diff --git a/src/Illuminate/Routing/ResponseFactory.php b/src/Illuminate/Routing/ResponseFactory.php index cd3f8ef0c922..4a767e39482f 100644 --- a/src/Illuminate/Routing/ResponseFactory.php +++ b/src/Illuminate/Routing/ResponseFactory.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; +use Illuminate\Http\StreamedEvent; use Illuminate\Routing\Exceptions\StreamedResponseException; use Illuminate\Support\Js; use Illuminate\Support\Str; @@ -39,7 +40,6 @@ class ResponseFactory implements FactoryContract * * @param \Illuminate\Contracts\View\Factory $view * @param \Illuminate\Routing\Redirector $redirector - * @return void */ public function __construct(ViewFactory $view, Redirector $redirector) { @@ -124,10 +124,10 @@ public function jsonp($callback, $data = [], $status = 200, array $headers = [], * * @param \Closure $callback * @param array $headers - * @param string $endStreamWith + * @param \Illuminate\Http\StreamedEvent|string|null $endStreamWith * @return \Symfony\Component\HttpFoundation\StreamedResponse */ - public function eventStream(Closure $callback, array $headers = [], string $endStreamWith = '') + public function eventStream(Closure $callback, array $headers = [], StreamedEvent|string|null $endStreamWith = '') { return $this->stream(function () use ($callback, $endStreamWith) { foreach ($callback() as $message) { @@ -135,24 +135,46 @@ public function eventStream(Closure $callback, array $headers = [], string $endS break; } + $event = 'update'; + + if ($message instanceof StreamedEvent) { + $event = $message->event; + $message = $message->data; + } + if (! is_string($message) && ! is_numeric($message)) { $message = Js::encode($message); } - echo "event: update\n"; + echo "event: $event\n"; echo 'data: '.$message; echo "\n\n"; - ob_flush(); + if (ob_get_level() > 0) { + ob_flush(); + } + flush(); } - echo "event: update\n"; - echo 'data: '.$endStreamWith; - echo "\n\n"; + if (filled($endStreamWith)) { + $endEvent = 'update'; + + if ($endStreamWith instanceof StreamedEvent) { + $endEvent = $endStreamWith->event; + $endStreamWith = $endStreamWith->data; + } + + echo "event: $endEvent\n"; + echo 'data: '.$endStreamWith; + echo "\n\n"; + + if (ob_get_level() > 0) { + ob_flush(); + } - ob_flush(); - flush(); + flush(); + } }, 200, array_merge($headers, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', @@ -174,7 +196,7 @@ public function stream($callback, $status = 200, array $headers = []) } /** - * Create a new streamed response instance. + * Create a new streamed JSON response instance. * * @param array $data * @param int $status diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 8253f0c26428..355d18b0e058 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -170,7 +170,6 @@ class Route * @param array|string $methods * @param string $uri * @param \Closure|array $action - * @return void */ public function __construct($methods, $uri, $action) { @@ -272,6 +271,8 @@ protected function runController() * Get the controller instance for the route. * * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getController() { @@ -792,7 +793,8 @@ public function domain($domain = null) public function getDomain() { return isset($this->action['domain']) - ? str_replace(['http://', 'https://'], '', $this->action['domain']) : null; + ? str_replace(['http://', 'https://'], '', $this->action['domain']) + : null; } /** @@ -1094,8 +1096,8 @@ public function can($ability, $models = []) $ability = enum_value($ability); return empty($models) - ? $this->middleware(['can:'.$ability]) - : $this->middleware(['can:'.$ability.','.implode(',', Arr::wrap($models))]); + ? $this->middleware(['can:'.$ability]) + : $this->middleware(['can:'.$ability.','.implode(',', Arr::wrap($models))]); } /** @@ -1138,15 +1140,22 @@ public function controllerMiddleware() */ protected function staticallyProvidedControllerMiddleware(string $class, string $method) { - return (new Collection($class::middleware()))->map(function ($middleware) { - return $middleware instanceof Middleware - ? $middleware - : new Middleware($middleware); - })->reject(function ($middleware) use ($method) { - return static::methodExcludedByOptions( - $method, ['only' => $middleware->only, 'except' => $middleware->except] - ); - })->map->middleware->flatten()->values()->all(); + return (new Collection($class::middleware())) + ->map(function ($middleware) { + return $middleware instanceof Middleware + ? $middleware + : new Middleware($middleware); + }) + ->reject(function ($middleware) use ($method) { + return static::methodExcludedByOptions( + $method, ['only' => $middleware->only, 'except' => $middleware->except], + ); + }) + ->map + ->middleware + ->flatten() + ->values() + ->all(); } /** @@ -1267,6 +1276,8 @@ public function waitsFor() * Get the dispatcher for the route's controller. * * @return \Illuminate\Routing\Contracts\ControllerDispatcher + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function controllerDispatcher() { @@ -1314,9 +1325,9 @@ public function toSymfonyRoute() /** * Get the optional parameter names for the route. * - * @return array + * @return array */ - protected function getOptionalParameterNames() + public function getOptionalParameterNames() { preg_match_all('/\{(\w+?)\?\}/', $this->uri(), $matches); diff --git a/src/Illuminate/Routing/RouteBinding.php b/src/Illuminate/Routing/RouteBinding.php index ef37e7063564..cec3fa998a07 100644 --- a/src/Illuminate/Routing/RouteBinding.php +++ b/src/Illuminate/Routing/RouteBinding.php @@ -69,8 +69,8 @@ public static function forModel($container, $class, $callback = null) $instance = $container->make($class); $routeBindingMethod = $route?->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance)) - ? 'resolveSoftDeletableRouteBinding' - : 'resolveRouteBinding'; + ? 'resolveSoftDeletableRouteBinding' + : 'resolveRouteBinding'; if ($model = $instance->{$routeBindingMethod}($value)) { return $model; diff --git a/src/Illuminate/Routing/RouteFileRegistrar.php b/src/Illuminate/Routing/RouteFileRegistrar.php index 7670b10eb376..f94e31d8d57e 100644 --- a/src/Illuminate/Routing/RouteFileRegistrar.php +++ b/src/Illuminate/Routing/RouteFileRegistrar.php @@ -15,7 +15,6 @@ class RouteFileRegistrar * Create a new route file registrar instance. * * @param \Illuminate\Routing\Router $router - * @return void */ public function __construct(Router $router) { diff --git a/src/Illuminate/Routing/RouteGroup.php b/src/Illuminate/Routing/RouteGroup.php index 2c60273068fd..cca24b29234d 100644 --- a/src/Illuminate/Routing/RouteGroup.php +++ b/src/Illuminate/Routing/RouteGroup.php @@ -46,8 +46,8 @@ protected static function formatNamespace($new, $old) { if (isset($new['namespace'])) { return isset($old['namespace']) && ! str_starts_with($new['namespace'], '\\') - ? trim($old['namespace'], '\\').'\\'.trim($new['namespace'], '\\') - : trim($new['namespace'], '\\'); + ? trim($old['namespace'], '\\').'\\'.trim($new['namespace'], '\\') + : trim($new['namespace'], '\\'); } return $old['namespace'] ?? null; diff --git a/src/Illuminate/Routing/RouteParameterBinder.php b/src/Illuminate/Routing/RouteParameterBinder.php index 8c3968e0f828..5c53e5c786e3 100644 --- a/src/Illuminate/Routing/RouteParameterBinder.php +++ b/src/Illuminate/Routing/RouteParameterBinder.php @@ -17,7 +17,6 @@ class RouteParameterBinder * Create a new Route parameter binder instance. * * @param \Illuminate\Routing\Route $route - * @return void */ public function __construct($route) { diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 11973904b397..b3e543b777b1 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -95,7 +95,6 @@ class RouteRegistrar * Create a new route registrar instance. * * @param \Illuminate\Routing\Router $router - * @return void */ public function __construct(Router $router) { diff --git a/src/Illuminate/Routing/RouteSignatureParameters.php b/src/Illuminate/Routing/RouteSignatureParameters.php index 7a4b6dadb996..872709758314 100644 --- a/src/Illuminate/Routing/RouteSignatureParameters.php +++ b/src/Illuminate/Routing/RouteSignatureParameters.php @@ -19,12 +19,12 @@ class RouteSignatureParameters public static function fromAction(array $action, $conditions = []) { $callback = RouteAction::containsSerializedClosure($action) - ? unserialize($action['uses'])->getClosure() - : $action['uses']; + ? unserialize($action['uses'])->getClosure() + : $action['uses']; $parameters = is_string($callback) - ? static::fromClassMethodString($callback) - : (new ReflectionFunction($callback))->getParameters(); + ? static::fromClassMethodString($callback) + : (new ReflectionFunction($callback))->getParameters(); return match (true) { ! empty($conditions['subClass']) => array_filter($parameters, fn ($p) => Reflector::isParameterSubclassOf($p, $conditions['subClass'])), diff --git a/src/Illuminate/Routing/RouteUri.php b/src/Illuminate/Routing/RouteUri.php index 323717741e17..77003fac9a92 100644 --- a/src/Illuminate/Routing/RouteUri.php +++ b/src/Illuminate/Routing/RouteUri.php @@ -23,7 +23,6 @@ class RouteUri * * @param string $uri * @param array $bindingFields - * @return void */ public function __construct(string $uri, array $bindingFields = []) { diff --git a/src/Illuminate/Routing/RouteUrlGenerator.php b/src/Illuminate/Routing/RouteUrlGenerator.php index a0e5cf6413c5..cd23162c015f 100644 --- a/src/Illuminate/Routing/RouteUrlGenerator.php +++ b/src/Illuminate/Routing/RouteUrlGenerator.php @@ -2,8 +2,11 @@ namespace Illuminate\Routing; +use BackedEnum; +use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Routing\Exceptions\UrlGenerationException; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; class RouteUrlGenerator { @@ -55,7 +58,6 @@ class RouteUrlGenerator * * @param \Illuminate\Routing\UrlGenerator $url * @param \Illuminate\Http\Request $request - * @return void */ public function __construct($url, $request) { @@ -75,6 +77,8 @@ public function __construct($url, $request) */ public function to($route, $parameters = [], $absolute = false) { + $parameters = $this->formatParameters($route, $parameters); + $domain = $this->getRouteDomain($route, $parameters); // First we will construct the entire URI including the root and query string. Once it @@ -164,7 +168,138 @@ protected function addPortToDomain($domain) $port = (int) $this->request->getPort(); return ($secure && $port === 443) || (! $secure && $port === 80) - ? $domain : $domain.':'.$port; + ? $domain + : $domain.':'.$port; + } + + /** + * Format the array of route parameters. + * + * @param \Illuminate\Routing\Route $route + * @param mixed $parameters + * @return array + */ + protected function formatParameters(Route $route, $parameters) + { + $parameters = Arr::wrap($parameters); + + $namedParameters = []; + $namedQueryParameters = []; + $requiredRouteParametersWithoutDefaultsOrNamedParameters = []; + + $routeParameters = $route->parameterNames(); + $optionalParameters = $route->getOptionalParameterNames(); + + foreach ($routeParameters as $name) { + if (isset($parameters[$name])) { + // Named parameters don't need any special handling... + $namedParameters[$name] = $parameters[$name]; + unset($parameters[$name]); + + continue; + } elseif (! isset($this->defaultParameters[$name]) && ! isset($optionalParameters[$name])) { + // No named parameter or default value for a required parameter, try to match to positional parameter below... + array_push($requiredRouteParametersWithoutDefaultsOrNamedParameters, $name); + } + + $namedParameters[$name] = ''; + } + + // Named parameters that don't have route parameters will be used for query string... + foreach ($parameters as $key => $value) { + if (is_string($key)) { + $namedQueryParameters[$key] = $value; + + unset($parameters[$key]); + } + } + + // Match positional parameters to the route parameters that didn't have a value in order... + if (count($parameters) == count($requiredRouteParametersWithoutDefaultsOrNamedParameters)) { + foreach (array_reverse($requiredRouteParametersWithoutDefaultsOrNamedParameters) as $name) { + if (count($parameters) === 0) { + break; + } + + $namedParameters[$name] = array_pop($parameters); + } + } + + $offset = 0; + $emptyParameters = array_filter($namedParameters, static fn ($val) => $val === ''); + + if (count($requiredRouteParametersWithoutDefaultsOrNamedParameters) !== 0 && + count($parameters) !== count($emptyParameters)) { + // Find the index of the first required parameter... + $offset = array_search($requiredRouteParametersWithoutDefaultsOrNamedParameters[0], array_keys($namedParameters)); + + // If more empty parameters remain, adjust the offset... + $remaining = count($emptyParameters) - $offset - count($parameters); + + if ($remaining < 0) { + // Effectively subtract the remaining count since it's negative... + $offset += $remaining; + } + + // Correct offset if it goes below zero... + if ($offset < 0) { + $offset = 0; + } + } elseif (count($requiredRouteParametersWithoutDefaultsOrNamedParameters) === 0 && count($parameters) !== 0) { + // Handle the case where all passed parameters are for parameters that have default values... + $remainingCount = count($parameters); + + // Loop over empty parameters backwards and stop when we run out of passed parameters... + for ($i = count($namedParameters) - 1; $i >= 0; $i--) { + if ($namedParameters[array_keys($namedParameters)[$i]] === '') { + $offset = $i; + $remainingCount--; + + if ($remainingCount === 0) { + // If there are no more passed parameters, we stop here... + break; + } + } + } + } + + // Starting from the offset, match any passed parameters from left to right... + for ($i = $offset; $i < count($namedParameters); $i++) { + $key = array_keys($namedParameters)[$i]; + + if ($namedParameters[$key] !== '') { + continue; + } elseif (! empty($parameters)) { + $namedParameters[$key] = array_shift($parameters); + } + } + + // Fill leftmost parameters with defaults if the loop above was offset... + foreach ($namedParameters as $key => $value) { + $bindingField = $route->bindingFieldFor($key); + $defaultParameterKey = $bindingField ? "$key:$bindingField" : $key; + + if ($value === '' && isset($this->defaultParameters[$defaultParameterKey])) { + $namedParameters[$key] = $this->defaultParameters[$defaultParameterKey]; + } + } + + // Any remaining values in $parameters are unnamed query string parameters... + $parameters = array_merge($namedParameters, $namedQueryParameters, $parameters); + + $parameters = Collection::wrap($parameters)->map(function ($value, $key) use ($route) { + return $value instanceof UrlRoutable && $route->bindingFieldFor($key) + ? $value->{$route->bindingFieldFor($key)} + : $value; + })->all(); + + array_walk_recursive($parameters, function (&$item) { + if ($item instanceof BackedEnum) { + $item = $item->value; + } + }); + + return $this->url->formatParameters($parameters); } /** @@ -200,8 +335,8 @@ protected function replaceRouteParameters($path, array &$parameters) $parameters = array_merge($parameters); return (! isset($parameters[0]) && ! str_ends_with($match[0], '?}')) - ? $match[0] - : Arr::pull($parameters, 0); + ? $match[0] + : Arr::pull($parameters, 0); }, $path); return trim(preg_replace('/\{.*?\?\}/', '', $path), '/'); diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index cea49ce721f9..eb9c986bfe9e 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -140,7 +140,6 @@ class Router implements BindingRegistrar, RegistrarContract * * @param \Illuminate\Contracts\Events\Dispatcher $events * @param \Illuminate\Container\Container|null $container - * @return void */ public function __construct(Dispatcher $events, ?Container $container = null) { @@ -306,7 +305,7 @@ public function view($uri, $view, $data = [], $status = 200, array $headers = [] */ public function match($methods, $uri, $action = null) { - return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + return $this->addRoute(array_map(strtoupper(...), (array) $methods), $uri, $action); } /** @@ -630,7 +629,8 @@ protected function prependGroupNamespace($class) $group = end($this->groupStack); return isset($group['namespace']) && ! str_starts_with($class, '\\') && ! str_starts_with($class, $group['namespace']) - ? $group['namespace'].'\\'.$class : $class; + ? $group['namespace'].'\\'.$class + : $class; } /** @@ -829,35 +829,39 @@ public function gatherRouteMiddleware(Route $route) */ public function resolveMiddleware(array $middleware, array $excluded = []) { - $excluded = (new Collection($excluded))->map(function ($name) { - return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups); - })->flatten()->values()->all(); + $excluded = $excluded === [] + ? $excluded + : (new Collection($excluded)) + ->map(fn ($name) => (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups)) + ->flatten() + ->values() + ->all(); $middleware = (new Collection($middleware))->map(function ($name) { return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups); - })->flatten()->reject(function ($name) use ($excluded) { - if (empty($excluded)) { - return false; - } - - if ($name instanceof Closure) { - return false; - } - - if (in_array($name, $excluded, true)) { - return true; - } - - if (! class_exists($name)) { - return false; - } - - $reflection = new ReflectionClass($name); - - return (new Collection($excluded))->contains( - fn ($exclude) => class_exists($exclude) && $reflection->isSubclassOf($exclude) - ); - })->values(); + })->flatten() + ->when( + ! empty($excluded), + fn ($collection) => $collection->reject(function ($name) use ($excluded) { + if ($name instanceof Closure) { + return false; + } + + if (in_array($name, $excluded, true)) { + return true; + } + + if (! class_exists($name)) { + return false; + } + + $reflection = new ReflectionClass($name); + + return (new Collection($excluded))->contains( + fn ($exclude) => class_exists($exclude) && $reflection->isSubclassOf($exclude) + ); + }) + )->values(); return $this->sortMiddleware($middleware); } diff --git a/src/Illuminate/Routing/SortedMiddleware.php b/src/Illuminate/Routing/SortedMiddleware.php index 3c2c7912d219..2fa2e3c5f06f 100644 --- a/src/Illuminate/Routing/SortedMiddleware.php +++ b/src/Illuminate/Routing/SortedMiddleware.php @@ -11,7 +11,6 @@ class SortedMiddleware extends Collection * * @param array $priorityMap * @param \Illuminate\Support\Collection|array $middlewares - * @return void */ public function __construct(array $priorityMap, $middlewares) { diff --git a/src/Illuminate/Routing/UrlGenerator.php b/src/Illuminate/Routing/UrlGenerator.php index 4c875d9c281a..4808c1c0a89e 100755 --- a/src/Illuminate/Routing/UrlGenerator.php +++ b/src/Illuminate/Routing/UrlGenerator.php @@ -124,7 +124,6 @@ class UrlGenerator implements UrlGeneratorContract * @param \Illuminate\Routing\RouteCollectionInterface $routes * @param \Illuminate\Http\Request $request * @param string|null $assetRoot - * @return void */ public function __construct(RouteCollectionInterface $routes, Request $request, $assetRoot = null) { @@ -419,10 +418,10 @@ public function temporarySignedRoute($name, $expiration, $parameters = [], $abso * * @param \Illuminate\Http\Request $request * @param bool $absolute - * @param array $ignoreQuery + * @param \Closure|array $ignoreQuery * @return bool */ - public function hasValidSignature(Request $request, $absolute = true, array $ignoreQuery = []) + public function hasValidSignature(Request $request, $absolute = true, Closure|array $ignoreQuery = []) { return $this->hasCorrectSignature($request, $absolute, $ignoreQuery) && $this->signatureHasNotExpired($request); @@ -432,10 +431,10 @@ public function hasValidSignature(Request $request, $absolute = true, array $ign * Determine if the given request has a valid signature for a relative URL. * * @param \Illuminate\Http\Request $request - * @param array $ignoreQuery + * @param \Closure|array $ignoreQuery * @return bool */ - public function hasValidRelativeSignature(Request $request, array $ignoreQuery = []) + public function hasValidRelativeSignature(Request $request, Closure|array $ignoreQuery = []) { return $this->hasValidSignature($request, false, $ignoreQuery); } @@ -445,17 +444,27 @@ public function hasValidRelativeSignature(Request $request, array $ignoreQuery = * * @param \Illuminate\Http\Request $request * @param bool $absolute - * @param array $ignoreQuery + * @param \Closure|array $ignoreQuery * @return bool */ - public function hasCorrectSignature(Request $request, $absolute = true, array $ignoreQuery = []) + public function hasCorrectSignature(Request $request, $absolute = true, Closure|array $ignoreQuery = []) { - $ignoreQuery[] = 'signature'; - $url = $absolute ? $request->url() : '/'.$request->path(); $queryString = (new Collection(explode('&', (string) $request->server->get('QUERY_STRING')))) - ->reject(fn ($parameter) => in_array(Str::before($parameter, '='), $ignoreQuery)) + ->reject(function ($parameter) use ($ignoreQuery) { + $parameter = Str::before($parameter, '='); + + if ($parameter === 'signature') { + return true; + } + + if ($ignoreQuery instanceof Closure) { + return $ignoreQuery($parameter); + } + + return in_array($parameter, $ignoreQuery); + }) ->join('&'); $original = rtrim($url.'?'.$queryString, '?'); @@ -529,20 +538,8 @@ public function route($name, $parameters = [], $absolute = true) */ public function toRoute($route, $parameters, $absolute) { - $parameters = Collection::wrap($parameters)->map(function ($value, $key) use ($route) { - return $value instanceof UrlRoutable && $route->bindingFieldFor($key) - ? $value->{$route->bindingFieldFor($key)} - : $value; - })->all(); - - array_walk_recursive($parameters, function (&$item) { - if ($item instanceof BackedEnum) { - $item = $item->value; - } - }); - return $this->routeUrl()->to( - $route, $this->formatParameters($parameters), $absolute + $route, $parameters, $absolute ); } diff --git a/src/Illuminate/Routing/ViewController.php b/src/Illuminate/Routing/ViewController.php index f5b5525d838b..e99d7ff871b3 100644 --- a/src/Illuminate/Routing/ViewController.php +++ b/src/Illuminate/Routing/ViewController.php @@ -17,7 +17,6 @@ class ViewController extends Controller * Create a new controller instance. * * @param \Illuminate\Contracts\Routing\ResponseFactory $response - * @return void */ public function __construct(ResponseFactory $response) { diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index b27e25d83a71..1f7e2281463f 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -17,17 +17,17 @@ "php": "^8.2", "ext-filter": "*", "ext-hash": "*", - "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.3", - "symfony/http-kernel": "^7.0.3", - "symfony/routing": "^7.0.3" + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/http": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/pipeline": "^12.0", + "illuminate/session": "^12.0", + "illuminate/support": "^12.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/routing": "^7.2.0" }, "autoload": { "psr-4": { @@ -36,13 +36,13 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/console": "Required to use the make commands (^11.0).", + "illuminate/console": "Required to use the make commands (^12.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." }, "config": { "sort-packages": true, diff --git a/src/Illuminate/Session/ArraySessionHandler.php b/src/Illuminate/Session/ArraySessionHandler.php index 44d50dafd93e..7820ab1aeee4 100644 --- a/src/Illuminate/Session/ArraySessionHandler.php +++ b/src/Illuminate/Session/ArraySessionHandler.php @@ -27,7 +27,6 @@ class ArraySessionHandler implements SessionHandlerInterface * Create a new array driven handler instance. * * @param int $minutes - * @return void */ public function __construct($minutes) { diff --git a/src/Illuminate/Session/CacheBasedSessionHandler.php b/src/Illuminate/Session/CacheBasedSessionHandler.php index 0cccdf2d922c..bac3e18619f1 100755 --- a/src/Illuminate/Session/CacheBasedSessionHandler.php +++ b/src/Illuminate/Session/CacheBasedSessionHandler.php @@ -26,7 +26,6 @@ class CacheBasedSessionHandler implements SessionHandlerInterface * * @param \Illuminate\Contracts\Cache\Repository $cache * @param int $minutes - * @return void */ public function __construct(CacheContract $cache, $minutes) { diff --git a/src/Illuminate/Session/CookieSessionHandler.php b/src/Illuminate/Session/CookieSessionHandler.php index 396022b37f4f..09376afee045 100755 --- a/src/Illuminate/Session/CookieSessionHandler.php +++ b/src/Illuminate/Session/CookieSessionHandler.php @@ -45,7 +45,6 @@ class CookieSessionHandler implements SessionHandlerInterface * @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie * @param int $minutes * @param bool $expireOnClose - * @return void */ public function __construct(CookieJar $cookie, $minutes, $expireOnClose = false) { diff --git a/src/Illuminate/Session/DatabaseSessionHandler.php b/src/Illuminate/Session/DatabaseSessionHandler.php index 132f1e347246..5023808bafa2 100644 --- a/src/Illuminate/Session/DatabaseSessionHandler.php +++ b/src/Illuminate/Session/DatabaseSessionHandler.php @@ -57,7 +57,6 @@ class DatabaseSessionHandler implements ExistenceAwareInterface, SessionHandlerI * @param string $table * @param int $minutes * @param \Illuminate\Contracts\Container\Container|null $container - * @return void */ public function __construct(ConnectionInterface $connection, $table, $minutes, ?Container $container = null) { diff --git a/src/Illuminate/Session/EncryptedStore.php b/src/Illuminate/Session/EncryptedStore.php index 0f2bbef124ea..a3a3e9abd897 100644 --- a/src/Illuminate/Session/EncryptedStore.php +++ b/src/Illuminate/Session/EncryptedStore.php @@ -23,7 +23,6 @@ class EncryptedStore extends Store * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @param string|null $id * @param string $serialization - * @return void */ public function __construct($name, SessionHandlerInterface $handler, EncrypterContract $encrypter, $id = null, $serialization = 'php') { diff --git a/src/Illuminate/Session/FileSessionHandler.php b/src/Illuminate/Session/FileSessionHandler.php index 82fe2245384b..64d930e5f378 100644 --- a/src/Illuminate/Session/FileSessionHandler.php +++ b/src/Illuminate/Session/FileSessionHandler.php @@ -36,7 +36,6 @@ class FileSessionHandler implements SessionHandlerInterface * @param \Illuminate\Filesystem\Filesystem $files * @param string $path * @param int $minutes - * @return void */ public function __construct(Filesystem $files, $path, $minutes) { diff --git a/src/Illuminate/Session/Middleware/AuthenticateSession.php b/src/Illuminate/Session/Middleware/AuthenticateSession.php index efd34c35e662..89c36e8a1d41 100644 --- a/src/Illuminate/Session/Middleware/AuthenticateSession.php +++ b/src/Illuminate/Session/Middleware/AuthenticateSession.php @@ -28,7 +28,6 @@ class AuthenticateSession implements AuthenticatesSessions * Create a new middleware instance. * * @param \Illuminate\Contracts\Auth\Factory $auth - * @return void */ public function __construct(AuthFactory $auth) { diff --git a/src/Illuminate/Session/Middleware/StartSession.php b/src/Illuminate/Session/Middleware/StartSession.php index c1ffbb84c3df..ec5b00b63ba9 100644 --- a/src/Illuminate/Session/Middleware/StartSession.php +++ b/src/Illuminate/Session/Middleware/StartSession.php @@ -33,7 +33,6 @@ class StartSession * * @param \Illuminate\Session\SessionManager $manager * @param callable|null $cacheFactoryResolver - * @return void */ public function __construct(SessionManager $manager, ?callable $cacheFactoryResolver = null) { @@ -79,8 +78,8 @@ protected function handleRequestWhileBlocking(Request $request, $session, Closur } $lockFor = $request->route() && $request->route()->locksFor() - ? $request->route()->locksFor() - : $this->manager->defaultRouteBlockLockSeconds(); + ? $request->route()->locksFor() + : $this->manager->defaultRouteBlockLockSeconds(); $lock = $this->cache($this->manager->blockDriver()) ->lock('session:'.$session->getId(), $lockFor) @@ -89,8 +88,8 @@ protected function handleRequestWhileBlocking(Request $request, $session, Closur try { $lock->block( ! is_null($request->route()->waitsFor()) - ? $request->route()->waitsFor() - : $this->manager->defaultRouteBlockWaitSeconds() + ? $request->route()->waitsFor() + : $this->manager->defaultRouteBlockWaitSeconds() ); return $this->handleStatefulRequest($request, $session, $next); @@ -266,7 +265,7 @@ protected function getCookieExpirationDate() $config = $this->manager->getSessionConfig(); return $config['expire_on_close'] ? 0 : Date::instance( - Carbon::now()->addRealMinutes($config['lifetime']) + Carbon::now()->addMinutes((int) $config['lifetime']) ); } diff --git a/src/Illuminate/Session/SessionManager.php b/src/Illuminate/Session/SessionManager.php index 6094627e6ef5..578fc2107ab2 100755 --- a/src/Illuminate/Session/SessionManager.php +++ b/src/Illuminate/Session/SessionManager.php @@ -190,13 +190,13 @@ protected function createCacheHandler($driver) protected function buildSession($handler) { return $this->config->get('session.encrypt') - ? $this->buildEncryptedSession($handler) - : new Store( - $this->config->get('session.cookie'), - $handler, - $id = null, - $this->config->get('session.serialization', 'php') - ); + ? $this->buildEncryptedSession($handler) + : new Store( + $this->config->get('session.cookie'), + $handler, + $id = null, + $this->config->get('session.serialization', 'php') + ); } /** diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index 5c5d447a3413..342544668ecb 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -69,7 +69,6 @@ class Store implements Session * @param \SessionHandlerInterface $handler * @param string|null $id * @param string $serialization - * @return void */ public function __construct($name, SessionHandlerInterface $handler, $id = null, $serialization = 'php') { diff --git a/src/Illuminate/Session/SymfonySessionDecorator.php b/src/Illuminate/Session/SymfonySessionDecorator.php index 55dde71a7cbe..365aca670333 100644 --- a/src/Illuminate/Session/SymfonySessionDecorator.php +++ b/src/Illuminate/Session/SymfonySessionDecorator.php @@ -21,7 +21,6 @@ class SymfonySessionDecorator implements SessionInterface * Create a new session decorator. * * @param \Illuminate\Contracts\Session\Session $store - * @return void */ public function __construct(Session $store) { diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index fe0a93c1eac3..c19e6cbe5081 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -17,12 +17,12 @@ "php": "^8.2", "ext-ctype": "*", "ext-session": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/filesystem": "^11.0", - "illuminate/support": "^11.0", - "symfony/finder": "^7.0.3", - "symfony/http-foundation": "^7.0.3" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/support": "^12.0", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0" }, "autoload": { "psr-4": { @@ -31,11 +31,11 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/console": "Required to use the session:table command (^11.0)." + "illuminate/console": "Required to use the session:table command (^12.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Support/Composer.php b/src/Illuminate/Support/Composer.php index 860c886faf06..78186c8e9afb 100644 --- a/src/Illuminate/Support/Composer.php +++ b/src/Illuminate/Support/Composer.php @@ -29,7 +29,6 @@ class Composer * * @param \Illuminate\Filesystem\Filesystem $files * @param string|null $workingPath - * @return void */ public function __construct(Filesystem $files, $workingPath = null) { @@ -45,7 +44,7 @@ public function __construct(Filesystem $files, $workingPath = null) * * @throw \RuntimeException */ - protected function hasPackage($package) + public function hasPackage($package) { $composer = json_decode(file_get_contents($this->findComposerFile()), true); diff --git a/src/Illuminate/Support/ConfigurationUrlParser.php b/src/Illuminate/Support/ConfigurationUrlParser.php index a1b933709236..b841b65e41c6 100644 --- a/src/Illuminate/Support/ConfigurationUrlParser.php +++ b/src/Illuminate/Support/ConfigurationUrlParser.php @@ -42,7 +42,7 @@ public function parseConfiguration($config) $rawComponents = $this->parseUrl($url); $decodedComponents = $this->parseStringsToNativeTypes( - array_map('rawurldecode', $rawComponents) + array_map(rawurldecode(...), $rawComponents) ); return array_merge( @@ -151,7 +151,7 @@ protected function parseUrl($url) protected function parseStringsToNativeTypes($value) { if (is_array($value)) { - return array_map([$this, 'parseStringsToNativeTypes'], $value); + return array_map($this->parseStringsToNativeTypes(...), $value); } if (! is_string($value)) { diff --git a/src/Illuminate/Support/DateFactory.php b/src/Illuminate/Support/DateFactory.php index 3d0cd04dfc5b..5a2feb599f9f 100644 --- a/src/Illuminate/Support/DateFactory.php +++ b/src/Illuminate/Support/DateFactory.php @@ -9,77 +9,96 @@ * @see https://carbon.nesbot.com/docs/ * @see https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Factory.php * - * @method \Illuminate\Support\Carbon create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) - * @method \Illuminate\Support\Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) - * @method \Illuminate\Support\Carbon|false createFromFormat($format, $time, $tz = null) - * @method \Illuminate\Support\Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) - * @method \Illuminate\Support\Carbon createFromTimeString($time, $tz = null) - * @method \Illuminate\Support\Carbon createFromTimestamp($timestamp, $tz = null) - * @method \Illuminate\Support\Carbon createFromTimestampMs($timestamp, $tz = null) - * @method \Illuminate\Support\Carbon createFromTimestampUTC($timestamp) - * @method \Illuminate\Support\Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) - * @method \Illuminate\Support\Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + * @method bool canBeCreatedFromFormat(?string $date, string $format) + * @method \Illuminate\Support\Carbon|null create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null) + * @method \Illuminate\Support\Carbon createFromDate($year = null, $month = null, $day = null, $timezone = null) + * @method \Illuminate\Support\Carbon|null createFromFormat($format, $time, $timezone = null) + * @method \Illuminate\Support\Carbon|null createFromIsoFormat(string $format, string $time, $timezone = null, ?string $locale = 'en', ?\Symfony\Contracts\Translation\TranslatorInterface $translator = null) + * @method \Illuminate\Support\Carbon|null createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null) + * @method \Illuminate\Support\Carbon|null createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null) + * @method \Illuminate\Support\Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null) + * @method \Illuminate\Support\Carbon createFromTimeString(string $time, \DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon createFromTimestamp(string|int|float $timestamp, \DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon createFromTimestampMs(string|int|float $timestamp, \DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon createFromTimestampMsUTC($timestamp) + * @method \Illuminate\Support\Carbon createFromTimestampUTC(string|int|float $timestamp) + * @method \Illuminate\Support\Carbon createMidnightDate($year = null, $month = null, $day = null, $timezone = null) + * @method \Illuminate\Support\Carbon|null createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null) + * @method \Illuminate\Support\Carbon createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null) * @method void disableHumanDiffOption($humanDiffOption) * @method void enableHumanDiffOption($humanDiffOption) - * @method mixed executeWithLocale($locale, $func) + * @method mixed executeWithLocale(string $locale, callable $func) * @method \Illuminate\Support\Carbon fromSerialized($value) * @method array getAvailableLocales() + * @method array getAvailableLocalesInfo() * @method array getDays() + * @method ?string getFallbackLocale() + * @method array getFormatsToIsoReplacements() * @method int getHumanDiffOptions() * @method array getIsoUnits() - * @method array getLastErrors() + * @method array|false getLastErrors() * @method string getLocale() * @method int getMidDayAt() + * @method string getTimeFormatByPrecision(string $unitPrecision) + * @method string|\Closure|null getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) * @method \Illuminate\Support\Carbon|null getTestNow() - * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() - * @method int getWeekEndsAt() - * @method int getWeekStartsAt() + * @method \Symfony\Contracts\Translation\TranslatorInterface getTranslator() + * @method int getWeekEndsAt(?string $locale = null) + * @method int getWeekStartsAt(?string $locale = null) * @method array getWeekendDays() - * @method bool hasFormat($date, $format) + * @method bool hasFormat(string $date, string $format) + * @method bool hasFormatWithModifiers(string $date, string $format) * @method bool hasMacro($name) - * @method bool hasRelativeKeywords($time) + * @method bool hasRelativeKeywords(?string $time) * @method bool hasTestNow() - * @method \Illuminate\Support\Carbon instance($date) + * @method \Illuminate\Support\Carbon instance(\DateTimeInterface $date) * @method bool isImmutable() * @method bool isModifiableUnit($unit) * @method bool isMutable() * @method bool isStrictModeEnabled() - * @method bool localeHasDiffOneDayWords($locale) - * @method bool localeHasDiffSyntax($locale) - * @method bool localeHasDiffTwoDayWords($locale) + * @method bool localeHasDiffOneDayWords(string $locale) + * @method bool localeHasDiffSyntax(string $locale) + * @method bool localeHasDiffTwoDayWords(string $locale) * @method bool localeHasPeriodSyntax($locale) - * @method bool localeHasShortUnits($locale) - * @method void macro($name, $macro) - * @method \Illuminate\Support\Carbon|null make($var) - * @method \Illuminate\Support\Carbon maxValue() - * @method \Illuminate\Support\Carbon minValue() - * @method void mixin($mixin) - * @method \Illuminate\Support\Carbon now($tz = null) - * @method \Illuminate\Support\Carbon parse($time = null, $tz = null) + * @method bool localeHasShortUnits(string $locale) + * @method void macro(string $name, ?callable $macro) + * @method \Illuminate\Support\Carbon|null make($var, \DateTimeZone|string|null $timezone = null) + * @method void mixin(object|string $mixin) + * @method \Illuminate\Support\Carbon now(\DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon parse(\DateTimeInterface|\Carbon\WeekDay|\Carbon\Month|string|int|float|null $time, \DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon parseFromLocale(string $time, ?string $locale = null, \DateTimeZone|string|int|null $timezone = null) * @method string pluralUnit(string $unit) + * @method \Illuminate\Support\Carbon|null rawCreateFromFormat(string $format, string $time, $timezone = null) + * @method \Illuminate\Support\Carbon rawParse(\DateTimeInterface|\Carbon\WeekDay|\Carbon\Month|string|int|float|null $time, \DateTimeZone|string|int|null $timezone = null) * @method void resetMonthsOverflow() * @method void resetToStringFormat() * @method void resetYearsOverflow() * @method void serializeUsing($callback) + * @method void setFallbackLocale(string $locale) * @method void setHumanDiffOptions($humanDiffOptions) - * @method bool setLocale($locale) + * @method void setLocale(string $locale) * @method void setMidDayAt($hour) - * @method void setTestNow($testNow = null) - * @method void setToStringFormat($format) - * @method void setTranslator(\Symfony\Component\Translation\TranslatorInterface $translator) - * @method void setUtf8($utf8) + * @method void setTestNow(mixed $testNow = null) + * @method void setTestNowAndTimezone(mixed $testNow = null, $timezone = null) + * @method void setToStringFormat(string|\Closure|null $format) + * @method void setTranslator(\Symfony\Contracts\Translation\TranslatorInterface $translator) * @method void setWeekEndsAt($day) * @method void setWeekStartsAt($day) * @method void setWeekendDays($days) * @method bool shouldOverflowMonths() * @method bool shouldOverflowYears() * @method string singularUnit(string $unit) - * @method \Illuminate\Support\Carbon today($tz = null) - * @method \Illuminate\Support\Carbon tomorrow($tz = null) + * @method void sleep(int|float $seconds) + * @method \Illuminate\Support\Carbon today(\DateTimeZone|string|int|null $timezone = null) + * @method \Illuminate\Support\Carbon tomorrow(\DateTimeZone|string|int|null $timezone = null) + * @method string translateTimeString(string $timeString, ?string $from = null, ?string $to = null, int $mode = \Carbon\CarbonInterface::TRANSLATE_ALL) + * @method string translateWith(\Symfony\Contracts\Translation\TranslatorInterface $translator, string $key, array $parameters = [], $number = null) * @method void useMonthsOverflow($monthsOverflow = true) * @method void useStrictMode($strictModeEnabled = true) * @method void useYearsOverflow($yearsOverflow = true) - * @method \Illuminate\Support\Carbon yesterday($tz = null) + * @method mixed withTestNow(mixed $testNow, callable $callback) + * @method static withTimeZone(\DateTimeZone|string|int|null $timezone) + * @method \Illuminate\Support\Carbon yesterday(\DateTimeZone|string|int|null $timezone = null) */ class DateFactory { diff --git a/src/Illuminate/Support/DefaultProviders.php b/src/Illuminate/Support/DefaultProviders.php index 791e86072b75..6430e8439f54 100644 --- a/src/Illuminate/Support/DefaultProviders.php +++ b/src/Illuminate/Support/DefaultProviders.php @@ -13,8 +13,6 @@ class DefaultProviders /** * Create a new default provider collection. - * - * @return void */ public function __construct(?array $providers = null) { diff --git a/src/Illuminate/Support/Defer/DeferredCallback.php b/src/Illuminate/Support/Defer/DeferredCallback.php index 2bf6ad4cbd1c..8372d12fccad 100644 --- a/src/Illuminate/Support/Defer/DeferredCallback.php +++ b/src/Illuminate/Support/Defer/DeferredCallback.php @@ -10,7 +10,6 @@ class DeferredCallback * Create a new deferred callback instance. * * @param callable $callback - * @return void */ public function __construct(public $callback, public ?string $name = null, public bool $always = false) { diff --git a/src/Illuminate/Support/Env.php b/src/Illuminate/Support/Env.php index 51ea918e0a4e..702f61d44f4c 100644 --- a/src/Illuminate/Support/Env.php +++ b/src/Illuminate/Support/Env.php @@ -2,6 +2,7 @@ namespace Illuminate\Support; +use Closure; use Dotenv\Repository\Adapter\PutenvAdapter; use Dotenv\Repository\RepositoryBuilder; use PhpOption\Option; @@ -23,6 +24,13 @@ class Env */ protected static $repository; + /** + * The list of custom adapters for loading environment variables. + * + * @var array + */ + protected static $customAdapters = []; + /** * Enable the putenv adapter. * @@ -45,6 +53,18 @@ public static function disablePutenv() static::$repository = null; } + /** + * Register a custom adapter creator Closure. + */ + public static function extend(Closure $callback, ?string $name = null): void + { + if (! is_null($name)) { + static::$customAdapters[$name] = $callback; + } else { + static::$customAdapters[] = $callback; + } + } + /** * Get the environment repository instance. * @@ -59,6 +79,10 @@ public static function getRepository() $builder = $builder->addAdapter(PutenvAdapter::class); } + foreach (static::$customAdapters as $adapter) { + $builder = $builder->addAdapter($adapter()); + } + static::$repository = $builder->immutable()->make(); } diff --git a/src/Illuminate/Support/Facades/App.php b/src/Illuminate/Support/Facades/App.php index fbf1035e54e6..ed8a9d6dce10 100755 --- a/src/Illuminate/Support/Facades/App.php +++ b/src/Illuminate/Support/Facades/App.php @@ -101,16 +101,16 @@ * @method static bool has(string $id) * @method static bool isShared(string $abstract) * @method static bool isAlias(string $name) - * @method static void bind(string $abstract, \Closure|string|null $concrete = null, bool $shared = false) + * @method static void bind(\Closure|string $abstract, \Closure|string|null $concrete = null, bool $shared = false) * @method static bool hasMethodBinding(string $method) * @method static void bindMethod(array|string $method, \Closure $callback) * @method static mixed callMethodBinding(string $method, mixed $instance) - * @method static void addContextualBinding(string $concrete, string $abstract, \Closure|string $implementation) - * @method static void bindIf(string $abstract, \Closure|string|null $concrete = null, bool $shared = false) - * @method static void singleton(string $abstract, \Closure|string|null $concrete = null) - * @method static void singletonIf(string $abstract, \Closure|string|null $concrete = null) - * @method static void scoped(string $abstract, \Closure|string|null $concrete = null) - * @method static void scopedIf(string $abstract, \Closure|string|null $concrete = null) + * @method static void addContextualBinding(string $concrete, \Closure|string $abstract, \Closure|string $implementation) + * @method static void bindIf(\Closure|string $abstract, \Closure|string|null $concrete = null, bool $shared = false) + * @method static void singleton(\Closure|string $abstract, \Closure|string|null $concrete = null) + * @method static void singletonIf(\Closure|string $abstract, \Closure|string|null $concrete = null) + * @method static void scoped(\Closure|string $abstract, \Closure|string|null $concrete = null) + * @method static void scopedIf(\Closure|string $abstract, \Closure|string|null $concrete = null) * @method static void extend(string $abstract, \Closure $closure) * @method static mixed instance(string $abstract, mixed $instance) * @method static void tag(array|string $abstracts, array|mixed $tags) diff --git a/src/Illuminate/Support/Facades/Bus.php b/src/Illuminate/Support/Facades/Bus.php index 337108f31d85..affeb6076985 100644 --- a/src/Illuminate/Support/Facades/Bus.php +++ b/src/Illuminate/Support/Facades/Bus.php @@ -20,6 +20,8 @@ * @method static void dispatchAfterResponse(mixed $command, mixed $handler = null) * @method static \Illuminate\Bus\Dispatcher pipeThrough(array $pipes) * @method static \Illuminate\Bus\Dispatcher map(array $map) + * @method static \Illuminate\Bus\Dispatcher withDispatchingAfterResponses() + * @method static \Illuminate\Bus\Dispatcher withoutDispatchingAfterResponses() * @method static \Illuminate\Support\Testing\Fakes\BusFake except(array|string $jobsToDispatch) * @method static void assertDispatched(string|\Closure $command, callable|int|null $callback = null) * @method static void assertDispatchedTimes(string|\Closure $command, int $times = 1) @@ -66,8 +68,8 @@ class Bus extends Facade public static function fake($jobsToFake = [], ?BatchRepository $batchRepository = null) { $actualDispatcher = static::isFake() - ? static::getFacadeRoot()->dispatcher - : static::getFacadeRoot(); + ? static::getFacadeRoot()->dispatcher + : static::getFacadeRoot(); return tap(new BusFake($actualDispatcher, $jobsToFake, $batchRepository), function ($fake) { static::swap($fake); diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index 1463306365ca..07553d8bb812 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -5,6 +5,7 @@ /** * @method static \Illuminate\Contracts\Cache\Repository store(string|null $name = null) * @method static \Illuminate\Contracts\Cache\Repository driver(string|null $driver = null) + * @method static \Illuminate\Contracts\Cache\Repository memo(string|null $driver = null) * @method static \Illuminate\Contracts\Cache\Repository resolve(string $name) * @method static \Illuminate\Cache\Repository build(array $config) * @method static \Illuminate\Cache\Repository repository(\Illuminate\Contracts\Cache\Store $store, array $config = []) diff --git a/src/Illuminate/Support/Facades/Context.php b/src/Illuminate/Support/Facades/Context.php index a08bf9b4a834..6b894fad39b7 100644 --- a/src/Illuminate/Support/Facades/Context.php +++ b/src/Illuminate/Support/Facades/Context.php @@ -25,8 +25,11 @@ * @method static mixed pop(string $key) * @method static \Illuminate\Log\Context\Repository pushHidden(string $key, mixed ...$values) * @method static mixed popHidden(string $key) + * @method static \Illuminate\Log\Context\Repository increment(string $key, int $amount = 1) + * @method static \Illuminate\Log\Context\Repository decrement(string $key, int $amount = 1) * @method static bool stackContains(string $key, mixed $value, bool $strict = false) * @method static bool hiddenStackContains(string $key, mixed $value, bool $strict = false) + * @method static mixed scope(callable $callback, array $data = [], array $hidden = []) * @method static bool isEmpty() * @method static \Illuminate\Log\Context\Repository dehydrating(callable $callback) * @method static \Illuminate\Log\Context\Repository hydrated(callable $callback) diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php index c2d4249727d2..f1c41bc0abb4 100644 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -104,8 +104,7 @@ * @method static \Illuminate\Database\Connection setReadWriteType(string|null $readWriteType) * @method static string getTablePrefix() * @method static \Illuminate\Database\Connection setTablePrefix(string $prefix) - * @method static \Illuminate\Database\Grammar withTablePrefix(\Illuminate\Database\Grammar $grammar) - * @method static void withoutTablePrefix(\Closure $callback) + * @method static mixed withoutTablePrefix(\Closure $callback) * @method static string getServerVersion() * @method static void resolverFor(string $driver, \Closure $callback) * @method static \Closure|null getResolver(string $driver) @@ -123,7 +122,7 @@ class DB extends Facade /** * Indicate if destructive Artisan commands should be prohibited. * - * Prohibits: db:wipe, migrate:fresh, migrate:refresh, and migrate:reset + * Prohibits: db:wipe, migrate:fresh, migrate:refresh, migrate:reset, and migrate:rollback * * @param bool $prohibit * @return void diff --git a/src/Illuminate/Support/Facades/Date.php b/src/Illuminate/Support/Facades/Date.php index 7d4607f99d67..4f62930ac285 100644 --- a/src/Illuminate/Support/Facades/Date.php +++ b/src/Illuminate/Support/Facades/Date.php @@ -13,77 +13,96 @@ * @method static void useCallable(callable $callable) * @method static void useClass(string $dateClass) * @method static void useFactory(object $factory) - * @method static \Illuminate\Support\Carbon create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) - * @method static \Illuminate\Support\Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) - * @method static \Illuminate\Support\Carbon|false createFromFormat($format, $time, $tz = null) - * @method static \Illuminate\Support\Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) - * @method static \Illuminate\Support\Carbon createFromTimeString($time, $tz = null) - * @method static \Illuminate\Support\Carbon createFromTimestamp($timestamp, $tz = null) - * @method static \Illuminate\Support\Carbon createFromTimestampMs($timestamp, $tz = null) - * @method static \Illuminate\Support\Carbon createFromTimestampUTC($timestamp) - * @method static \Illuminate\Support\Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) - * @method static \Illuminate\Support\Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + * @method static bool canBeCreatedFromFormat(?string $date, string $format) + * @method static \Illuminate\Support\Carbon|null create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null) + * @method static \Illuminate\Support\Carbon createFromDate($year = null, $month = null, $day = null, $timezone = null) + * @method static \Illuminate\Support\Carbon|null createFromFormat($format, $time, $timezone = null) + * @method static \Illuminate\Support\Carbon|null createFromIsoFormat(string $format, string $time, $timezone = null, ?string $locale = 'en', ?\Symfony\Contracts\Translation\TranslatorInterface $translator = null) + * @method static \Illuminate\Support\Carbon|null createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null) + * @method static \Illuminate\Support\Carbon|null createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null) + * @method static \Illuminate\Support\Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null) + * @method static \Illuminate\Support\Carbon createFromTimeString(string $time, \DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon createFromTimestamp(string|int|float $timestamp, \DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon createFromTimestampMs(string|int|float $timestamp, \DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon createFromTimestampMsUTC($timestamp) + * @method static \Illuminate\Support\Carbon createFromTimestampUTC(string|int|float $timestamp) + * @method static \Illuminate\Support\Carbon createMidnightDate($year = null, $month = null, $day = null, $timezone = null) + * @method static \Illuminate\Support\Carbon|null createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null) + * @method static \Illuminate\Support\Carbon createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null) * @method static void disableHumanDiffOption($humanDiffOption) * @method static void enableHumanDiffOption($humanDiffOption) - * @method static mixed executeWithLocale($locale, $func) + * @method static mixed executeWithLocale(string $locale, callable $func) * @method static \Illuminate\Support\Carbon fromSerialized($value) * @method static array getAvailableLocales() + * @method static array getAvailableLocalesInfo() * @method static array getDays() + * @method static ?string getFallbackLocale() + * @method static array getFormatsToIsoReplacements() * @method static int getHumanDiffOptions() * @method static array getIsoUnits() - * @method static array getLastErrors() + * @method static array|false getLastErrors() * @method static string getLocale() * @method static int getMidDayAt() + * @method static string getTimeFormatByPrecision(string $unitPrecision) + * @method static string|\Closure|null getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) * @method static \Illuminate\Support\Carbon|null getTestNow() - * @method static \Symfony\Component\Translation\TranslatorInterface getTranslator() - * @method static int getWeekEndsAt() - * @method static int getWeekStartsAt() + * @method static \Symfony\Contracts\Translation\TranslatorInterface getTranslator() + * @method static int getWeekEndsAt(?string $locale = null) + * @method static int getWeekStartsAt(?string $locale = null) * @method static array getWeekendDays() - * @method static bool hasFormat($date, $format) + * @method static bool hasFormat(string $date, string $format) + * @method static bool hasFormatWithModifiers(string $date, string $format) * @method static bool hasMacro($name) - * @method static bool hasRelativeKeywords($time) + * @method static bool hasRelativeKeywords(?string $time) * @method static bool hasTestNow() - * @method static \Illuminate\Support\Carbon instance($date) + * @method static \Illuminate\Support\Carbon instance(\DateTimeInterface $date) * @method static bool isImmutable() * @method static bool isModifiableUnit($unit) * @method static bool isMutable() * @method static bool isStrictModeEnabled() - * @method static bool localeHasDiffOneDayWords($locale) - * @method static bool localeHasDiffSyntax($locale) - * @method static bool localeHasDiffTwoDayWords($locale) + * @method static bool localeHasDiffOneDayWords(string $locale) + * @method static bool localeHasDiffSyntax(string $locale) + * @method static bool localeHasDiffTwoDayWords(string $locale) * @method static bool localeHasPeriodSyntax($locale) - * @method static bool localeHasShortUnits($locale) - * @method static void macro($name, $macro) - * @method static \Illuminate\Support\Carbon|null make($var) - * @method static \Illuminate\Support\Carbon maxValue() - * @method static \Illuminate\Support\Carbon minValue() - * @method static void mixin($mixin) - * @method static \Illuminate\Support\Carbon now($tz = null) - * @method static \Illuminate\Support\Carbon parse($time = null, $tz = null) + * @method static bool localeHasShortUnits(string $locale) + * @method static void macro(string $name, ?callable $macro) + * @method static \Illuminate\Support\Carbon|null make($var, \DateTimeZone|string|null $timezone = null) + * @method static void mixin(object|string $mixin) + * @method static \Illuminate\Support\Carbon now(\DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon parse(\DateTimeInterface|\Carbon\WeekDay|\Carbon\Month|string|int|float|null $time, \DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon parseFromLocale(string $time, ?string $locale = null, \DateTimeZone|string|int|null $timezone = null) * @method static string pluralUnit(string $unit) + * @method static \Illuminate\Support\Carbon|null rawCreateFromFormat(string $format, string $time, $timezone = null) + * @method static \Illuminate\Support\Carbon rawParse(\DateTimeInterface|\Carbon\WeekDay|\Carbon\Month|string|int|float|null $time, \DateTimeZone|string|int|null $timezone = null) * @method static void resetMonthsOverflow() * @method static void resetToStringFormat() * @method static void resetYearsOverflow() * @method static void serializeUsing($callback) + * @method static void setFallbackLocale(string $locale) * @method static void setHumanDiffOptions($humanDiffOptions) - * @method static bool setLocale($locale) + * @method static void setLocale(string $locale) * @method static void setMidDayAt($hour) - * @method static void setTestNow($testNow = null) - * @method static void setToStringFormat($format) - * @method static void setTranslator(\Symfony\Component\Translation\TranslatorInterface $translator) - * @method static void setUtf8($utf8) + * @method static void setTestNow(mixed $testNow = null) + * @method static void setTestNowAndTimezone(mixed $testNow = null, $timezone = null) + * @method static void setToStringFormat(string|\Closure|null $format) + * @method static void setTranslator(\Symfony\Contracts\Translation\TranslatorInterface $translator) * @method static void setWeekEndsAt($day) * @method static void setWeekStartsAt($day) * @method static void setWeekendDays($days) * @method static bool shouldOverflowMonths() * @method static bool shouldOverflowYears() * @method static string singularUnit(string $unit) - * @method static \Illuminate\Support\Carbon today($tz = null) - * @method static \Illuminate\Support\Carbon tomorrow($tz = null) + * @method static void sleep(int|float $seconds) + * @method static \Illuminate\Support\Carbon today(\DateTimeZone|string|int|null $timezone = null) + * @method static \Illuminate\Support\Carbon tomorrow(\DateTimeZone|string|int|null $timezone = null) + * @method static string translateTimeString(string $timeString, ?string $from = null, ?string $to = null, int $mode = \Carbon\CarbonInterface::TRANSLATE_ALL) + * @method static string translateWith(\Symfony\Contracts\Translation\TranslatorInterface $translator, string $key, array $parameters = [], $number = null) * @method static void useMonthsOverflow($monthsOverflow = true) * @method static void useStrictMode($strictModeEnabled = true) * @method static void useYearsOverflow($yearsOverflow = true) - * @method static \Illuminate\Support\Carbon yesterday($tz = null) + * @method static mixed withTestNow(mixed $testNow, callable $callback) + * @method static static withTimeZone(\DateTimeZone|string|int|null $timezone) + * @method static \Illuminate\Support\Carbon yesterday(\DateTimeZone|string|int|null $timezone = null) * * @see \Illuminate\Support\DateFactory */ diff --git a/src/Illuminate/Support/Facades/Event.php b/src/Illuminate/Support/Facades/Event.php index cbd5e5f8a69d..7200978c3baf 100755 --- a/src/Illuminate/Support/Facades/Event.php +++ b/src/Illuminate/Support/Facades/Event.php @@ -50,8 +50,8 @@ class Event extends Facade public static function fake($eventsToFake = []) { $actualDispatcher = static::isFake() - ? static::getFacadeRoot()->dispatcher - : static::getFacadeRoot(); + ? static::getFacadeRoot()->dispatcher + : static::getFacadeRoot(); return tap(new EventFake($actualDispatcher, $eventsToFake), function ($fake) { static::swap($fake); diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index 60489694d3c6..ecbcca5ba49c 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -10,10 +10,13 @@ * @method static \Illuminate\Http\Client\Factory globalResponseMiddleware(callable $middleware) * @method static \Illuminate\Http\Client\Factory globalOptions(\Closure|array $options) * @method static \GuzzleHttp\Promise\PromiseInterface response(array|string|null $body = null, int $status = 200, array $headers = []) + * @method static \GuzzleHttp\Psr7\Response psr7Response(array|string|null $body = null, int $status = 200, array $headers = []) + * @method static \Illuminate\Http\Client\RequestException failedRequest(array|string|null $body = null, int $status = 200, array $headers = []) * @method static \GuzzleHttp\Promise\PromiseInterface failedConnection(string|null $message = null) * @method static \Illuminate\Http\Client\ResponseSequence sequence(array $responses = []) * @method static bool preventingStrayRequests() * @method static \Illuminate\Http\Client\Factory allowStrayRequests() + * @method static \Illuminate\Http\Client\Factory record() * @method static void recordRequestResponsePair(\Illuminate\Http\Client\Request $request, \Illuminate\Http\Client\Response|null $response) * @method static void assertSent(callable $callback) * @method static void assertSentInOrder(array $callbacks) @@ -69,10 +72,10 @@ * @method static \Illuminate\Http\Client\PendingRequest dd() * @method static \Illuminate\Http\Client\Response get(string $url, array|string|null $query = null) * @method static \Illuminate\Http\Client\Response head(string $url, array|string|null $query = null) - * @method static \Illuminate\Http\Client\Response post(string $url, array $data = []) - * @method static \Illuminate\Http\Client\Response patch(string $url, array $data = []) - * @method static \Illuminate\Http\Client\Response put(string $url, array $data = []) - * @method static \Illuminate\Http\Client\Response delete(string $url, array $data = []) + * @method static \Illuminate\Http\Client\Response post(string $url, array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data = []) + * @method static \Illuminate\Http\Client\Response patch(string $url, array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data = []) + * @method static \Illuminate\Http\Client\Response put(string $url, array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data = []) + * @method static \Illuminate\Http\Client\Response delete(string $url, array|\JsonSerializable|\Illuminate\Contracts\Support\Arrayable $data = []) * @method static array pool(callable $callback) * @method static \Illuminate\Http\Client\Response send(string $method, string $url, array $options = []) * @method static \GuzzleHttp\Client buildClient() diff --git a/src/Illuminate/Support/Facades/Log.php b/src/Illuminate/Support/Facades/Log.php index 88c04028dc0c..fad58f3698f9 100755 --- a/src/Illuminate/Support/Facades/Log.php +++ b/src/Illuminate/Support/Facades/Log.php @@ -9,7 +9,7 @@ * @method static \Psr\Log\LoggerInterface driver(string|null $driver = null) * @method static \Illuminate\Log\LogManager shareContext(array $context) * @method static array sharedContext() - * @method static \Illuminate\Log\LogManager withoutContext() + * @method static \Illuminate\Log\LogManager withoutContext(string[]|null $keys = null) * @method static \Illuminate\Log\LogManager flushSharedContext() * @method static string|null getDefaultDriver() * @method static void setDefaultDriver(string $name) diff --git a/src/Illuminate/Support/Facades/Mail.php b/src/Illuminate/Support/Facades/Mail.php index fb4019b25aae..8e68581772e5 100755 --- a/src/Illuminate/Support/Facades/Mail.php +++ b/src/Illuminate/Support/Facades/Mail.php @@ -71,8 +71,8 @@ class Mail extends Facade public static function fake() { $actualMailManager = static::isFake() - ? static::getFacadeRoot()->manager - : static::getFacadeRoot(); + ? static::getFacadeRoot()->manager + : static::getFacadeRoot(); return tap(new MailFake($actualMailManager), function ($fake) { static::swap($fake); diff --git a/src/Illuminate/Support/Facades/Queue.php b/src/Illuminate/Support/Facades/Queue.php index 4201b728ab92..f11c374c8dac 100755 --- a/src/Illuminate/Support/Facades/Queue.php +++ b/src/Illuminate/Support/Facades/Queue.php @@ -49,6 +49,7 @@ * @method static void assertNothingPushed() * @method static \Illuminate\Support\Collection pushed(string $job, callable|null $callback = null) * @method static \Illuminate\Support\Collection pushedRaw(null|\Closure $callback = null) + * @method static \Illuminate\Support\Collection listenersPushed(string $listenerClass, \Closure|null $callback = null) * @method static bool hasPushed(string $job) * @method static bool shouldFakeJob(object $job) * @method static array pushedJobs() @@ -82,8 +83,8 @@ public static function popUsing($workerName, $callback) public static function fake($jobsToFake = []) { $actualQueueManager = static::isFake() - ? static::getFacadeRoot()->queue - : static::getFacadeRoot(); + ? static::getFacadeRoot()->queue + : static::getFacadeRoot(); return tap(new QueueFake(static::getFacadeApplication(), $jobsToFake, $actualQueueManager), function ($fake) { static::swap($fake); diff --git a/src/Illuminate/Support/Facades/RateLimiter.php b/src/Illuminate/Support/Facades/RateLimiter.php index 376c3ccc19dc..7f8cf5c2166c 100644 --- a/src/Illuminate/Support/Facades/RateLimiter.php +++ b/src/Illuminate/Support/Facades/RateLimiter.php @@ -5,11 +5,11 @@ /** * @method static \Illuminate\Cache\RateLimiter for(\BackedEnum|\UnitEnum|string $name, \Closure $callback) * @method static \Closure|null limiter(\BackedEnum|\UnitEnum|string $name) - * @method static mixed attempt(string $key, int $maxAttempts, \Closure $callback, int $decaySeconds = 60) + * @method static mixed attempt(string $key, int $maxAttempts, \Closure $callback, \DateTimeInterface|\DateInterval|int $decaySeconds = 60) * @method static bool tooManyAttempts(string $key, int $maxAttempts) - * @method static int hit(string $key, int $decaySeconds = 60) - * @method static int increment(string $key, int $decaySeconds = 60, int $amount = 1) - * @method static int decrement(string $key, int $decaySeconds = 60, int $amount = 1) + * @method static int hit(string $key, \DateTimeInterface|\DateInterval|int $decaySeconds = 60) + * @method static int increment(string $key, \DateTimeInterface|\DateInterval|int $decaySeconds = 60, int $amount = 1) + * @method static int decrement(string $key, \DateTimeInterface|\DateInterval|int $decaySeconds = 60, int $amount = 1) * @method static mixed attempts(string $key) * @method static mixed resetAttempts(string $key) * @method static int remaining(string $key, int $maxAttempts) diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 8c782950b75f..8461fa41b1ee 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -151,7 +151,7 @@ * @method static string|array|null cookie(string|null $key = null, string|array|null $default = null) * @method static array allFiles() * @method static bool hasFile(string $key) - * @method static \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null file(string|null $key = null, mixed $default = null) + * @method static array|(\Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null file(string|null $key = null, mixed $default = null) * @method static \Illuminate\Http\Request dump(mixed $keys = []) * @method static never dd(mixed ...$args) * @method static bool exists(string|array $key) diff --git a/src/Illuminate/Support/Facades/Response.php b/src/Illuminate/Support/Facades/Response.php index 1c5bfac0d137..f2dc641b77c5 100755 --- a/src/Illuminate/Support/Facades/Response.php +++ b/src/Illuminate/Support/Facades/Response.php @@ -10,7 +10,7 @@ * @method static \Illuminate\Http\Response view(string|array $view, array $data = [], int $status = 200, array $headers = []) * @method static \Illuminate\Http\JsonResponse json(mixed $data = [], int $status = 200, array $headers = [], int $options = 0) * @method static \Illuminate\Http\JsonResponse jsonp(string $callback, mixed $data = [], int $status = 200, array $headers = [], int $options = 0) - * @method static \Symfony\Component\HttpFoundation\StreamedResponse eventStream(\Closure $callback, array $headers = [], string $endStreamWith = '') + * @method static \Symfony\Component\HttpFoundation\StreamedResponse eventStream(\Closure $callback, array $headers = [], \Illuminate\Http\StreamedEvent|string|null $endStreamWith = '') * @method static \Symfony\Component\HttpFoundation\StreamedResponse stream(callable $callback, int $status = 200, array $headers = []) * @method static \Symfony\Component\HttpFoundation\StreamedJsonResponse streamJson(array $data, int $status = 200, array $headers = [], int $encodingOptions = 15) * @method static \Symfony\Component\HttpFoundation\StreamedResponse streamDownload(callable $callback, string|null $name = null, array $headers = [], string|null $disposition = 'attachment') diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index a7feff2e7045..d0e3e5f84bf1 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -4,17 +4,19 @@ /** * @method static void defaultStringLength(int $length) + * @method static void defaultTimePrecision(int|null $precision) * @method static void defaultMorphKeyType(string $type) * @method static void morphUsingUuids() * @method static void morphUsingUlids() * @method static bool createDatabase(string $name) * @method static bool dropDatabaseIfExists(string $name) + * @method static array getSchemas() * @method static bool hasTable(string $table) * @method static bool hasView(string $view) - * @method static array getTables() - * @method static array getTableListing() - * @method static array getViews() - * @method static array getTypes() + * @method static array getTables(string|string[]|null $schema = null) + * @method static array getTableListing(string|string[]|null $schema = null, bool $schemaQualified = true) + * @method static array getViews(string|string[]|null $schema = null) + * @method static array getTypes(string|string[]|null $schema = null) * @method static bool hasColumn(string $table, string $column) * @method static bool hasColumns(string $table, array $columns) * @method static void whenTableHasColumn(string $table, string $column, \Closure $callback) @@ -38,9 +40,11 @@ * @method static bool enableForeignKeyConstraints() * @method static bool disableForeignKeyConstraints() * @method static mixed withoutForeignKeyConstraints(\Closure $callback) + * @method static string[]|null getCurrentSchemaListing() + * @method static string|null getCurrentSchemaName() + * @method static array parseSchemaAndTable(string $reference, string|bool|null $withDefaultSchema = null) * @method static \Illuminate\Database\Connection getConnection() - * @method static \Illuminate\Database\Schema\Builder setConnection(\Illuminate\Database\Connection $connection) - * @method static void blueprintResolver(\Closure $resolver) + * @method static void blueprintResolver(\Closure|null $resolver) * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) diff --git a/src/Illuminate/Support/Facades/URL.php b/src/Illuminate/Support/Facades/URL.php index 6febb2c1b135..acd1107c10f6 100755 --- a/src/Illuminate/Support/Facades/URL.php +++ b/src/Illuminate/Support/Facades/URL.php @@ -16,9 +16,9 @@ * @method static string formatScheme(bool|null $secure = null) * @method static string signedRoute(\BackedEnum|string $name, mixed $parameters = [], \DateTimeInterface|\DateInterval|int|null $expiration = null, bool $absolute = true) * @method static string temporarySignedRoute(\BackedEnum|string $name, \DateTimeInterface|\DateInterval|int $expiration, array $parameters = [], bool $absolute = true) - * @method static bool hasValidSignature(\Illuminate\Http\Request $request, bool $absolute = true, array $ignoreQuery = []) - * @method static bool hasValidRelativeSignature(\Illuminate\Http\Request $request, array $ignoreQuery = []) - * @method static bool hasCorrectSignature(\Illuminate\Http\Request $request, bool $absolute = true, array $ignoreQuery = []) + * @method static bool hasValidSignature(\Illuminate\Http\Request $request, bool $absolute = true, \Closure|array $ignoreQuery = []) + * @method static bool hasValidRelativeSignature(\Illuminate\Http\Request $request, \Closure|array $ignoreQuery = []) + * @method static bool hasCorrectSignature(\Illuminate\Http\Request $request, bool $absolute = true, \Closure|array $ignoreQuery = []) * @method static bool signatureHasNotExpired(\Illuminate\Http\Request $request) * @method static string route(\BackedEnum|string $name, mixed $parameters = [], bool $absolute = true) * @method static string toRoute(\Illuminate\Routing\Route $route, mixed $parameters, bool $absolute) diff --git a/src/Illuminate/Support/Facades/Vite.php b/src/Illuminate/Support/Facades/Vite.php index 7cdde4f5eece..6f727c89c77d 100644 --- a/src/Illuminate/Support/Facades/Vite.php +++ b/src/Illuminate/Support/Facades/Vite.php @@ -27,6 +27,7 @@ * @method static string|null manifestHash(string|null $buildDirectory = null) * @method static bool isRunningHot() * @method static string toHtml() + * @method static void flush() * @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/Fluent.php b/src/Illuminate/Support/Fluent.php index 4622829907ff..e420711b3fa3 100755 --- a/src/Illuminate/Support/Fluent.php +++ b/src/Illuminate/Support/Fluent.php @@ -5,6 +5,7 @@ use ArrayAccess; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\InteractsWithData; use Illuminate\Support\Traits\Macroable; use JsonSerializable; @@ -18,7 +19,7 @@ */ class Fluent implements Arrayable, ArrayAccess, Jsonable, JsonSerializable { - use InteractsWithData, Macroable { + use Conditionable, InteractsWithData, Macroable { __call as macroCall; } @@ -33,13 +34,23 @@ class Fluent implements Arrayable, ArrayAccess, Jsonable, JsonSerializable * Create a new fluent instance. * * @param iterable $attributes - * @return void */ public function __construct($attributes = []) { $this->fill($attributes); } + /** + * Create a new fluent instance. + * + * @param iterable $attributes + * @return static + */ + public static function make($attributes = []) + { + return new static($attributes); + } + /** * Get an attribute from the fluent instance using "dot" notation. * diff --git a/src/Illuminate/Support/HigherOrderTapProxy.php b/src/Illuminate/Support/HigherOrderTapProxy.php index bbf9b2e54db8..85201f0814c1 100644 --- a/src/Illuminate/Support/HigherOrderTapProxy.php +++ b/src/Illuminate/Support/HigherOrderTapProxy.php @@ -15,7 +15,6 @@ class HigherOrderTapProxy * Create a new tap proxy instance. * * @param mixed $target - * @return void */ public function __construct($target) { diff --git a/src/Illuminate/Support/HtmlString.php b/src/Illuminate/Support/HtmlString.php index a25311132579..6b8d98ccb063 100644 --- a/src/Illuminate/Support/HtmlString.php +++ b/src/Illuminate/Support/HtmlString.php @@ -18,7 +18,6 @@ class HtmlString implements Htmlable, Stringable * Create a new HTML string instance. * * @param string $html - * @return void */ public function __construct($html = '') { diff --git a/src/Illuminate/Support/InteractsWithTime.php b/src/Illuminate/Support/InteractsWithTime.php index 6a64f12a4ce4..6b78be16c9b0 100644 --- a/src/Illuminate/Support/InteractsWithTime.php +++ b/src/Illuminate/Support/InteractsWithTime.php @@ -19,8 +19,8 @@ protected function secondsUntil($delay) $delay = $this->parseDateInterval($delay); return $delay instanceof DateTimeInterface - ? max(0, $delay->getTimestamp() - $this->currentTime()) - : (int) $delay; + ? max(0, $delay->getTimestamp() - $this->currentTime()) + : (int) $delay; } /** @@ -34,8 +34,8 @@ protected function availableAt($delay = 0) $delay = $this->parseDateInterval($delay); return $delay instanceof DateTimeInterface - ? $delay->getTimestamp() - : Carbon::now()->addRealSeconds($delay)->getTimestamp(); + ? $delay->getTimestamp() + : Carbon::now()->addSeconds($delay)->getTimestamp(); } /** diff --git a/src/Illuminate/Support/Js.php b/src/Illuminate/Support/Js.php index aeb67665d2ca..ad26ddb4b47a 100644 --- a/src/Illuminate/Support/Js.php +++ b/src/Illuminate/Support/Js.php @@ -31,7 +31,6 @@ class Js implements Htmlable, Stringable * @param mixed $data * @param int|null $flags * @param int $depth - * @return void * * @throws \JsonException */ diff --git a/src/Illuminate/Support/Lottery.php b/src/Illuminate/Support/Lottery.php index d6de350dbc9f..1f8c80588345 100644 --- a/src/Illuminate/Support/Lottery.php +++ b/src/Illuminate/Support/Lottery.php @@ -46,7 +46,6 @@ class Lottery * * @param int|float $chances * @param int|null $outOf - * @return void */ public function __construct($chances, $outOf = null) { diff --git a/src/Illuminate/Support/Manager.php b/src/Illuminate/Support/Manager.php index dac4731226b2..ea1a22751241 100755 --- a/src/Illuminate/Support/Manager.php +++ b/src/Illuminate/Support/Manager.php @@ -40,7 +40,6 @@ abstract class Manager * Create a new manager instance. * * @param \Illuminate\Contracts\Container\Container $container - * @return void */ public function __construct(Container $container) { diff --git a/src/Illuminate/Support/MessageBag.php b/src/Illuminate/Support/MessageBag.php index 5b4e6a64ebd5..24c5dadf544b 100755 --- a/src/Illuminate/Support/MessageBag.php +++ b/src/Illuminate/Support/MessageBag.php @@ -14,7 +14,7 @@ class MessageBag implements Jsonable, JsonSerializable, MessageBagContract, Mess /** * All of the registered messages. * - * @var array + * @var array> */ protected $messages = []; @@ -28,8 +28,7 @@ class MessageBag implements Jsonable, JsonSerializable, MessageBagContract, Mess /** * Create a new message bag instance. * - * @param array $messages - * @return void + * @param array> $messages */ public function __construct(array $messages = []) { @@ -43,7 +42,7 @@ public function __construct(array $messages = []) /** * Get the keys present in the message bag. * - * @return array + * @return array */ public function keys() { @@ -96,7 +95,7 @@ protected function isUnique($key, $message) /** * Merge a new array of messages into the message bag. * - * @param \Illuminate\Contracts\Support\MessageProvider|array $messages + * @param \Illuminate\Contracts\Support\MessageProvider|array> $messages * @return $this */ public function merge($messages) @@ -194,7 +193,7 @@ public function first($key = null, $format = null) * * @param string $key * @param string|null $format - * @return array + * @return array|array> */ public function get($key, $format = null) { @@ -219,7 +218,7 @@ public function get($key, $format = null) * * @param string $key * @param string|null $format - * @return array + * @return array> */ protected function getMessagesForWildcardKey($key, $format) { @@ -235,7 +234,7 @@ protected function getMessagesForWildcardKey($key, $format) * Get all of the messages for every key in the message bag. * * @param string|null $format - * @return array + * @return array */ public function all($format = null) { @@ -277,10 +276,10 @@ public function forget($key) /** * Format an array of messages. * - * @param array $messages + * @param array $messages * @param string $format * @param string $messageKey - * @return array + * @return array */ protected function transform($messages, $format, $messageKey) { @@ -311,7 +310,7 @@ protected function checkFormat($format) /** * Get the raw messages in the message bag. * - * @return array + * @return array> */ public function messages() { @@ -321,7 +320,7 @@ public function messages() /** * Get the raw messages in the message bag. * - * @return array + * @return array> */ public function getMessages() { diff --git a/src/Illuminate/Support/MultipleInstanceManager.php b/src/Illuminate/Support/MultipleInstanceManager.php index 05a8c23b4135..66fff08e3c9a 100644 --- a/src/Illuminate/Support/MultipleInstanceManager.php +++ b/src/Illuminate/Support/MultipleInstanceManager.php @@ -47,7 +47,6 @@ abstract class MultipleInstanceManager * Create a new manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { @@ -192,6 +191,9 @@ public function purge($name = null) * * @param string $name * @param \Closure $callback + * + * @param-closure-this $this $callback + * * @return $this */ public function extend($name, Closure $callback) diff --git a/src/Illuminate/Support/NamespacedItemResolver.php b/src/Illuminate/Support/NamespacedItemResolver.php index a059c6daff87..10007be9e30e 100755 --- a/src/Illuminate/Support/NamespacedItemResolver.php +++ b/src/Illuminate/Support/NamespacedItemResolver.php @@ -60,8 +60,8 @@ protected function parseBasicSegments(array $segments) // a specific item out of a group and will need to return this item name // as well as the group so we know which item to pull from the arrays. $item = count($segments) === 1 - ? null - : implode('.', array_slice($segments, 1)); + ? null + : implode('.', array_slice($segments, 1)); return [null, $group, $item]; } diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index d27a717e74a0..ec5d6342d416 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -267,21 +267,22 @@ public static function clamp(int|float $number, int|float $min, int|float $max) * * @param int|float $to * @param int|float $by + * @param int|float $start * @param int|float $offset * @return array */ - public static function pairs(int|float $to, int|float $by, int|float $offset = 1) + public static function pairs(int|float $to, int|float $by, int|float $start = 0, int|float $offset = 1) { $output = []; - for ($lower = 0; $lower < $to; $lower += $by) { - $upper = $lower + $by; + for ($lower = $start; $lower < $to; $lower += $by) { + $upper = $lower + $by - $offset; if ($upper > $to) { $upper = $to; } - $output[] = [$lower + $offset, $upper]; + $output[] = [$lower, $upper]; } return $output; diff --git a/src/Illuminate/Support/Once.php b/src/Illuminate/Support/Once.php index 0b1741ff46b1..4d860b298f02 100644 --- a/src/Illuminate/Support/Once.php +++ b/src/Illuminate/Support/Once.php @@ -24,7 +24,6 @@ class Once * Create a new once instance. * * @param \WeakMap> $values - * @return void */ protected function __construct(protected WeakMap $values) { diff --git a/src/Illuminate/Support/Onceable.php b/src/Illuminate/Support/Onceable.php index 51a3aa021802..3b55d79227cc 100644 --- a/src/Illuminate/Support/Onceable.php +++ b/src/Illuminate/Support/Onceable.php @@ -13,7 +13,6 @@ class Onceable * @param string $hash * @param object|null $object * @param callable $callable - * @return void */ public function __construct( public string $hash, @@ -66,10 +65,14 @@ protected static function hashFromTrace(array $trace, callable $callable) $callable instanceof Closure ? (new ReflectionClosure($callable))->getClosureUsedVariables() : [], ); - return md5(sprintf( + $class = $callable instanceof Closure ? (new ReflectionClosure($callable))->getClosureCalledClass()?->getName() : null; + + $class ??= isset($trace[1]['class']) ? $trace[1]['class'] : null; + + return hash('xxh128', sprintf( '%s@%s%s:%s (%s)', $trace[0]['file'], - isset($trace[1]['class']) ? ($trace[1]['class'].'@') : '', + $class ? $class.'@' : '', $trace[1]['function'], $trace[0]['line'], serialize($uses), diff --git a/src/Illuminate/Support/Optional.php b/src/Illuminate/Support/Optional.php index ba84a2ccf231..fcf71ed0c4a4 100644 --- a/src/Illuminate/Support/Optional.php +++ b/src/Illuminate/Support/Optional.php @@ -23,7 +23,6 @@ class Optional implements ArrayAccess * Create a new optional instance. * * @param mixed $value - * @return void */ public function __construct($value) { diff --git a/src/Illuminate/Support/Process/PhpExecutableFinder.php b/src/Illuminate/Support/Process/PhpExecutableFinder.php deleted file mode 100644 index e1b4cff04697..000000000000 --- a/src/Illuminate/Support/Process/PhpExecutableFinder.php +++ /dev/null @@ -1,22 +0,0 @@ -find('php', false, [implode(DIRECTORY_SEPARATOR, [$herdPath, 'bin'])]); - } - - return parent::find($includeArgs); - } -} diff --git a/src/Illuminate/Support/Reflector.php b/src/Illuminate/Support/Reflector.php index a767d5ea7073..f5eb72f0fcdd 100644 --- a/src/Illuminate/Support/Reflector.php +++ b/src/Illuminate/Support/Reflector.php @@ -2,6 +2,7 @@ namespace Illuminate\Support; +use ReflectionAttribute; use ReflectionClass; use ReflectionEnum; use ReflectionMethod; @@ -56,6 +57,46 @@ public static function isCallable($var, $syntaxOnly = false) return false; } + /** + * Get the specified class attribute, optionally following an inheritance chain. + * + * @template TAttribute of object + * + * @param object|class-string $objectOrClass + * @param class-string $attribute + * @return TAttribute|null + */ + public static function getClassAttribute($objectOrClass, $attribute, $ascend = false) + { + return static::getClassAttributes($objectOrClass, $attribute, $ascend)->flatten()->first(); + } + + /** + * Get the specified class attribute(s), optionally following an inheritance chain. + * + * @template TTarget of object + * @template TAttribute of object + * + * @param TTarget|class-string $objectOrClass + * @param class-string $attribute + * @return ($includeParents is true ? Collection, Collection> : Collection) + */ + public static function getClassAttributes($objectOrClass, $attribute, $includeParents = false) + { + $reflectionClass = new ReflectionClass($objectOrClass); + + $attributes = []; + + do { + $attributes[$reflectionClass->name] = new Collection(array_map( + fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance(), + $reflectionClass->getAttributes($attribute) + )); + } while ($includeParents && false !== $reflectionClass = $reflectionClass->getParentClass()); + + return $includeParents ? new Collection($attributes) : reset($attributes); + } + /** * Get the class name of the given parameter's type, if possible. * diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index 3db60f9f5988..5c68c101313d 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -76,7 +76,6 @@ abstract class ServiceProvider * Create a new service provider instance. * * @param \Illuminate\Contracts\Foundation\Application $app - * @return void */ public function __construct($app) { diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index b1825957b6a3..9b761811cfb9 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -80,7 +80,6 @@ class Sleep * Create a new class instance. * * @param int|float|\DateInterval $duration - * @return void */ public function __construct($duration) { diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 5acd66529544..3bb47011d654 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -11,7 +11,9 @@ use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\MarkdownConverter; use Ramsey\Uuid\Codec\TimestampFirstCombCodec; +use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Generator\CombGenerator; +use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; use Symfony\Component\Uid\Ulid; @@ -452,7 +454,7 @@ public static function finish($value, $cap) */ public static function wrap($value, $before, $after = null) { - return $before.$value.($after ??= $before); + return $before.$value.($after ?? $before); } /** @@ -498,7 +500,7 @@ public static function is($pattern, $value, $ignoreCase = false) // If the given value is an exact match we can of course return true right // from the beginning. Otherwise, we will translate asterisks and do an // actual pattern match against the two strings to see if they match. - if ($pattern === $value) { + if ($pattern === '*' || $pattern === $value) { return true; } @@ -513,7 +515,7 @@ public static function is($pattern, $value, $ignoreCase = false) // pattern such as "library/*", making any string check convenient. $pattern = str_replace('\*', '.*', $pattern); - if (preg_match('#^'.$pattern.'\z#'.($ignoreCase ? 'iu' : 'u'), $value) === 1) { + if (preg_match('#^'.$pattern.'\z#'.($ignoreCase ? 'isu' : 'su'), $value) === 1) { return true; } } @@ -604,15 +606,42 @@ public static function isUrl($value, array $protocols = []) * Determine if a given value is a valid UUID. * * @param mixed $value + * @param int<0, 8>|'max'|null $version * @return bool */ - public static function isUuid($value) + public static function isUuid($value, $version = null) { if (! is_string($value)) { return false; } - return preg_match('/^[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}$/D', $value) > 0; + if ($version === null) { + return preg_match('/^[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}$/D', $value) > 0; + } + + $factory = new UuidFactory; + + try { + $factoryUuid = $factory->fromString($value); + } catch (InvalidUuidStringException) { + return false; + } + + $fields = $factoryUuid->getFields(); + + if (! ($fields instanceof FieldsInterface)) { + return false; + } + + if ($version === 0 || $version === 'nil') { + return $fields->isNil(); + } + + if ($version === 'max') { + return $fields->isMax(); + } + + return $fields->getVersion() === $version; } /** @@ -1022,8 +1051,10 @@ public static function password($length = 32, $letters = true, $numbers = true, ']', '|', ':', ';', ] : null, 'spaces' => $spaces === true ? [' '] : null, - ]))->filter()->each(fn ($c) => $password->push($c[random_int(0, count($c) - 1)]) - )->flatten(); + ])) + ->filter() + ->each(fn ($c) => $password->push($c[random_int(0, count($c) - 1)])) + ->flatten(); $length = $length - $password->count(); @@ -1203,8 +1234,8 @@ public static function replace($search, $replace, $subject, $caseSensitive = tru } return $caseSensitive - ? str_replace($search, $replace, $subject) - : str_ireplace($search, $replace, $subject); + ? str_replace($search, $replace, $subject) + : str_ireplace($search, $replace, $subject); } /** @@ -1336,8 +1367,8 @@ public static function remove($search, $subject, $caseSensitive = true) } return $caseSensitive - ? str_replace($search, '', $subject) - : str_ireplace($search, '', $subject); + ? str_replace($search, '', $subject) + : str_ireplace($search, '', $subject); } /** @@ -1398,8 +1429,8 @@ public static function headline($value) $parts = explode(' ', $value); $parts = count($parts) > 1 - ? array_map([static::class, 'title'], $parts) - : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts))); + ? array_map(static::title(...), $parts) + : array_map(static::title(...), static::ucsplit(implode('_', $parts))); $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts)); @@ -1817,8 +1848,8 @@ public static function wordWrap($string, $characters = 75, $break = "\n", $cutLo public static function uuid() { return static::$uuidFactory - ? call_user_func(static::$uuidFactory) - : Uuid::uuid4(); + ? call_user_func(static::$uuidFactory) + : Uuid::uuid4(); } /** @@ -1830,8 +1861,8 @@ public static function uuid() public static function uuid7($time = null) { return static::$uuidFactory - ? call_user_func(static::$uuidFactory) - : Uuid::uuid7($time); + ? call_user_func(static::$uuidFactory) + : Uuid::uuid7($time); } /** diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 797ffbccaeb1..b0f194fa6e0b 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -27,7 +27,6 @@ class Stringable implements JsonSerializable, ArrayAccess, BaseStringable * Create a new instance of the class. * * @param string $value - * @return void */ public function __construct($value = '') { diff --git a/src/Illuminate/Support/Testing/Fakes/BatchFake.php b/src/Illuminate/Support/Testing/Fakes/BatchFake.php index 57eb2013705b..0d1176c2b3ec 100644 --- a/src/Illuminate/Support/Testing/Fakes/BatchFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BatchFake.php @@ -37,7 +37,6 @@ class BatchFake extends Batch * @param \Carbon\CarbonImmutable $createdAt * @param \Carbon\CarbonImmutable|null $cancelledAt * @param \Carbon\CarbonImmutable|null $finishedAt - * @return void */ public function __construct( string $id, @@ -107,7 +106,7 @@ public function recordSuccessfulJob(string $jobId) * Decrement the pending jobs for the batch. * * @param string $jobId - * @return \Illuminate\Bus\UpdatedBatchJobCounts + * @return void */ public function decrementPendingJobs(string $jobId) { diff --git a/src/Illuminate/Support/Testing/Fakes/BusFake.php b/src/Illuminate/Support/Testing/Fakes/BusFake.php index 181fac01b60d..129e6c39eb55 100644 --- a/src/Illuminate/Support/Testing/Fakes/BusFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BusFake.php @@ -86,7 +86,6 @@ class BusFake implements Fake, QueueingDispatcher * @param \Illuminate\Contracts\Bus\QueueingDispatcher $dispatcher * @param array|string $jobsToFake * @param \Illuminate\Bus\BatchRepository|null $batchRepository - * @return void */ public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [], ?BatchRepository $batchRepository = null) { @@ -785,8 +784,8 @@ protected function shouldFakeJob($command) return (new Collection($this->jobsToFake)) ->filter(function ($job) use ($command) { return $job instanceof Closure - ? $job($command) - : $job === get_class($command); + ? $job($command) + : $job === get_class($command); })->isNotEmpty(); } diff --git a/src/Illuminate/Support/Testing/Fakes/ChainedBatchTruthTest.php b/src/Illuminate/Support/Testing/Fakes/ChainedBatchTruthTest.php index 4026ad2338b3..63a44e75e52a 100644 --- a/src/Illuminate/Support/Testing/Fakes/ChainedBatchTruthTest.php +++ b/src/Illuminate/Support/Testing/Fakes/ChainedBatchTruthTest.php @@ -17,7 +17,6 @@ class ChainedBatchTruthTest * Create a new truth test instance. * * @param \Closure $callback - * @return void */ public function __construct(Closure $callback) { diff --git a/src/Illuminate/Support/Testing/Fakes/EventFake.php b/src/Illuminate/Support/Testing/Fakes/EventFake.php index 7f226a786faf..cc30fd3a9474 100644 --- a/src/Illuminate/Support/Testing/Fakes/EventFake.php +++ b/src/Illuminate/Support/Testing/Fakes/EventFake.php @@ -51,7 +51,6 @@ class EventFake implements Dispatcher, Fake * * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher * @param array|string $eventsToFake - * @return void */ public function __construct(Dispatcher $dispatcher, $eventsToFake = []) { @@ -335,8 +334,8 @@ protected function shouldFakeEvent($eventName, $payload) return (new Collection($this->eventsToFake)) ->filter(function ($event) use ($eventName, $payload) { return $event instanceof Closure - ? $event($eventName, $payload) - : $event === $eventName; + ? $event($eventName, $payload) + : $event === $eventName; }) ->isNotEmpty(); } diff --git a/src/Illuminate/Support/Testing/Fakes/ExceptionHandlerFake.php b/src/Illuminate/Support/Testing/Fakes/ExceptionHandlerFake.php index f70af6df8751..7a187980bf05 100644 --- a/src/Illuminate/Support/Testing/Fakes/ExceptionHandlerFake.php +++ b/src/Illuminate/Support/Testing/Fakes/ExceptionHandlerFake.php @@ -39,7 +39,6 @@ class ExceptionHandlerFake implements ExceptionHandler, Fake * * @param \Illuminate\Contracts\Debug\ExceptionHandler $handler * @param list> $exceptions - * @return void */ public function __construct( protected ExceptionHandler $handler, @@ -73,7 +72,7 @@ public function assertReported(Closure|string $exception) if (is_string($exception)) { Assert::assertTrue( - in_array($exception, array_map('get_class', $this->reported), true), + in_array($exception, array_map(get_class(...), $this->reported), true), $message, ); @@ -135,7 +134,7 @@ public function assertNothingReported() $this->reported, sprintf( 'The following exceptions were reported: %s.', - implode(', ', array_map('get_class', $this->reported)), + implode(', ', array_map(get_class(...), $this->reported)), ), ); } diff --git a/src/Illuminate/Support/Testing/Fakes/MailFake.php b/src/Illuminate/Support/Testing/Fakes/MailFake.php index 7d4f16b334d0..ec00522c2dfb 100644 --- a/src/Illuminate/Support/Testing/Fakes/MailFake.php +++ b/src/Illuminate/Support/Testing/Fakes/MailFake.php @@ -51,7 +51,6 @@ class MailFake implements Factory, Fake, Mailer, MailQueue * Create a new mail fake. * * @param MailManager $manager - * @return void */ public function __construct(MailManager $manager) { diff --git a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php index bc3f16ce59e0..ecc4d63b1090 100644 --- a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php +++ b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php @@ -279,7 +279,7 @@ public function hasSent($notifiable, $notification) */ protected function notificationsFor($notifiable, $notification) { - return $this->notifications[get_class($notifiable)][$notifiable->getKey()][$notification] ?? []; + return $this->notifications[get_class($notifiable)][(string) $notifiable->getKey()][$notification] ?? []; } /** @@ -326,7 +326,7 @@ public function sendNow($notifiables, $notification, ?array $channels = null) continue; } - $this->notifications[get_class($notifiable)][$notifiable->getKey()][get_class($notification)][] = [ + $this->notifications[get_class($notifiable)][(string) $notifiable->getKey()][get_class($notification)][] = [ 'notification' => $this->serializeAndRestore && $notification instanceof ShouldQueue ? $this->serializeAndRestoreNotification($notification) : $notification, diff --git a/src/Illuminate/Support/Testing/Fakes/PendingBatchFake.php b/src/Illuminate/Support/Testing/Fakes/PendingBatchFake.php index 3d0f499291b1..6c06cf06d7bc 100644 --- a/src/Illuminate/Support/Testing/Fakes/PendingBatchFake.php +++ b/src/Illuminate/Support/Testing/Fakes/PendingBatchFake.php @@ -19,7 +19,6 @@ class PendingBatchFake extends PendingBatch * * @param \Illuminate\Support\Testing\Fakes\BusFake $bus * @param \Illuminate\Support\Collection $jobs - * @return void */ public function __construct(BusFake $bus, Collection $jobs) { diff --git a/src/Illuminate/Support/Testing/Fakes/PendingChainFake.php b/src/Illuminate/Support/Testing/Fakes/PendingChainFake.php index 533c6498b331..42d98c172ad3 100644 --- a/src/Illuminate/Support/Testing/Fakes/PendingChainFake.php +++ b/src/Illuminate/Support/Testing/Fakes/PendingChainFake.php @@ -21,7 +21,6 @@ class PendingChainFake extends PendingChain * @param \Illuminate\Support\Testing\Fakes\BusFake $bus * @param mixed $job * @param array $chain - * @return void */ public function __construct(BusFake $bus, $job, $chain) { diff --git a/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php b/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php index 37c797dce8ee..a3d64e73e14f 100644 --- a/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php +++ b/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php @@ -11,7 +11,6 @@ class PendingMailFake extends PendingMail * Create a new instance. * * @param \Illuminate\Support\Testing\Fakes\MailFake $mailer - * @return void */ public function __construct($mailer) { diff --git a/src/Illuminate/Support/Testing/Fakes/QueueFake.php b/src/Illuminate/Support/Testing/Fakes/QueueFake.php index 777266695f3c..c2fa139c5fb2 100644 --- a/src/Illuminate/Support/Testing/Fakes/QueueFake.php +++ b/src/Illuminate/Support/Testing/Fakes/QueueFake.php @@ -5,6 +5,7 @@ use BadMethodCallException; use Closure; use Illuminate\Contracts\Queue\Queue; +use Illuminate\Events\CallQueuedListener; use Illuminate\Queue\CallQueuedClosure; use Illuminate\Queue\QueueManager; use Illuminate\Support\Collection; @@ -66,7 +67,6 @@ class QueueFake extends QueueManager implements Fake, Queue * @param \Illuminate\Contracts\Foundation\Application $app * @param array $jobsToFake * @param \Illuminate\Queue\QueueManager|null $queue - * @return void */ public function __construct($app, $jobsToFake = [], $queue = null) { @@ -174,8 +174,8 @@ public function assertPushedWithChain($job, $expectedChain = [], $callback = nul ); $this->isChainOfObjects($expectedChain) - ? $this->assertPushedWithChainOfObjects($job, $expectedChain, $callback) - : $this->assertPushedWithChainOfClasses($job, $expectedChain, $callback); + ? $this->assertPushedWithChainOfObjects($job, $expectedChain, $callback) + : $this->assertPushedWithChainOfClasses($job, $expectedChain, $callback); } /** @@ -349,6 +349,29 @@ public function pushedRaw($callback = null) return (new Collection($this->rawPushes))->filter(fn ($data) => $callback($data['payload'], $data['queue'], $data['options'])); } + /** + * Get all of the jobs by listener class, passing an optional truth-test callback. + * + * @param class-string $listenerClass + * @param (\Closure(mixed, \Illuminate\Events\CallQueuedListener, string|null, mixed): bool)|null $callback + * @return \Illuminate\Support\Collection + */ + public function listenersPushed($listenerClass, $callback = null) + { + if (! $this->hasPushed(CallQueuedListener::class)) { + return new Collection; + } + + $collection = (new Collection($this->jobs[CallQueuedListener::class])) + ->filter(fn ($data) => $data['job']->class === $listenerClass); + + if ($callback) { + $collection = $collection->filter(fn ($data) => $callback($data['job']->data[0] ?? null, $data['job'], $data['queue'], $data['data'])); + } + + return $collection->pluck('job'); + } + /** * Determine if there are any stored jobs for a given class. * @@ -379,9 +402,10 @@ public function connection($value = null) */ public function size($queue = null) { - return (new Collection($this->jobs))->flatten(1)->filter( - fn ($job) => $job['queue'] === $queue - )->count(); + return (new Collection($this->jobs)) + ->flatten(1) + ->filter(fn ($job) => $job['queue'] === $queue) + ->count(); } /** diff --git a/src/Illuminate/Support/Traits/InteractsWithData.php b/src/Illuminate/Support/Traits/InteractsWithData.php index 722478ec0a0b..bd39205d942d 100644 --- a/src/Illuminate/Support/Traits/InteractsWithData.php +++ b/src/Illuminate/Support/Traits/InteractsWithData.php @@ -338,9 +338,10 @@ public function enums($key, $enumClass) return []; } - return $this->collect($key)->map(function ($value) use ($enumClass) { - return $enumClass::tryFrom($value); - })->filter()->all(); + return $this->collect($key) + ->map(fn ($value) => $enumClass::tryFrom($value)) + ->filter() + ->all(); } /** diff --git a/src/Illuminate/Support/Traits/ReflectsClosures.php b/src/Illuminate/Support/Traits/ReflectsClosures.php index 34f3ec805e89..9e12285a30a7 100644 --- a/src/Illuminate/Support/Traits/ReflectsClosures.php +++ b/src/Illuminate/Support/Traits/ReflectsClosures.php @@ -47,13 +47,17 @@ protected function firstClosureParameterTypes(Closure $closure) { $reflection = new ReflectionFunction($closure); - $types = (new Collection($reflection->getParameters()))->mapWithKeys(function ($parameter) { - if ($parameter->isVariadic()) { - return [$parameter->getName() => null]; - } + $types = (new Collection($reflection->getParameters())) + ->mapWithKeys(function ($parameter) { + if ($parameter->isVariadic()) { + return [$parameter->getName() => null]; + } - return [$parameter->getName() => Reflector::getParameterClassNames($parameter)]; - })->filter()->values()->all(); + return [$parameter->getName() => Reflector::getParameterClassNames($parameter)]; + }) + ->filter() + ->values() + ->all(); if (empty($types)) { throw new RuntimeException('The given Closure has no parameters.'); @@ -78,12 +82,14 @@ protected function closureParameterTypes(Closure $closure) { $reflection = new ReflectionFunction($closure); - return (new Collection($reflection->getParameters()))->mapWithKeys(function ($parameter) { - if ($parameter->isVariadic()) { - return [$parameter->getName() => null]; - } + return (new Collection($reflection->getParameters())) + ->mapWithKeys(function ($parameter) { + if ($parameter->isVariadic()) { + return [$parameter->getName() => null]; + } - return [$parameter->getName() => Reflector::getParameterClassName($parameter)]; - })->all(); + return [$parameter->getName() => Reflector::getParameterClassName($parameter)]; + }) + ->all(); } } diff --git a/src/Illuminate/Support/Uri.php b/src/Illuminate/Support/Uri.php index c25814d54220..0f2779c41ed5 100644 --- a/src/Illuminate/Support/Uri.php +++ b/src/Illuminate/Support/Uri.php @@ -9,6 +9,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Dumpable; +use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use League\Uri\Contracts\UriInterface; use League\Uri\Uri as LeagueUri; @@ -17,7 +18,7 @@ class Uri implements Htmlable, Responsable, Stringable { - use Conditionable, Dumpable, Tappable; + use Conditionable, Dumpable, Macroable, Tappable; /** * The URI instance. @@ -98,6 +99,21 @@ public static function temporarySignedRoute($name, $expiration, $parameters = [] return static::signedRoute($name, $parameters, $expiration, $absolute); } + /** + * Get a URI instance for a controller action. + * + * @param string|array $action + * @param mixed $parameters + * @param bool $absolute + * @return static + * + * @throws \InvalidArgumentException + */ + public static function action($action, $parameters = [], $absolute = true): static + { + return new static(call_user_func(static::$urlGeneratorResolver)->action($action, $parameters, $absolute)); + } + /** * Get the URI's scheme. */ @@ -152,6 +168,18 @@ public function path(): ?string return $path === '' ? '/' : $path; } + /** + * Get the URI's path segments. + * + * Empty or missing paths are returned as an empty collection. + */ + public function pathSegments(): Collection + { + $path = $this->path(); + + return $path === '/' ? new Collection : new Collection(explode('/', $path)); + } + /** * Get the URI's query string. */ @@ -195,7 +223,7 @@ public function withHost(Stringable|string $host): static /** * Specify the port of the URI. */ - public function withPort(int|null $port): static + public function withPort(?int $port): static { return new static($this->uri->withPort($port)); } @@ -235,7 +263,7 @@ public function withQuery(array $query, bool $merge = true): static } } - return new static($this->uri->withQuery(Arr::query($newQuery))); + return new static($this->uri->withQuery(Arr::query($newQuery) ?: null)); } /** diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index f3ac30c79e2a..1fa586ba10d1 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -23,7 +23,6 @@ class ValidatedInput implements ValidatedData * Create a new validated input container. * * @param array $input - * @return void */ public function __construct(array $input) { diff --git a/src/Illuminate/Support/ViewErrorBag.php b/src/Illuminate/Support/ViewErrorBag.php index 2865fbfa4bdd..3d95bded960b 100644 --- a/src/Illuminate/Support/ViewErrorBag.php +++ b/src/Illuminate/Support/ViewErrorBag.php @@ -14,7 +14,7 @@ class ViewErrorBag implements Countable, Stringable /** * The array of the view error bags. * - * @var array + * @var array */ protected $bags = []; @@ -43,7 +43,7 @@ public function getBag($key) /** * Get all the bags. * - * @return array + * @return array */ public function getBags() { diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 286a90b0a76e..4b53abef7e57 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -19,11 +19,11 @@ "ext-filter": "*", "ext-mbstring": "*", "doctrine/inflector": "^2.0", - "illuminate/collections": "^11.0", - "illuminate/conditionable": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "nesbot/carbon": "^3.8.4", "voku/portable-ascii": "^2.0.2" }, "conflict": { @@ -43,18 +43,18 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/filesystem": "Required to use the Composer class (^11.0).", + "illuminate/filesystem": "Required to use the Composer class (^12.0).", "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", "league/uri": "Required to use the Uri class (^7.5.1).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", - "symfony/process": "Required to use the Composer class (^7.0).", - "symfony/uid": "Required to use Str::ulid() (^7.0).", - "symfony/var-dumper": "Required to use the dd function (^7.0).", + "symfony/process": "Required to use the Composer class (^7.2).", + "symfony/uid": "Required to use Str::ulid() (^7.2).", + "symfony/var-dumper": "Required to use the dd function (^7.2).", "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." }, "config": { diff --git a/src/Illuminate/Support/functions.php b/src/Illuminate/Support/functions.php index 95d98c94bbe1..1da5ebcef263 100644 --- a/src/Illuminate/Support/functions.php +++ b/src/Illuminate/Support/functions.php @@ -4,7 +4,7 @@ use Illuminate\Support\Defer\DeferredCallback; use Illuminate\Support\Defer\DeferredCallbackCollection; -use Illuminate\Support\Process\PhpExecutableFinder; +use Symfony\Component\Process\PhpExecutableFinder; if (! function_exists('Illuminate\Support\defer')) { /** diff --git a/src/Illuminate/Testing/AssertableJsonString.php b/src/Illuminate/Testing/AssertableJsonString.php index b970edd3b482..d089ab071dc3 100644 --- a/src/Illuminate/Testing/AssertableJsonString.php +++ b/src/Illuminate/Testing/AssertableJsonString.php @@ -12,6 +12,8 @@ use Illuminate\Testing\Assert as PHPUnit; use JsonSerializable; +use function Illuminate\Support\enum_value; + class AssertableJsonString implements ArrayAccess, Countable { /** @@ -32,7 +34,6 @@ class AssertableJsonString implements ArrayAccess, Countable * Create a new assertable JSON string instance. * * @param \Illuminate\Contracts\Support\Jsonable|\JsonSerializable|array|string $jsonable - * @return void */ public function __construct($jsonable) { @@ -239,7 +240,7 @@ public function assertPath($path, $expect) if ($expect instanceof Closure) { PHPUnit::assertTrue($expect($this->json($path))); } else { - PHPUnit::assertSame($expect, $this->json($path)); + PHPUnit::assertSame(enum_value($expect), $this->json($path)); } return $this; diff --git a/src/Illuminate/Testing/Concerns/RunsInParallel.php b/src/Illuminate/Testing/Concerns/RunsInParallel.php index 18380e588ab1..468bba8e0c48 100644 --- a/src/Illuminate/Testing/Concerns/RunsInParallel.php +++ b/src/Illuminate/Testing/Concerns/RunsInParallel.php @@ -54,7 +54,6 @@ trait RunsInParallel * * @param \ParaTest\Runners\PHPUnit\Options|\ParaTest\Options $options * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return void */ public function __construct($options, OutputInterface $output) { diff --git a/src/Illuminate/Testing/Constraints/ArraySubset.php b/src/Illuminate/Testing/Constraints/ArraySubset.php index b77088a3b8ac..ccc20beb7f56 100644 --- a/src/Illuminate/Testing/Constraints/ArraySubset.php +++ b/src/Illuminate/Testing/Constraints/ArraySubset.php @@ -25,7 +25,6 @@ class ArraySubset extends Constraint * * @param iterable $subset * @param bool $strict - * @return void */ public function __construct(iterable $subset, bool $strict = false) { diff --git a/src/Illuminate/Testing/Constraints/CountInDatabase.php b/src/Illuminate/Testing/Constraints/CountInDatabase.php index 3ed6826929a4..3ba260790ffb 100644 --- a/src/Illuminate/Testing/Constraints/CountInDatabase.php +++ b/src/Illuminate/Testing/Constraints/CountInDatabase.php @@ -34,7 +34,6 @@ class CountInDatabase extends Constraint * * @param \Illuminate\Database\Connection $database * @param int $expectedCount - * @return void */ public function __construct(Connection $database, int $expectedCount) { diff --git a/src/Illuminate/Testing/Constraints/HasInDatabase.php b/src/Illuminate/Testing/Constraints/HasInDatabase.php index 3a30b8b07172..cb885b133c20 100644 --- a/src/Illuminate/Testing/Constraints/HasInDatabase.php +++ b/src/Illuminate/Testing/Constraints/HasInDatabase.php @@ -34,7 +34,6 @@ class HasInDatabase extends Constraint * * @param \Illuminate\Database\Connection $database * @param array $data - * @return void */ public function __construct(Connection $database, array $data) { diff --git a/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php b/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php index 665a50588caf..3eebc12f1a85 100644 --- a/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php +++ b/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php @@ -41,7 +41,6 @@ class NotSoftDeletedInDatabase extends Constraint * @param \Illuminate\Database\Connection $database * @param array $data * @param string $deletedAtColumn - * @return void */ public function __construct(Connection $database, array $data, string $deletedAtColumn) { diff --git a/src/Illuminate/Testing/Constraints/SeeInOrder.php b/src/Illuminate/Testing/Constraints/SeeInOrder.php index aba5c6bdac4c..08d30ab82fe6 100644 --- a/src/Illuminate/Testing/Constraints/SeeInOrder.php +++ b/src/Illuminate/Testing/Constraints/SeeInOrder.php @@ -25,7 +25,6 @@ class SeeInOrder extends Constraint * Create a new constraint instance. * * @param string $content - * @return void */ public function __construct($content) { diff --git a/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php b/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php index c764d5f39c4e..0e78f34bc319 100644 --- a/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php +++ b/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php @@ -41,7 +41,6 @@ class SoftDeletedInDatabase extends Constraint * @param \Illuminate\Database\Connection $database * @param array $data * @param string $deletedAtColumn - * @return void */ public function __construct(Connection $database, array $data, string $deletedAtColumn) { diff --git a/src/Illuminate/Testing/Fluent/AssertableJson.php b/src/Illuminate/Testing/Fluent/AssertableJson.php index 9afc94c7fd36..0b57db0f54b5 100644 --- a/src/Illuminate/Testing/Fluent/AssertableJson.php +++ b/src/Illuminate/Testing/Fluent/AssertableJson.php @@ -40,7 +40,6 @@ class AssertableJson implements Arrayable * * @param array $props * @param string|null $path - * @return void */ protected function __construct(array $props, ?string $path = null) { diff --git a/src/Illuminate/Testing/Fluent/Concerns/Matching.php b/src/Illuminate/Testing/Fluent/Concerns/Matching.php index 3e3d940d9651..76c6fcba5750 100644 --- a/src/Illuminate/Testing/Fluent/Concerns/Matching.php +++ b/src/Illuminate/Testing/Fluent/Concerns/Matching.php @@ -92,6 +92,52 @@ public function whereNot(string $key, $expected): self return $this; } + /** + * Asserts that the property is null. + * + * @param string $key + * @return $this + */ + public function whereNull(string $key): self + { + $this->has($key); + + $actual = $this->prop($key); + + PHPUnit::assertNull( + $actual, + sprintf( + 'Property [%s] should be null.', + $this->dotPath($key), + ) + ); + + return $this; + } + + /** + * Asserts that the property is not null. + * + * @param string $key + * @return $this + */ + public function whereNotNull(string $key): self + { + $this->has($key); + + $actual = $this->prop($key); + + PHPUnit::assertNotNull( + $actual, + sprintf( + 'Property [%s] should not be null.', + $this->dotPath($key), + ) + ); + + return $this; + } + /** * Asserts that all properties match their expected values. * diff --git a/src/Illuminate/Testing/ParallelConsoleOutput.php b/src/Illuminate/Testing/ParallelConsoleOutput.php index c4927cfdce34..6dd351e6d26a 100644 --- a/src/Illuminate/Testing/ParallelConsoleOutput.php +++ b/src/Illuminate/Testing/ParallelConsoleOutput.php @@ -29,7 +29,6 @@ class ParallelConsoleOutput extends ConsoleOutput * Create a new Parallel ConsoleOutput instance. * * @param \Symfony\Component\Console\Output\OutputInterface $output - * @return void */ public function __construct($output) { diff --git a/src/Illuminate/Testing/ParallelTesting.php b/src/Illuminate/Testing/ParallelTesting.php index e633bc57192f..736aea91a880 100644 --- a/src/Illuminate/Testing/ParallelTesting.php +++ b/src/Illuminate/Testing/ParallelTesting.php @@ -67,7 +67,6 @@ class ParallelTesting * Create a new parallel testing instance. * * @param \Illuminate\Contracts\Container\Container $container - * @return void */ public function __construct(Container $container) { diff --git a/src/Illuminate/Testing/PendingCommand.php b/src/Illuminate/Testing/PendingCommand.php index 0946161d4caf..d1ed6f3382bd 100644 --- a/src/Illuminate/Testing/PendingCommand.php +++ b/src/Illuminate/Testing/PendingCommand.php @@ -10,6 +10,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Tappable; use Mockery; use Mockery\Exception\NoMatchingExpectationException; use PHPUnit\Framework\TestCase as PHPUnitTestCase; @@ -21,8 +22,7 @@ class PendingCommand { - use Conditionable; - use Macroable; + use Conditionable, Macroable, Tappable; /** * The test being run. @@ -80,7 +80,6 @@ class PendingCommand * @param \Illuminate\Contracts\Container\Container $app * @param string $command * @param array $parameters - * @return void */ public function __construct(PHPUnitTestCase $test, Container $app, $command, $parameters) { diff --git a/src/Illuminate/Testing/TestComponent.php b/src/Illuminate/Testing/TestComponent.php index 31b3831a316e..c0bebbc96529 100644 --- a/src/Illuminate/Testing/TestComponent.php +++ b/src/Illuminate/Testing/TestComponent.php @@ -32,7 +32,6 @@ class TestComponent implements Stringable * * @param \Illuminate\View\Component $component * @param \Illuminate\View\View $view - * @return void */ public function __construct($component, $view) { @@ -66,7 +65,7 @@ public function assertSee($value, $escape = true) */ public function assertSeeInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::assertThat($values, new SeeInOrder($this->rendered)); @@ -98,7 +97,7 @@ public function assertSeeText($value, $escape = true) */ public function assertSeeTextInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->rendered))); diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 4851b175c885..3f6f59a36730 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -71,7 +71,6 @@ class TestResponse implements ArrayAccess * * @param TResponse $response * @param \Illuminate\Http\Request|null $request - * @return void */ public function __construct($response, $request = null) { @@ -597,7 +596,7 @@ public function assertSee($value, $escape = true) { $value = Arr::wrap($value); - $values = $escape ? array_map('e', $value) : $value; + $values = $escape ? array_map(e(...), $value) : $value; foreach ($values as $value) { PHPUnit::withResponse($this)->assertStringContainsString((string) $value, $this->getContent()); @@ -626,7 +625,7 @@ public function assertSeeHtml($value) */ public function assertSeeInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::withResponse($this)->assertThat($values, new SeeInOrder($this->getContent())); @@ -655,7 +654,7 @@ public function assertSeeText($value, $escape = true) { $value = Arr::wrap($value); - $values = $escape ? array_map('e', $value) : $value; + $values = $escape ? array_map(e(...), $value) : $value; $content = strip_tags($this->getContent()); @@ -675,7 +674,7 @@ public function assertSeeText($value, $escape = true) */ public function assertSeeTextInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::withResponse($this)->assertThat($values, new SeeInOrder(strip_tags($this->getContent()))); @@ -693,7 +692,7 @@ public function assertDontSee($value, $escape = true) { $value = Arr::wrap($value); - $values = $escape ? array_map('e', $value) : $value; + $values = $escape ? array_map(e(...), $value) : $value; foreach ($values as $value) { PHPUnit::withResponse($this)->assertStringNotContainsString((string) $value, $this->getContent()); @@ -724,7 +723,7 @@ public function assertDontSeeText($value, $escape = true) { $value = Arr::wrap($value); - $values = $escape ? array_map('e', $value) : $value; + $values = $escape ? array_map(e(...), $value) : $value; $content = strip_tags($this->getContent()); @@ -941,9 +940,9 @@ public function assertJsonValidationErrors($errors, $responseKey = 'errors') $jsonErrors = Arr::get($this->json(), $responseKey) ?? []; $errorMessage = $jsonErrors - ? 'Response has the following JSON validation errors:'. - PHP_EOL.PHP_EOL.json_encode($jsonErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL - : 'Response does not have JSON validation errors.'; + ? 'Response has the following JSON validation errors:'. + PHP_EOL.PHP_EOL.json_encode($jsonErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL + : 'Response does not have JSON validation errors.'; foreach ($errors as $key => $value) { if (is_int($key)) { @@ -989,11 +988,18 @@ public function assertOnlyJsonValidationErrors($errors, $responseKey = 'errors') $jsonErrors = Arr::get($this->json(), $responseKey) ?? []; - $expectedErrorKeys = collect($errors)->map(fn ($value, $key) => is_int($key) ? $value : $key)->all(); + $expectedErrorKeys = (new Collection($errors)) + ->map(fn ($value, $key) => is_int($key) ? $value : $key) + ->all(); $unexpectedErrorKeys = Arr::except($jsonErrors, $expectedErrorKeys); - PHPUnit::withResponse($this)->assertTrue(count($unexpectedErrorKeys) === 0, 'Response has unexpected validation errors: '.collect($unexpectedErrorKeys)->keys()->map(fn ($key) => "'{$key}'")->join(', ')); + PHPUnit::withResponse($this)->assertTrue( + count($unexpectedErrorKeys) === 0, + 'Response has unexpected validation errors: '.(new Collection($unexpectedErrorKeys))->keys()->map(fn ($key) => "'{$key}'")->join(', ') + ); + + return $this; } /** @@ -1186,21 +1192,21 @@ public function assertViewHas($key, $value = null) $this->ensureResponseHasView(); + $actual = Arr::get($this->original->gatherData(), $key); + if (is_null($value)) { - PHPUnit::withResponse($this)->assertTrue(Arr::has($this->original->gatherData(), $key)); + PHPUnit::withResponse($this)->assertTrue(Arr::has($this->original->gatherData(), $key), "Failed asserting that the data contains the key [{$key}]."); } elseif ($value instanceof Closure) { - PHPUnit::withResponse($this)->assertTrue($value(Arr::get($this->original->gatherData(), $key))); + PHPUnit::withResponse($this)->assertTrue($value($actual), "Failed asserting that the value at [{$key}] fulfills the expectations defined by the closure."); } elseif ($value instanceof Model) { - PHPUnit::withResponse($this)->assertTrue($value->is(Arr::get($this->original->gatherData(), $key))); + PHPUnit::withResponse($this)->assertTrue($value->is($actual), "Failed asserting that the model at [{$key}] matches the given model."); } elseif ($value instanceof EloquentCollection) { - $actual = Arr::get($this->original->gatherData(), $key); - PHPUnit::withResponse($this)->assertInstanceOf(EloquentCollection::class, $actual); PHPUnit::withResponse($this)->assertSameSize($value, $actual); - $value->each(fn ($item, $index) => PHPUnit::withResponse($this)->assertTrue($actual->get($index)->is($item))); + $value->each(fn ($item, $index) => PHPUnit::withResponse($this)->assertTrue($actual->get($index)->is($item), "Failed asserting that the collection at [{$key}.[{$index}]]' matches the given collection.")); } else { - PHPUnit::withResponse($this)->assertEquals($value, Arr::get($this->original->gatherData(), $key)); + PHPUnit::withResponse($this)->assertEquals($value, $actual, "Failed asserting that [{$key}] matches the expected value."); } return $this; @@ -1341,9 +1347,9 @@ public function assertInvalid($errors = null, $sessionErrors = $this->session()->get('errors')->getBag($errorBag)->getMessages(); $errorMessage = $sessionErrors - ? 'Response has the following validation errors in the session:'. - PHP_EOL.PHP_EOL.json_encode($sessionErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL - : 'Response does not have validation errors in the session.'; + ? 'Response has the following validation errors in the session:'. + PHP_EOL.PHP_EOL.json_encode($sessionErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL + : 'Response does not have validation errors in the session.'; foreach (Arr::wrap($errors) as $key => $value) { PHPUnit::withResponse($this)->assertArrayHasKey( @@ -1396,15 +1402,18 @@ public function assertOnlyInvalid($errors = null, $errorBag = 'default', $respon ->getBag($errorBag) ->getMessages(); - $expectedErrorKeys = collect($errors) - ->map(fn ($value, $key) => is_int($key) ? $value : $key)->all(); + $expectedErrorKeys = (new Collection($errors)) + ->map(fn ($value, $key) => is_int($key) ? $value : $key) + ->all(); $unexpectedErrorKeys = Arr::except($sessionErrors, $expectedErrorKeys); PHPUnit::withResponse($this)->assertTrue( count($unexpectedErrorKeys) === 0, - 'Response has unexpected validation errors: '.collect($unexpectedErrorKeys)->keys()->map(fn ($key) => "'{$key}'")->join(', ') + 'Response has unexpected validation errors: '.(new Collection($unexpectedErrorKeys))->keys()->map(fn ($key) => "'{$key}'")->join(', ') ); + + return $this; } /** @@ -1645,6 +1654,23 @@ public function ddHeaders() exit(1); } + /** + * Dump the body of the response and end the script. + * + * @param string|null $key + * @return never + */ + public function ddBody($key = null) + { + $content = $this->content(); + + if (function_exists('json_validate') && json_validate($content)) { + $this->ddJson($key); + } + + dd($content); + } + /** * Dump the JSON payload from the response and end the script. * @@ -1797,8 +1823,8 @@ public function __isset($key) public function offsetExists($offset): bool { return $this->responseHasView() - ? isset($this->original->gatherData()[$offset]) - : isset($this->json()[$offset]); + ? isset($this->original->gatherData()[$offset]) + : isset($this->json()[$offset]); } /** @@ -1810,8 +1836,8 @@ public function offsetExists($offset): bool public function offsetGet($offset): mixed { return $this->responseHasView() - ? $this->viewData($offset) - : $this->json()[$offset]; + ? $this->viewData($offset) + : $this->json()[$offset]; } /** diff --git a/src/Illuminate/Testing/TestView.php b/src/Illuminate/Testing/TestView.php index a31a814ab402..694d1dc7ce23 100644 --- a/src/Illuminate/Testing/TestView.php +++ b/src/Illuminate/Testing/TestView.php @@ -34,7 +34,6 @@ class TestView implements Stringable * Create a new test view instance. * * @param \Illuminate\View\View $view - * @return void */ public function __construct(View $view) { @@ -144,7 +143,7 @@ public function assertSee($value, $escape = true) */ public function assertSeeInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::assertThat($values, new SeeInOrder($this->rendered)); @@ -176,7 +175,7 @@ public function assertSeeText($value, $escape = true) */ public function assertSeeTextInOrder(array $values, $escape = true) { - $values = $escape ? array_map('e', $values) : $values; + $values = $escape ? array_map(e(...), $values) : $values; PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->rendered))); diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index f8b01f1c56b2..0f4286aba64c 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -16,10 +16,10 @@ "require": { "php": "^8.2", "ext-mbstring": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -28,16 +28,16 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { "brianium/paratest": "Required to run tests in parallel (^7.0|^8.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).", + "illuminate/console": "Required to assert console commands (^12.0).", + "illuminate/database": "Required to assert databases (^12.0).", + "illuminate/http": "Required to assert responses (^12.0).", "mockery/mockery": "Required to use mocking (^1.6).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1)." + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Translation/FileLoader.php b/src/Illuminate/Translation/FileLoader.php index 2723491e07a8..e30fdf7c413e 100755 --- a/src/Illuminate/Translation/FileLoader.php +++ b/src/Illuminate/Translation/FileLoader.php @@ -42,7 +42,6 @@ class FileLoader implements Loader * * @param \Illuminate\Filesystem\Filesystem $files * @param array|string $path - * @return void */ public function __construct(Filesystem $files, array|string $path) { @@ -103,15 +102,15 @@ protected function loadNamespaced($locale, $group, $namespace) protected function loadNamespaceOverrides(array $lines, $locale, $group, $namespace) { return (new Collection($this->paths)) - ->reduce(function ($output, $path) use ($lines, $locale, $group, $namespace) { + ->reduce(function ($output, $path) use ($locale, $group, $namespace) { $file = "{$path}/vendor/{$namespace}/{$locale}/{$group}.php"; if ($this->files->exists($file)) { - $lines = array_replace_recursive($lines, $this->files->getRequire($file)); + $output = array_replace_recursive($output, $this->files->getRequire($file)); } - return $lines; - }, []); + return $output; + }, $lines); } /** diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php index a24f1d6f542f..6296bff9b84a 100755 --- a/src/Illuminate/Translation/Translator.php +++ b/src/Illuminate/Translation/Translator.php @@ -84,7 +84,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract * * @param \Illuminate\Contracts\Translation\Loader $loader * @param string $locale - * @return void */ public function __construct(Loader $loader, $locale) { diff --git a/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index 539a651b2696..1b0dfa7a5d4b 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -15,11 +15,11 @@ ], "require": { "php": "^8.2", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/filesystem": "^11.0", - "illuminate/support": "^11.0" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/src/Illuminate/Translation/lang/en/validation.php b/src/Illuminate/Translation/lang/en/validation.php index f19bd64ed6c9..a57a95ed9858 100644 --- a/src/Illuminate/Translation/lang/en/validation.php +++ b/src/Illuminate/Translation/lang/en/validation.php @@ -21,6 +21,7 @@ 'alpha' => 'The :attribute field must only contain letters.', 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'any_of' => 'The :attribute field is invalid.', 'array' => 'The :attribute field must be an array.', 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', 'before' => 'The :attribute field must be a date before :date.', diff --git a/src/Illuminate/Validation/ClosureValidationRule.php b/src/Illuminate/Validation/ClosureValidationRule.php index 0ce67698aede..29ee46884cb5 100644 --- a/src/Illuminate/Validation/ClosureValidationRule.php +++ b/src/Illuminate/Validation/ClosureValidationRule.php @@ -42,7 +42,6 @@ class ClosureValidationRule implements RuleContract, ValidatorAwareRule * Create a new Closure based validation rule. * * @param \Closure $callback - * @return void */ public function __construct($callback) { diff --git a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php index 84ed212f2755..50acbcf1311a 100644 --- a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php +++ b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php @@ -19,7 +19,6 @@ class FilterEmailValidation implements EmailValidation * Create a new validation instance. * * @param int $flags - * @return void */ public function __construct($flags = null) { @@ -46,8 +45,8 @@ public static function unicode() public function isValid(string $email, EmailLexer $emailLexer): bool { return is_null($this->flags) - ? filter_var($email, FILTER_VALIDATE_EMAIL) !== false - : filter_var($email, FILTER_VALIDATE_EMAIL, $this->flags) !== false; + ? filter_var($email, FILTER_VALIDATE_EMAIL) !== false + : filter_var($email, FILTER_VALIDATE_EMAIL, $this->flags) !== false; } /** diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index ad1352aa767b..5e36ad881920 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -84,8 +84,8 @@ protected function getInlineMessage($attribute, $rule) $inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule)); return is_array($inlineEntry) && in_array($rule, $this->sizeRules) - ? $inlineEntry[$this->getAttributeType($attribute)] - : $inlineEntry; + ? $inlineEntry[$this->getAttributeType($attribute)] + : $inlineEntry; } /** @@ -267,7 +267,8 @@ public function getDisplayableAttribute($attribute) $primaryAttribute = $this->getPrimaryAttribute($attribute); $expectedAttributes = $attribute != $primaryAttribute - ? [$attribute, $primaryAttribute] : [$attribute]; + ? [$attribute, $primaryAttribute] + : [$attribute]; foreach ($expectedAttributes as $name) { // The developer may dynamically specify the array of custom attributes on this @@ -290,8 +291,8 @@ public function getDisplayableAttribute($attribute) // modify it with any of these replacements before we display the name. if (isset($this->implicitAttributes[$primaryAttribute])) { return ($formatter = $this->implicitAttributesFormatter) - ? $formatter($attribute) - : $attribute; + ? $formatter($attribute) + : $attribute; } return str_replace('_', ' ', Str::snake($attribute)); diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index a2dfb054a7f6..21577ce4eb87 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -731,8 +731,8 @@ public function validateDimensions($attribute, $value, $parameters) } $dimensions = method_exists($value, 'dimensions') - ? $value->dimensions() - : @getimagesize($value->getRealPath()); + ? $value->dimensions() + : @getimagesize($value->getRealPath()); if (! $dimensions) { return false; @@ -980,8 +980,8 @@ protected function getExistCount($connection, $table, $column, $value, $paramete } return is_array($value) - ? $verifier->getMultiCount($table, $column, $value, $extra) - : $verifier->getCount($table, $column, $value, null, null, $extra); + ? $verifier->getMultiCount($table, $column, $value, $extra) + : $verifier->getCount($table, $column, $value, null, null, $extra); } /** @@ -1119,7 +1119,8 @@ public function parseTable($table) public function getQueryColumn($parameters, $attribute) { return isset($parameters[1]) && $parameters[1] !== 'NULL' - ? $parameters[1] : $this->guessColumnForQuery($attribute); + ? $parameters[1] + : $this->guessColumnForQuery($attribute); } /** @@ -1389,11 +1390,18 @@ public function validateHexColor($attribute, $value) * * @param string $attribute * @param mixed $value + * @param array $parameters * @return bool */ - public function validateImage($attribute, $value) + public function validateImage($attribute, $value, $parameters = []) { - return $this->validateMimes($attribute, $value, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']); + $mimes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + + if (is_array($parameters) && in_array('allow_svg', $parameters)) { + $mimes[] = 'svg'; + } + + return $this->validateMimes($attribute, $value, $mimes); } /** @@ -1630,8 +1638,8 @@ protected function shouldBlockPhpUpload($value, $parameters) ]; return ($value instanceof UploadedFile) - ? in_array(trim(strtolower($value->getClientOriginalExtension())), $phpExtensions) - : in_array(trim(strtolower($value->getExtension())), $phpExtensions); + ? in_array(trim(strtolower($value->getClientOriginalExtension())), $phpExtensions) + : in_array(trim(strtolower($value->getExtension())), $phpExtensions); } /** @@ -2591,11 +2599,22 @@ public function validateUlid($attribute, $value) * * @param string $attribute * @param mixed $value + * @param array|'max'> $parameters * @return bool */ - public function validateUuid($attribute, $value) + public function validateUuid($attribute, $value, $parameters) { - return Str::isUuid($value); + $version = null; + + if ($parameters !== null && count($parameters) === 1) { + $version = $parameters[0]; + + if ($version !== 'max') { + $version = (int) $parameters[0]; + } + } + + return Str::isUuid($value, $version); } /** diff --git a/src/Illuminate/Validation/ConditionalRules.php b/src/Illuminate/Validation/ConditionalRules.php index fa6022209672..d78d3e5da1b2 100644 --- a/src/Illuminate/Validation/ConditionalRules.php +++ b/src/Illuminate/Validation/ConditionalRules.php @@ -33,7 +33,6 @@ class ConditionalRules * @param callable|bool $condition * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $rules * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $defaultRules - * @return void */ public function __construct($condition, $rules, $defaultRules = []) { @@ -51,8 +50,8 @@ public function __construct($condition, $rules, $defaultRules = []) public function passes(array $data = []) { return is_callable($this->condition) - ? call_user_func($this->condition, new Fluent($data)) - : $this->condition; + ? call_user_func($this->condition, new Fluent($data)) + : $this->condition; } /** @@ -64,8 +63,8 @@ public function passes(array $data = []) public function rules(array $data = []) { return is_string($this->rules) - ? explode('|', $this->rules) - : value($this->rules, new Fluent($data)); + ? explode('|', $this->rules) + : value($this->rules, new Fluent($data)); } /** @@ -77,7 +76,7 @@ public function rules(array $data = []) public function defaultRules(array $data = []) { return is_string($this->defaultRules) - ? explode('|', $this->defaultRules) - : value($this->defaultRules, new Fluent($data)); + ? explode('|', $this->defaultRules) + : value($this->defaultRules, new Fluent($data)); } } diff --git a/src/Illuminate/Validation/DatabasePresenceVerifier.php b/src/Illuminate/Validation/DatabasePresenceVerifier.php index 9229f06b708a..46601a35e872 100755 --- a/src/Illuminate/Validation/DatabasePresenceVerifier.php +++ b/src/Illuminate/Validation/DatabasePresenceVerifier.php @@ -25,7 +25,6 @@ class DatabasePresenceVerifier implements DatabasePresenceVerifierInterface * Create a new database presence verifier. * * @param \Illuminate\Database\ConnectionResolverInterface $db - * @return void */ public function __construct(ConnectionResolverInterface $db) { diff --git a/src/Illuminate/Validation/Factory.php b/src/Illuminate/Validation/Factory.php index 6ebfcac50d2d..8cf5027eb26f 100755 --- a/src/Illuminate/Validation/Factory.php +++ b/src/Illuminate/Validation/Factory.php @@ -85,7 +85,6 @@ class Factory implements FactoryContract * * @param \Illuminate\Contracts\Translation\Translator $translator * @param \Illuminate\Contracts\Container\Container|null $container - * @return void */ public function __construct(Translator $translator, ?Container $container = null) { diff --git a/src/Illuminate/Validation/InvokableValidationRule.php b/src/Illuminate/Validation/InvokableValidationRule.php index 49451dfbbcd9..dbd60a0173ad 100644 --- a/src/Illuminate/Validation/InvokableValidationRule.php +++ b/src/Illuminate/Validation/InvokableValidationRule.php @@ -53,7 +53,6 @@ class InvokableValidationRule implements Rule, ValidatorAwareRule * Create a new explicit Invokable validation rule. * * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule $invokable - * @return void */ protected function __construct(ValidationRule|InvokableRule $invokable) { @@ -96,8 +95,8 @@ public function passes($attribute, $value) } $method = $this->invokable instanceof ValidationRule - ? 'validate' - : '__invoke'; + ? 'validate' + : '__invoke'; $this->invokable->{$method}($attribute, $value, function ($attribute, $message = null) { $this->failed = true; diff --git a/src/Illuminate/Validation/NestedRules.php b/src/Illuminate/Validation/NestedRules.php index cae1302f0696..c5abe6680de2 100644 --- a/src/Illuminate/Validation/NestedRules.php +++ b/src/Illuminate/Validation/NestedRules.php @@ -2,9 +2,9 @@ namespace Illuminate\Validation; -use Illuminate\Support\Arr; +use Illuminate\Contracts\Validation\CompilableRules; -class NestedRules +class NestedRules implements CompilableRules { /** * The callback to execute. @@ -17,7 +17,6 @@ class NestedRules * Create a new nested rule instance. * * @param callable $callback - * @return void */ public function __construct(callable $callback) { @@ -37,22 +36,6 @@ public function compile($attribute, $value, $data = null, $context = null) { $rules = call_user_func($this->callback, $value, $attribute, $data, $context); - $parser = new ValidationRuleParser( - Arr::undot(Arr::wrap($data)) - ); - - if (is_array($rules) && ! array_is_list($rules)) { - $nested = []; - - foreach ($rules as $key => $rule) { - $nested[$attribute.'.'.$key] = $rule; - } - - $rules = $nested; - } else { - $rules = [$attribute => $rules]; - } - - return $parser->explode(ValidationRuleParser::filterConditionalRules($rules, $data)); + return Rule::compile($attribute, $rules, $data); } } diff --git a/src/Illuminate/Validation/NotPwnedVerifier.php b/src/Illuminate/Validation/NotPwnedVerifier.php index 50ab2954db40..a6dbaa3c7de0 100644 --- a/src/Illuminate/Validation/NotPwnedVerifier.php +++ b/src/Illuminate/Validation/NotPwnedVerifier.php @@ -27,7 +27,6 @@ class NotPwnedVerifier implements UncompromisedVerifier * * @param \Illuminate\Http\Client\Factory $factory * @param int|null $timeout - * @return void */ public function __construct($factory, $timeout = null) { diff --git a/src/Illuminate/Validation/Rule.php b/src/Illuminate/Validation/Rule.php index c15772bba7d5..170f4d04a1ea 100644 --- a/src/Illuminate/Validation/Rule.php +++ b/src/Illuminate/Validation/Rule.php @@ -3,7 +3,9 @@ namespace Illuminate\Validation; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Support\Arr; use Illuminate\Support\Traits\Macroable; +use Illuminate\Validation\Rules\AnyOf; use Illuminate\Validation\Rules\ArrayRule; use Illuminate\Validation\Rules\Can; use Illuminate\Validation\Rules\Date; @@ -216,11 +218,12 @@ public static function file() /** * Get an image file rule builder instance. * + * @param bool $allowSvg * @return \Illuminate\Validation\Rules\ImageFile */ - public static function imageFile() + public static function imageFile($allowSvg = false) { - return new ImageFile; + return new ImageFile($allowSvg); } /** @@ -243,4 +246,46 @@ public static function numeric() { return new Numeric; } + + /** + * Get an "any of" rule builder instance. + * + * @param array + * @return \Illuminate\Validation\Rules\AnyOf + * + * @throws \InvalidArgumentException + */ + public static function anyOf($rules) + { + return new AnyOf($rules); + } + + /** + * Compile a set of rules for an attribute. + * + * @param string $attribute + * @param array $rules + * @param array|null $data + * @return object|\stdClass + */ + public static function compile($attribute, $rules, $data = null) + { + $parser = new ValidationRuleParser( + Arr::undot(Arr::wrap($data)) + ); + + if (is_array($rules) && ! array_is_list($rules)) { + $nested = []; + + foreach ($rules as $key => $rule) { + $nested[$attribute.'.'.$key] = $rule; + } + + $rules = $nested; + } else { + $rules = [$attribute => $rules]; + } + + return $parser->explode(ValidationRuleParser::filterConditionalRules($rules, $data)); + } } diff --git a/src/Illuminate/Validation/Rules/AnyOf.php b/src/Illuminate/Validation/Rules/AnyOf.php new file mode 100644 index 000000000000..a27b20c98100 --- /dev/null +++ b/src/Illuminate/Validation/Rules/AnyOf.php @@ -0,0 +1,94 @@ +rules = $rules; + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value) + { + foreach ($this->rules as $rule) { + $validator = Validator::make( + Arr::isAssoc(Arr::wrap($value)) ? $value : [$value], + Arr::isAssoc(Arr::wrap($rule)) ? $rule : [$rule], + $this->validator->customMessages, + $this->validator->customAttributes + ); + + if ($validator->passes()) { + return true; + } + } + + return false; + } + + /** + * Get the validation error messages. + * + * @return array + */ + public function message() + { + $message = $this->validator->getTranslator()->get('validation.any_of'); + + return $message === 'validation.any_of' + ? ['The :attribute field is invalid.'] + : $message; + } + + /** + * Set the current validator. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + * @return $this + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } +} diff --git a/src/Illuminate/Validation/Rules/ArrayRule.php b/src/Illuminate/Validation/Rules/ArrayRule.php index 8914f77a449c..f29264f59933 100644 --- a/src/Illuminate/Validation/Rules/ArrayRule.php +++ b/src/Illuminate/Validation/Rules/ArrayRule.php @@ -20,7 +20,6 @@ class ArrayRule implements Stringable * Create a new array rule instance. * * @param array|null $keys - * @return void */ public function __construct($keys = null) { diff --git a/src/Illuminate/Validation/Rules/DatabaseRule.php b/src/Illuminate/Validation/Rules/DatabaseRule.php index 90e69f087ae5..00de0a0821d6 100644 --- a/src/Illuminate/Validation/Rules/DatabaseRule.php +++ b/src/Illuminate/Validation/Rules/DatabaseRule.php @@ -44,7 +44,6 @@ trait DatabaseRule * * @param string $table * @param string $column - * @return void */ public function __construct($table, $column = 'NULL') { diff --git a/src/Illuminate/Validation/Rules/Date.php b/src/Illuminate/Validation/Rules/Date.php index 6b6140fbd8f8..cec8894f630c 100644 --- a/src/Illuminate/Validation/Rules/Date.php +++ b/src/Illuminate/Validation/Rules/Date.php @@ -12,17 +12,24 @@ class Date implements Stringable { use Conditionable, Macroable; + /** + * The format of the date. + */ + protected ?string $format = null; + /** * The constraints for the date rule. */ - protected array $constraints = ['date']; + protected array $constraints = []; /** * Ensure the date has the given format. */ public function format(string $format): static { - return $this->addRule('date_format:'.$format); + $this->format = $format; + + return $this; } /** @@ -121,7 +128,7 @@ protected function addRule(array|string $rules): static protected function formatDate(DateTimeInterface|string $date): string { return $date instanceof DateTimeInterface - ? $date->format('Y-m-d') + ? $date->format($this->format ?? 'Y-m-d') : $date; } @@ -130,6 +137,9 @@ protected function formatDate(DateTimeInterface|string $date): string */ public function __toString(): string { - return implode('|', $this->constraints); + return implode('|', [ + $this->format === null ? 'date' : 'date_format:'.$this->format, + ...$this->constraints, + ]); } } diff --git a/src/Illuminate/Validation/Rules/Dimensions.php b/src/Illuminate/Validation/Rules/Dimensions.php index bdbaf13a2ea1..76331fbd6ff8 100644 --- a/src/Illuminate/Validation/Rules/Dimensions.php +++ b/src/Illuminate/Validation/Rules/Dimensions.php @@ -20,7 +20,6 @@ class Dimensions implements Stringable * Create a new dimensions rule instance. * * @param array $constraints - * @return void */ public function __construct(array $constraints = []) { diff --git a/src/Illuminate/Validation/Rules/Enum.php b/src/Illuminate/Validation/Rules/Enum.php index 59991bbdd7c6..4ffd6ec70efe 100644 --- a/src/Illuminate/Validation/Rules/Enum.php +++ b/src/Illuminate/Validation/Rules/Enum.php @@ -16,7 +16,7 @@ class Enum implements Rule, ValidatorAwareRule /** * The type of the enum. * - * @var class-string + * @var class-string<\UnitEnum> */ protected $type; @@ -44,8 +44,7 @@ class Enum implements Rule, ValidatorAwareRule /** * Create a new rule instance. * - * @param class-string $type - * @return void + * @param class-string<\UnitEnum> $type */ public function __construct($type) { diff --git a/src/Illuminate/Validation/Rules/ExcludeIf.php b/src/Illuminate/Validation/Rules/ExcludeIf.php index 12869d46679c..3d00ff9422de 100644 --- a/src/Illuminate/Validation/Rules/ExcludeIf.php +++ b/src/Illuminate/Validation/Rules/ExcludeIf.php @@ -19,7 +19,6 @@ class ExcludeIf implements Stringable * Create a new exclude validation rule based on a condition. * * @param \Closure|bool $condition - * @return void * * @throws \InvalidArgumentException */ diff --git a/src/Illuminate/Validation/Rules/File.php b/src/Illuminate/Validation/Rules/File.php index 00e71d6553fc..b7589853e9d2 100644 --- a/src/Illuminate/Validation/Rules/File.php +++ b/src/Illuminate/Validation/Rules/File.php @@ -118,11 +118,12 @@ public static function default() /** * Limit the uploaded file to only image types. * + * @param bool $allowSvg * @return ImageFile */ - public static function image() + public static function image($allowSvg = false) { - return new ImageFile(); + return new ImageFile($allowSvg); } /** @@ -216,6 +217,8 @@ protected function toKilobytes($size) return $size; } + $size = strtolower(trim($size)); + $value = floatval($size); return round(match (true) { @@ -277,7 +280,7 @@ protected function buildValidationRules() $rules = array_merge($rules, $this->buildMimetypes()); if (! empty($this->allowedExtensions)) { - $rules[] = 'extensions:'.implode(',', array_map('strtolower', $this->allowedExtensions)); + $rules[] = 'extensions:'.implode(',', array_map(strtolower(...), $this->allowedExtensions)); } $rules[] = match (true) { diff --git a/src/Illuminate/Validation/Rules/ImageFile.php b/src/Illuminate/Validation/Rules/ImageFile.php index 2cee97e5bc00..6c89ee96bd79 100644 --- a/src/Illuminate/Validation/Rules/ImageFile.php +++ b/src/Illuminate/Validation/Rules/ImageFile.php @@ -7,17 +7,22 @@ class ImageFile extends File /** * Create a new image file rule instance. * - * @return void + * @param bool $allowSvg */ - public function __construct() + public function __construct($allowSvg = false) { - $this->rules('image'); + if ($allowSvg) { + $this->rules('image:allow_svg'); + } else { + $this->rules('image'); + } } /** * The dimension constraints for the uploaded file. * * @param \Illuminate\Validation\Rules\Dimensions $dimensions + * @return $this */ public function dimensions($dimensions) { diff --git a/src/Illuminate/Validation/Rules/In.php b/src/Illuminate/Validation/Rules/In.php index b4a8769825bc..4a2071f9adf1 100644 --- a/src/Illuminate/Validation/Rules/In.php +++ b/src/Illuminate/Validation/Rules/In.php @@ -27,7 +27,6 @@ class In implements Stringable * Create a new in rule instance. * * @param \Illuminate\Contracts\Support\Arrayable|\BackedEnum|\UnitEnum|array|string $values - * @return void */ public function __construct($values) { diff --git a/src/Illuminate/Validation/Rules/NotIn.php b/src/Illuminate/Validation/Rules/NotIn.php index 38dd259821cc..7ccb8f99bd12 100644 --- a/src/Illuminate/Validation/Rules/NotIn.php +++ b/src/Illuminate/Validation/Rules/NotIn.php @@ -27,7 +27,6 @@ class NotIn implements Stringable * Create a new "not in" rule instance. * * @param \Illuminate\Contracts\Support\Arrayable|\BackedEnum|\UnitEnum|array|string $values - * @return void */ public function __construct($values) { diff --git a/src/Illuminate/Validation/Rules/Password.php b/src/Illuminate/Validation/Rules/Password.php index 4f8608c2a206..1c131149c54a 100644 --- a/src/Illuminate/Validation/Rules/Password.php +++ b/src/Illuminate/Validation/Rules/Password.php @@ -111,7 +111,6 @@ class Password implements Rule, DataAwareRule, ValidatorAwareRule * Create a new rule instance. * * @param int $min - * @return void */ public function __construct($min) { @@ -147,8 +146,8 @@ public static function defaults($callback = null) public static function default() { $password = is_callable(static::$defaultCallback) - ? call_user_func(static::$defaultCallback) - : static::$defaultCallback; + ? call_user_func(static::$defaultCallback) + : static::$defaultCallback; return $password instanceof Rule ? $password : static::min(8); } @@ -380,4 +379,24 @@ protected function fail($messages) return false; } + + /** + * Get information about the current state of the password validation rules. + * + * @return array + */ + public function appliedRules() + { + return [ + 'min' => $this->min, + 'max' => $this->max, + 'mixedCase' => $this->mixedCase, + 'letters' => $this->letters, + 'numbers' => $this->numbers, + 'symbols' => $this->symbols, + 'uncompromised' => $this->uncompromised, + 'compromisedThreshold' => $this->compromisedThreshold, + 'customRules' => $this->customRules, + ]; + } } diff --git a/src/Illuminate/Validation/Rules/RequiredIf.php b/src/Illuminate/Validation/Rules/RequiredIf.php index bee7c2886033..6643de8a7b16 100644 --- a/src/Illuminate/Validation/Rules/RequiredIf.php +++ b/src/Illuminate/Validation/Rules/RequiredIf.php @@ -18,7 +18,6 @@ class RequiredIf implements Stringable * Create a new required validation rule based on a condition. * * @param callable|bool $condition - * @return void */ public function __construct($condition) { diff --git a/src/Illuminate/Validation/ValidationException.php b/src/Illuminate/Validation/ValidationException.php index 1418874333b8..aacbfe8d6571 100644 --- a/src/Illuminate/Validation/ValidationException.php +++ b/src/Illuminate/Validation/ValidationException.php @@ -49,7 +49,6 @@ class ValidationException extends Exception * @param \Illuminate\Contracts\Validation\Validator $validator * @param \Symfony\Component\HttpFoundation\Response|null $response * @param string $errorBag - * @return void */ public function __construct($validator, $response = null, $errorBag = 'default') { diff --git a/src/Illuminate/Validation/ValidationRuleParser.php b/src/Illuminate/Validation/ValidationRuleParser.php index c7fafbfa2149..bf63a222c0dc 100644 --- a/src/Illuminate/Validation/ValidationRuleParser.php +++ b/src/Illuminate/Validation/ValidationRuleParser.php @@ -3,6 +3,7 @@ namespace Illuminate\Validation; use Closure; +use Illuminate\Contracts\Validation\CompilableRules; use Illuminate\Contracts\Validation\InvokableRule; use Illuminate\Contracts\Validation\Rule as RuleContract; use Illuminate\Contracts\Validation\ValidationRule; @@ -34,7 +35,6 @@ class ValidationRuleParser * Create a new validation rule parser. * * @param array $data - * @return void */ public function __construct(array $data) { @@ -138,7 +138,7 @@ protected function prepareRule($rule, $attribute) return $rule; } - if ($rule instanceof NestedRules) { + if ($rule instanceof CompilableRules) { return $rule->compile( $attribute, $this->data[$attribute] ?? null, Arr::dot($this->data), $this->data )->rules[$attribute]; @@ -164,7 +164,7 @@ protected function explodeWildcardRules($results, $attribute, $rules) foreach ($data as $key => $value) { if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) { foreach ((array) $rules as $rule) { - if ($rule instanceof NestedRules) { + if ($rule instanceof CompilableRules) { $context = Arr::get($this->data, Str::beforeLast($key, '.')); $compiled = $rule->compile($key, $value, $data, $context); @@ -238,7 +238,7 @@ protected function mergeRulesForAttribute($results, $attribute, $rules) */ public static function parse($rule) { - if ($rule instanceof RuleContract || $rule instanceof NestedRules) { + if ($rule instanceof RuleContract || $rule instanceof CompilableRules) { return [$rule, []]; } @@ -341,8 +341,8 @@ public static function filterConditionalRules($rules, array $data = []) if ($attributeRules instanceof ConditionalRules) { return [$attribute => $attributeRules->passes($data) - ? array_filter($attributeRules->rules($data)) - : array_filter($attributeRules->defaultRules($data)), ]; + ? array_filter($attributeRules->rules($data)) + : array_filter($attributeRules->defaultRules($data)), ]; } return [$attribute => (new Collection($attributeRules))->map(function ($rule) use ($data) { diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index efe08f28cf26..816ac3df65b6 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -335,7 +335,6 @@ class Validator implements ValidatorContract * @param array $rules * @param array $messages * @param array $attributes - * @return void */ public function __construct( Translator $translator, @@ -396,8 +395,8 @@ protected function replacePlaceholders($data) foreach ($data as $key => $value) { $originalData[$this->replacePlaceholderInString($key)] = is_array($value) - ? $this->replacePlaceholders($value) - : $value; + ? $this->replacePlaceholders($value) + : $value; } return $originalData; @@ -421,8 +420,8 @@ protected function replacePlaceholderInString(string $value) /** * Replace each field parameter dot placeholder with dot. * - * @param string $value - * @return string + * @param array $parameters + * @return array */ protected function replaceDotPlaceholderInParameters(array $parameters) { @@ -588,8 +587,8 @@ public function validateWithBag(string $errorBag) public function safe(?array $keys = null) { return is_array($keys) - ? (new ValidatedInput($this->validated()))->only($keys) - : new ValidatedInput($this->validated()); + ? (new ValidatedInput($this->validated()))->only($keys) + : new ValidatedInput($this->validated()); } /** @@ -675,8 +674,8 @@ protected function validateAttribute($attribute, $rule) if ($rule instanceof RuleContract) { return $validatable - ? $this->validateUsingCustomRule($attribute, $value, $rule) - : null; + ? $this->validateUsingCustomRule($attribute, $value, $rule) + : null; } $method = "validate{$rule}"; @@ -1042,9 +1041,11 @@ public function invalid() */ protected function attributesThatHaveMessages() { - return (new Collection($this->messages()->toArray()))->map(function ($message, $key) { - return explode('.', $key)[0]; - })->unique()->flip()->all(); + return (new Collection($this->messages()->toArray())) + ->map(fn ($message, $key) => explode('.', $key)[0]) + ->unique() + ->flip() + ->all(); } /** @@ -1217,9 +1218,11 @@ public function getRulesWithoutPlaceholders() */ public function setRules(array $rules) { - $rules = (new Collection($rules))->mapWithKeys(function ($value, $key) { - return [str_replace('\.', '__dot__'.static::$placeholderHash, $key) => $value]; - })->toArray(); + $rules = (new Collection($rules)) + ->mapWithKeys(function ($value, $key) { + return [str_replace('\.', '__dot__'.static::$placeholderHash, $key) => $value]; + }) + ->toArray(); $this->initialRules = $rules; @@ -1244,9 +1247,9 @@ public function addRules($rules) $response = (new ValidationRuleParser($this->data)) ->explode(ValidationRuleParser::filterConditionalRules($rules, $this->data)); - $this->rules = array_merge_recursive( - $this->rules, $response->rules - ); + foreach ($response->rules as $key => $rule) { + $this->rules[$key] = array_merge($this->rules[$key] ?? [], $rule); + } $this->implicitAttributes = array_merge( $this->implicitAttributes, $response->implicitAttributes @@ -1291,8 +1294,8 @@ private function dataForSometimesIteration(string $attribute, $removeLastSegment $lastSegmentOfAttribute = strrchr($attribute, '.'); $attribute = $lastSegmentOfAttribute && $removeLastSegmentOfAttribute - ? Str::replaceLast($lastSegmentOfAttribute, '', $attribute) - : $attribute; + ? Str::replaceLast($lastSegmentOfAttribute, '', $attribute) + : $attribute; return is_array($data = data_get($this->data, $attribute)) ? new Fluent($data) @@ -1321,7 +1324,7 @@ public function stopOnFirstFailure($stopOnFirstFailure = true) public function addExtensions(array $extensions) { if ($extensions) { - $keys = array_map([Str::class, 'snake'], array_keys($extensions)); + $keys = array_map(Str::snake(...), array_keys($extensions)); $extensions = array_combine($keys, array_values($extensions)); } @@ -1408,7 +1411,7 @@ public function addDependentExtension($rule, $extension) public function addReplacers(array $replacers) { if ($replacers) { - $keys = array_map([Str::class, 'snake'], array_keys($replacers)); + $keys = array_map(Str::snake(...), array_keys($replacers)); $replacers = array_combine($keys, array_values($replacers)); } diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index ac4f997747a0..020433ac0016 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -17,16 +17,16 @@ "php": "^8.2", "ext-filter": "*", "ext-mbstring": "*", - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "brick/math": "^0.11|^0.12", "egulias/email-validator": "^3.2.5|^4.0", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", - "illuminate/translation": "^11.0", - "symfony/http-foundation": "^7.0", - "symfony/mime": "^7.0" + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "illuminate/translation": "^12.0", + "symfony/http-foundation": "^7.2", + "symfony/mime": "^7.2" }, "autoload": { "psr-4": { @@ -35,11 +35,12 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "illuminate/database": "Required to use the database presence verifier (^11.0)." + "illuminate/database": "Required to use the database presence verifier (^12.0).", + "ramsey/uuid": "Required to use Validator::validateUuid() (^4.7)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/View/AnonymousComponent.php b/src/Illuminate/View/AnonymousComponent.php index eba64365626b..661838181bda 100644 --- a/src/Illuminate/View/AnonymousComponent.php +++ b/src/Illuminate/View/AnonymousComponent.php @@ -23,7 +23,6 @@ class AnonymousComponent extends Component * * @param string $view * @param array $data - * @return void */ public function __construct($view, $data) { diff --git a/src/Illuminate/View/AppendableAttributeValue.php b/src/Illuminate/View/AppendableAttributeValue.php index 10981bf976e7..35609f26f717 100644 --- a/src/Illuminate/View/AppendableAttributeValue.php +++ b/src/Illuminate/View/AppendableAttributeValue.php @@ -17,7 +17,6 @@ class AppendableAttributeValue implements Stringable * Create a new appendable attribute value. * * @param mixed $value - * @return void */ public function __construct($value) { diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php index bd739b90a5d9..883eb16c4582 100644 --- a/src/Illuminate/View/Compilers/BladeCompiler.php +++ b/src/Illuminate/View/Compilers/BladeCompiler.php @@ -193,7 +193,17 @@ public function compile($path = null) $compiledPath = $this->getCompiledPath($this->getPath()) ); - $this->files->put($compiledPath, $contents); + if (! $this->files->exists($compiledPath)) { + $this->files->put($compiledPath, $contents); + + return; + } + + $compiledHash = $this->files->hash($compiledPath, 'xxh128'); + + if ($compiledHash !== hash('xxh128', $contents)) { + $this->files->put($compiledPath, $contents); + } } } @@ -713,8 +723,8 @@ public function if($name, callable $callback) $this->directive($name, function ($expression) use ($name) { return $expression !== '' - ? "" - : ""; + ? "" + : ""; }); $this->directive('unless'.$name, function ($expression) use ($name) { @@ -762,10 +772,10 @@ public function component($class, $alias = null, $prefix = '') if (is_null($alias)) { $alias = str_contains($class, '\\View\\Components\\') - ? (new Collection(explode('\\', Str::after($class, '\\View\\Components\\'))))->map(function ($segment) { - return Str::kebab($segment); - })->implode(':') - : Str::kebab(class_basename($class)); + ? (new Collection(explode('\\', Str::after($class, '\\View\\Components\\'))))->map(function ($segment) { + return Str::kebab($segment); + })->implode(':') + : Str::kebab(class_basename($class)); } if (! empty($prefix)) { @@ -812,7 +822,7 @@ public function getClassComponentAliases() */ public function anonymousComponentPath(string $path, ?string $prefix = null) { - $prefixHash = md5($prefix ?: $path); + $prefixHash = hash('xxh128', $prefix ?: $path); $this->anonymousComponentPaths[] = [ 'path' => $path, @@ -897,8 +907,8 @@ public function aliasComponent($path, $alias = null) $this->directive($alias, function ($expression) use ($path) { return $expression - ? "startComponent('{$path}', {$expression}); ?>" - : "startComponent('{$path}'); ?>"; + ? "startComponent('{$path}', {$expression}); ?>" + : "startComponent('{$path}'); ?>"; }); $this->directive('end'.$alias, function ($expression) { diff --git a/src/Illuminate/View/Compilers/Compiler.php b/src/Illuminate/View/Compilers/Compiler.php index e93b78310d17..1e61eae95932 100755 --- a/src/Illuminate/View/Compilers/Compiler.php +++ b/src/Illuminate/View/Compilers/Compiler.php @@ -44,6 +44,13 @@ abstract class Compiler */ protected $compiledExtension = 'php'; + /** + * Indicates if view cache timestamps should be checked. + * + * @var bool + */ + protected $shouldCheckTimestamps; + /** * Create a new compiler instance. * @@ -51,8 +58,8 @@ abstract class Compiler * @param string $cachePath * @param string $basePath * @param bool $shouldCache + * @param bool $shouldCheckTimestamps * @param string $compiledExtension - * @return void * * @throws \InvalidArgumentException */ @@ -62,6 +69,7 @@ public function __construct( $basePath = '', $shouldCache = true, $compiledExtension = 'php', + $shouldCheckTimestamps = true, ) { if (! $cachePath) { throw new InvalidArgumentException('Please provide a valid cache path.'); @@ -72,6 +80,7 @@ public function __construct( $this->basePath = $basePath; $this->shouldCache = $shouldCache; $this->compiledExtension = $compiledExtension; + $this->shouldCheckTimestamps = $shouldCheckTimestamps; } /** @@ -108,6 +117,10 @@ public function isExpired($path) return true; } + if (! $this->shouldCheckTimestamps) { + return false; + } + try { return $this->files->lastModified($path) >= $this->files->lastModified($compiled); diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index 357bcd241f58..94876988546f 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -54,7 +54,6 @@ class ComponentTagCompiler * @param array $aliases * @param array $namespaces * @param \Illuminate\View\Compilers\BladeCompiler|null $blade - * @return void */ public function __construct(array $aliases = [], array $namespaces = [], ?BladeCompiler $blade = null) { @@ -337,8 +336,8 @@ protected function guessAnonymousComponentUsingPaths(Factory $viewFactory, strin } $formattedComponent = str_starts_with($component, $path['prefix'].$delimiter) - ? Str::after($component, $delimiter) - : $component; + ? Str::after($component, $delimiter) + : $component; if (! is_null($guess = match (true) { $viewFactory->exists($guess = $path['prefixHash'].$delimiter.$formattedComponent) => $guess, @@ -485,8 +484,8 @@ public function partitionDataAndAttributes($class, array $attributes) $constructor = (new ReflectionClass($class))->getConstructor(); $parameterNames = $constructor - ? (new Collection($constructor->getParameters()))->map->getName()->all() - : []; + ? (new Collection($constructor->getParameters()))->map->getName()->all() + : []; return (new Collection($attributes)) ->partition(fn ($value, $key) => in_array(Str::camel($key), $parameterNames)) @@ -772,8 +771,8 @@ protected function escapeSingleQuotesOutsideOfPhpBlocks(string $value) } return $token[0] === T_INLINE_HTML - ? str_replace("'", "\\'", $token[1]) - : $token[1]; + ? str_replace("'", "\\'", $token[1]) + : $token[1]; })->implode(''); } @@ -789,8 +788,8 @@ protected function attributesToString(array $attributes, $escapeBound = true) return (new Collection($attributes)) ->map(function (string $value, string $attribute) use ($escapeBound) { return $escapeBound && isset($this->boundAttributes[$attribute]) && $value !== 'true' && ! is_numeric($value) - ? "'{$attribute}' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})" - : "'{$attribute}' => {$value}"; + ? "'{$attribute}' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute({$value})" + : "'{$attribute}' => {$value}"; }) ->implode(','); } @@ -804,7 +803,7 @@ protected function attributesToString(array $attributes, $escapeBound = true) public function stripQuotes(string $value) { return Str::startsWith($value, ['"', '\'']) - ? substr($value, 1, -1) - : $value; + ? substr($value, 1, -1) + : $value; } } diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php index 56d1399e85dc..0d8ef22239f5 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php @@ -25,8 +25,8 @@ trait CompilesComponents protected function compileComponent($expression) { [$component, $alias, $data] = str_contains($expression, ',') - ? array_map('trim', explode(',', trim($expression, '()'), 3)) + ['', '', ''] - : [trim($expression, '()'), '', '']; + ? array_map(trim(...), explode(',', trim($expression, '()'), 3)) + ['', '', ''] + : [trim($expression, '()'), '', '']; $component = trim($component, '\'"'); @@ -215,8 +215,8 @@ public static function sanitizeComponentAttribute($value) } return is_string($value) || - (is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) - ? e($value) - : $value; + (is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString')) + ? e($value) + : $value; } } diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php b/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php index 8218c9fdf2c6..cefd5fd6eb94 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php @@ -12,11 +12,35 @@ trait CompilesUseStatements */ protected function compileUse($expression) { - $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); + $expression = trim(preg_replace('/[()]/', '', $expression), " '\""); - $use = ltrim(trim($segments[0], " '\""), '\\'); - $as = isset($segments[1]) ? ' as '.trim($segments[1], " '\"") : ''; + // Isolate alias... + if (str_contains($expression, '{')) { + $pathWithOptionalModifier = $expression; + $aliasWithLeadingSpace = ''; + } else { + $segments = explode(',', $expression); + $pathWithOptionalModifier = trim($segments[0], " '\""); - return ""; + $aliasWithLeadingSpace = isset($segments[1]) + ? ' as '.trim($segments[1], " '\"") + : ''; + } + + // Split modifier and path... + if (str_starts_with($pathWithOptionalModifier, 'function ')) { + $modifierWithTrailingSpace = 'function '; + $path = explode(' ', $pathWithOptionalModifier, 2)[1] ?? $pathWithOptionalModifier; + } elseif (str_starts_with($pathWithOptionalModifier, 'const ')) { + $modifierWithTrailingSpace = 'const '; + $path = explode(' ', $pathWithOptionalModifier, 2)[1] ?? $pathWithOptionalModifier; + } else { + $modifierWithTrailingSpace = ''; + $path = $pathWithOptionalModifier; + } + + $path = ltrim($path, '\\'); + + return ""; } } diff --git a/src/Illuminate/View/Component.php b/src/Illuminate/View/Component.php index ae4f760c6637..4e67b9a897bd 100644 --- a/src/Illuminate/View/Component.php +++ b/src/Illuminate/View/Component.php @@ -288,8 +288,8 @@ protected function extractPublicMethods() protected function createVariableFromMethod(ReflectionMethod $method) { return $method->getNumberOfParameters() === 0 - ? $this->createInvokableVariable($method->getName()) - : Closure::fromCallable([$this, $method->getName()]); + ? $this->createInvokableVariable($method->getName()) + : Closure::fromCallable([$this, $method->getName()]); } /** diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php index 780d93deb51d..6ab3ab433ca5 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -4,6 +4,7 @@ use ArrayAccess; use ArrayIterator; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -16,7 +17,7 @@ use Stringable; use Traversable; -class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSerializable, Htmlable, Stringable +class ComponentAttributeBag implements Arrayable, ArrayAccess, IteratorAggregate, JsonSerializable, Htmlable, Stringable { use Conditionable, Macroable; @@ -31,7 +32,6 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSeria * Create a new component attribute bag instance. * * @param array $attributes - * @return void */ public function __construct(array $attributes = []) { @@ -39,7 +39,7 @@ public function __construct(array $attributes = []) } /** - * Get all of the attribute values. + * Get all the attribute values. * * @return array */ @@ -269,8 +269,8 @@ public function merge(array $attributeDefaults = [], $escape = true) { $attributeDefaults = array_map(function ($value) use ($escape) { return $this->shouldEscapeAttributeValue($escape, $value) - ? e($value) - : $value; + ? e($value) + : $value; }, $attributeDefaults); [$appendableAttributes, $nonAppendableAttributes] = (new Collection($this->attributes)) @@ -283,8 +283,8 @@ public function merge(array $attributeDefaults = [], $escape = true) $attributes = $appendableAttributes->mapWithKeys(function ($value, $key) use ($attributeDefaults, $escape) { $defaultsValue = isset($attributeDefaults[$key]) && $attributeDefaults[$key] instanceof AppendableAttributeValue - ? $this->resolveAppendableAttributeDefault($attributeDefaults, $key, $escape) - : ($attributeDefaults[$key] ?? ''); + ? $this->resolveAppendableAttributeDefault($attributeDefaults, $key, $escape) + : ($attributeDefaults[$key] ?? ''); if ($key === 'style') { $value = Str::finish($value, ';'); @@ -498,6 +498,16 @@ public function jsonSerialize(): mixed return $this->attributes; } + /** + * Get all the attribute values. + * + * @return array + */ + public function toArray() + { + return $this->all(); + } + /** * Implode the attributes into a single HTML ready string. * diff --git a/src/Illuminate/View/ComponentSlot.php b/src/Illuminate/View/ComponentSlot.php index f363391cffc9..3a3ecd2e5a17 100644 --- a/src/Illuminate/View/ComponentSlot.php +++ b/src/Illuminate/View/ComponentSlot.php @@ -27,7 +27,6 @@ class ComponentSlot implements Htmlable, Stringable * * @param string $contents * @param array $attributes - * @return void */ public function __construct($contents = '', $attributes = []) { diff --git a/src/Illuminate/View/Concerns/ManagesLoops.php b/src/Illuminate/View/Concerns/ManagesLoops.php index 95f06c825cb4..7098f4a1d27f 100644 --- a/src/Illuminate/View/Concerns/ManagesLoops.php +++ b/src/Illuminate/View/Concerns/ManagesLoops.php @@ -23,8 +23,8 @@ trait ManagesLoops public function addLoop($data) { $length = is_countable($data) && ! $data instanceof LazyCollection - ? count($data) - : null; + ? count($data) + : null; $parent = Arr::last($this->loopsStack); diff --git a/src/Illuminate/View/DynamicComponent.php b/src/Illuminate/View/DynamicComponent.php index 0e0edd1f6bfa..b34d3759d086 100644 --- a/src/Illuminate/View/DynamicComponent.php +++ b/src/Illuminate/View/DynamicComponent.php @@ -34,7 +34,6 @@ class DynamicComponent extends Component * Create a new component instance. * * @param string $component - * @return void */ public function __construct(string $component) { diff --git a/src/Illuminate/View/Engines/CompilerEngine.php b/src/Illuminate/View/Engines/CompilerEngine.php index a14903421830..e72afac85fd5 100755 --- a/src/Illuminate/View/Engines/CompilerEngine.php +++ b/src/Illuminate/View/Engines/CompilerEngine.php @@ -40,7 +40,6 @@ class CompilerEngine extends PhpEngine * * @param \Illuminate\View\Compilers\CompilerInterface $compiler * @param \Illuminate\Filesystem\Filesystem|null $files - * @return void */ public function __construct(CompilerInterface $compiler, ?Filesystem $files = null) { diff --git a/src/Illuminate/View/Engines/FileEngine.php b/src/Illuminate/View/Engines/FileEngine.php index 992f6758d980..65cf2d06924c 100644 --- a/src/Illuminate/View/Engines/FileEngine.php +++ b/src/Illuminate/View/Engines/FileEngine.php @@ -18,7 +18,6 @@ class FileEngine implements Engine * Create a new file engine instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/View/Engines/PhpEngine.php b/src/Illuminate/View/Engines/PhpEngine.php index 13525aeeab53..617fbd8c7245 100755 --- a/src/Illuminate/View/Engines/PhpEngine.php +++ b/src/Illuminate/View/Engines/PhpEngine.php @@ -19,7 +19,6 @@ class PhpEngine implements Engine * Create a new file engine instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @return void */ public function __construct(Filesystem $files) { diff --git a/src/Illuminate/View/Factory.php b/src/Illuminate/View/Factory.php index e5efe067e86e..56718e2a84ad 100755 --- a/src/Illuminate/View/Factory.php +++ b/src/Illuminate/View/Factory.php @@ -110,7 +110,6 @@ class Factory implements FactoryContract * @param \Illuminate\View\Engines\EngineResolver $engines * @param \Illuminate\View\ViewFinderInterface $finder * @param \Illuminate\Contracts\Events\Dispatcher $events - * @return void */ public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) { @@ -246,8 +245,8 @@ public function renderEach($view, $data, $iterator, $empty = 'raw|') // with "raw|" for convenience and to let this know that it is a string. else { $result = str_starts_with($empty, 'raw|') - ? substr($empty, 4) - : $this->make($empty)->render(); + ? substr($empty, 4) + : $this->make($empty)->render(); } return $result; diff --git a/src/Illuminate/View/FileViewFinder.php b/src/Illuminate/View/FileViewFinder.php index c2f49bd68bde..b29c0088e3c2 100755 --- a/src/Illuminate/View/FileViewFinder.php +++ b/src/Illuminate/View/FileViewFinder.php @@ -17,21 +17,21 @@ class FileViewFinder implements ViewFinderInterface /** * The array of active view paths. * - * @var array + * @var string[] */ protected $paths; /** * The array of views that have been located. * - * @var array + * @var array */ protected $views = []; /** * The namespace to file path hints. * - * @var array + * @var array */ protected $hints = []; @@ -46,14 +46,13 @@ class FileViewFinder implements ViewFinderInterface * Create a new file view loader instance. * * @param \Illuminate\Filesystem\Filesystem $files - * @param array $paths - * @param array|null $extensions - * @return void + * @param string[] $paths + * @param string[]|null $extensions */ public function __construct(Filesystem $files, array $paths, ?array $extensions = null) { $this->files = $files; - $this->paths = array_map([$this, 'resolvePath'], $paths); + $this->paths = array_map($this->resolvePath(...), $paths); if (isset($extensions)) { $this->extensions = $extensions; @@ -96,7 +95,7 @@ protected function findNamespacedView($name) * Get the segments of a template with a named path. * * @param string $name - * @return array + * @return string[] * * @throws \InvalidArgumentException */ @@ -119,7 +118,7 @@ protected function parseNamespaceSegments($name) * Find the given view in the list of paths. * * @param string $name - * @param array $paths + * @param string|string[] $paths * @return string * * @throws \InvalidArgumentException @@ -143,7 +142,7 @@ protected function findInPaths($name, $paths) * Get an array of possible view files. * * @param string $name - * @return array + * @return string[] */ protected function getPossibleViewFiles($name) { @@ -187,7 +186,7 @@ protected function resolvePath($path) * Add a namespace hint to the finder. * * @param string $namespace - * @param string|array $hints + * @param string|string[] $hints * @return void */ public function addNamespace($namespace, $hints) @@ -205,7 +204,7 @@ public function addNamespace($namespace, $hints) * Prepend a namespace hint to the finder. * * @param string $namespace - * @param string|array $hints + * @param string|string[] $hints * @return void */ public function prependNamespace($namespace, $hints) @@ -223,7 +222,7 @@ public function prependNamespace($namespace, $hints) * Replace the namespace hints for the given namespace. * * @param string $namespace - * @param string|array $hints + * @param string|string[] $hints * @return void */ public function replaceNamespace($namespace, $hints) @@ -280,7 +279,7 @@ public function getFilesystem() /** * Set the active view paths. * - * @param array $paths + * @param string[] $paths * @return $this */ public function setPaths($paths) @@ -293,7 +292,7 @@ public function setPaths($paths) /** * Get the active view paths. * - * @return array + * @return string[] */ public function getPaths() { @@ -303,7 +302,7 @@ public function getPaths() /** * Get the views that have been located. * - * @return array + * @return array */ public function getViews() { @@ -313,7 +312,7 @@ public function getViews() /** * Get the namespace to file path hints. * - * @return array + * @return array */ public function getHints() { @@ -323,7 +322,7 @@ public function getHints() /** * Get registered extensions. * - * @return array + * @return string[] */ public function getExtensions() { diff --git a/src/Illuminate/View/InvokableComponentVariable.php b/src/Illuminate/View/InvokableComponentVariable.php index d1ea11768d8c..e9963b9ea96b 100644 --- a/src/Illuminate/View/InvokableComponentVariable.php +++ b/src/Illuminate/View/InvokableComponentVariable.php @@ -23,7 +23,6 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorA * Create a new variable instance. * * @param \Closure $callable - * @return void */ public function __construct(Closure $callable) { diff --git a/src/Illuminate/View/Middleware/ShareErrorsFromSession.php b/src/Illuminate/View/Middleware/ShareErrorsFromSession.php index 64015d58678c..6c99a87ed17e 100644 --- a/src/Illuminate/View/Middleware/ShareErrorsFromSession.php +++ b/src/Illuminate/View/Middleware/ShareErrorsFromSession.php @@ -19,7 +19,6 @@ class ShareErrorsFromSession * Create a new error binder instance. * * @param \Illuminate\Contracts\View\Factory $view - * @return void */ public function __construct(ViewFactory $view) { diff --git a/src/Illuminate/View/View.php b/src/Illuminate/View/View.php index ac165842a03c..c388d23d5baa 100755 --- a/src/Illuminate/View/View.php +++ b/src/Illuminate/View/View.php @@ -67,7 +67,6 @@ class View implements ArrayAccess, Htmlable, Stringable, ViewContract * @param string $view * @param string $path * @param mixed $data - * @return void */ public function __construct(Factory $factory, Engine $engine, $view, $path, $data = []) { @@ -295,8 +294,8 @@ public function withErrors($provider, $bag = 'default') protected function formatErrors($provider) { return $provider instanceof MessageProvider - ? $provider->getMessageBag() - : new MessageBag((array) $provider); + ? $provider->getMessageBag() + : new MessageBag((array) $provider); } /** diff --git a/src/Illuminate/View/ViewServiceProvider.php b/src/Illuminate/View/ViewServiceProvider.php index 41cd8b93c9aa..2ef3d31177b4 100755 --- a/src/Illuminate/View/ViewServiceProvider.php +++ b/src/Illuminate/View/ViewServiceProvider.php @@ -100,6 +100,7 @@ public function registerBladeCompiler() $app['config']->get('view.relative_hash', false) ? $app->basePath() : '', $app['config']->get('view.cache', true), $app['config']->get('view.compiled_extension', 'php'), + $app['config']->get('view.check_cache_timestamps', true), ), function ($blade) { $blade->component('dynamic-component', DynamicComponent::class); }); diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index 41472bc1cff1..fd32f9538711 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -16,13 +16,13 @@ "require": { "php": "^8.2", "ext-tokenizer": "*", - "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" + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/events": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0" }, "autoload": { "psr-4": { @@ -31,7 +31,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/tests/Broadcasting/BroadcasterTest.php b/tests/Broadcasting/BroadcasterTest.php index 57312935ccdb..0e49c4b9e231 100644 --- a/tests/Broadcasting/BroadcasterTest.php +++ b/tests/Broadcasting/BroadcasterTest.php @@ -61,9 +61,7 @@ public function testExtractingParametersWhileCheckingForUserAccess() $parameters = $this->broadcaster->extractAuthParameters('asd', 'asd', $callback); $this->assertEquals([], $parameters); - /* - * Test Explicit Binding... - */ + // Test Explicit Binding... $container = new Container; Container::setInstance($container); $binder = m::mock(BindingRegistrar::class); diff --git a/tests/Bus/BusPendingBatchTest.php b/tests/Bus/BusPendingBatchTest.php index b9cc08066103..baed6e2726b6 100644 --- a/tests/Bus/BusPendingBatchTest.php +++ b/tests/Bus/BusPendingBatchTest.php @@ -71,7 +71,8 @@ public function test_batch_is_deleted_from_storage_if_exception_thrown_during_ba $container = new Container; - $job = new class {}; + $job = new class { + }; $pendingBatch = new PendingBatch($container, new Collection([$job])); @@ -225,7 +226,8 @@ public function test_batch_before_event_is_called() public function test_it_throws_exception_if_batched_job_is_not_batchable(): void { - $nonBatchableJob = new class {}; + $nonBatchableJob = new class { + }; $this->expectException(RuntimeException::class); @@ -240,7 +242,8 @@ public function test_it_throws_an_exception_if_batched_job_contains_batch_with_n new PendingBatch( $container, new Collection( - [new PendingBatch($container, new Collection([new BatchableJob, new class {}]))] + [new PendingBatch($container, new Collection([new BatchableJob, new class { + }]))] ) ); } diff --git a/tests/Cache/CacheEventsTest.php b/tests/Cache/CacheEventsTest.php index 04036db602fd..e0c747d0ca7d 100755 --- a/tests/Cache/CacheEventsTest.php +++ b/tests/Cache/CacheEventsTest.php @@ -3,6 +3,9 @@ namespace Illuminate\Tests\Cache; use Illuminate\Cache\ArrayStore; +use Illuminate\Cache\Events\CacheFlushed; +use Illuminate\Cache\Events\CacheFlushFailed; +use Illuminate\Cache\Events\CacheFlushing; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; use Illuminate\Cache\Events\ForgettingKey; @@ -221,6 +224,50 @@ public function testForgetDoesTriggerFailedEventOnFailure() $this->assertFalse($repository->forget('baz')); } + public function testFlushTriggersEvents() + { + $dispatcher = $this->getDispatcher(); + $repository = $this->getRepository($dispatcher); + + $dispatcher->shouldReceive('dispatch')->once()->with( + $this->assertEventMatches(CacheFlushing::class, [ + 'storeName' => 'array', + ]) + ); + + $dispatcher->shouldReceive('dispatch')->once()->with( + $this->assertEventMatches(CacheFlushed::class, [ + 'storeName' => 'array', + ]) + ); + $this->assertTrue($repository->clear()); + } + + public function testFlushFailureDoesDispatchEvent() + { + $dispatcher = $this->getDispatcher(); + + // Create a store that fails to flush + $failingStore = m::mock(Store::class); + $failingStore->shouldReceive('flush')->andReturn(false); + + $repository = new Repository($failingStore, ['store' => 'array']); + $repository->setEventDispatcher($dispatcher); + + $dispatcher->shouldReceive('dispatch')->once()->with( + $this->assertEventMatches(CacheFlushing::class, [ + 'storeName' => 'array', + ]) + ); + + $dispatcher->shouldReceive('dispatch')->once()->with( + $this->assertEventMatches(CacheFlushFailed::class, [ + 'storeName' => 'array', + ]) + ); + $this->assertFalse($repository->clear()); + } + protected function assertEventMatches($eventClass, $properties = []) { return m::on(function ($event) use ($eventClass, $properties) { diff --git a/tests/Cache/CacheRepositoryTest.php b/tests/Cache/CacheRepositoryTest.php index 5c4b77ce070f..5097a2797795 100755 --- a/tests/Cache/CacheRepositoryTest.php +++ b/tests/Cache/CacheRepositoryTest.php @@ -128,9 +128,7 @@ public function testRememberMethodCallsPutAndReturnsDefault() }); $this->assertSame('qux', $result); - /* - * Use a callable... - */ + // Use a callable... $repo = $this->getRepository(); $repo->getStore()->shouldReceive('get')->once()->andReturn(null); $repo->getStore()->shouldReceive('put')->once()->with('foo', 'bar', 10); diff --git a/tests/Config/RepositoryTest.php b/tests/Config/RepositoryTest.php index 727918fcb5a6..64cd8ca7b414 100644 --- a/tests/Config/RepositoryTest.php +++ b/tests/Config/RepositoryTest.php @@ -280,7 +280,7 @@ public function testOffsetUnset() $this->assertNull($this->repository->get('associate')); } - public function testsItIsMacroable() + public function testItIsMacroable() { $this->repository->macro('foo', function () { return 'macroable'; diff --git a/tests/Console/Scheduling/FrequencyTest.php b/tests/Console/Scheduling/FrequencyTest.php index 17cc6f398814..87f61d62cd08 100644 --- a/tests/Console/Scheduling/FrequencyTest.php +++ b/tests/Console/Scheduling/FrequencyTest.php @@ -10,9 +10,7 @@ class FrequencyTest extends TestCase { - /* - * @var \Illuminate\Console\Scheduling\Event - */ + /** @var \Illuminate\Console\Scheduling\Event */ protected $event; protected function setUp(): void diff --git a/tests/Console/View/ComponentsTest.php b/tests/Console/View/ComponentsTest.php index ef710e51effd..31958ba6cb8f 100644 --- a/tests/Console/View/ComponentsTest.php +++ b/tests/Console/View/ComponentsTest.php @@ -4,6 +4,7 @@ use Illuminate\Console\OutputStyle; use Illuminate\Console\View\Components; +use Illuminate\Database\Migrations\MigrationResult; use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Output\BufferedOutput; @@ -108,15 +109,20 @@ public function testTask() { $output = new BufferedOutput(); - with(new Components\Task($output))->render('My task', fn () => true); + with(new Components\Task($output))->render('My task', fn () => MigrationResult::Success->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('DONE', $result); - with(new Components\Task($output))->render('My task', fn () => false); + with(new Components\Task($output))->render('My task', fn () => MigrationResult::Failure->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('FAIL', $result); + + with(new Components\Task($output))->render('My task', fn () => MigrationResult::Skipped->value); + $result = $output->fetch(); + $this->assertStringContainsString('My task', $result); + $this->assertStringContainsString('SKIPPED', $result); } public function testTwoColumnDetail() diff --git a/tests/Container/ContainerCallTest.php b/tests/Container/ContainerCallTest.php index 694ccd511fcb..cd9d207a89c1 100644 --- a/tests/Container/ContainerCallTest.php +++ b/tests/Container/ContainerCallTest.php @@ -146,9 +146,7 @@ public function testCallWithDependencies() $this->assertInstanceOf(stdClass::class, $result[0]); $this->assertSame($stub, $result[1]); - /* - * Wrap a function... - */ + // Wrap a function... $result = $container->wrap(function (stdClass $foo, $bar = []) { return func_get_args(); }, ['bar' => 'taylor']); diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 7c99b0cbea21..5522b8af9b2f 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -40,6 +40,24 @@ public function testClosureResolution() $this->assertSame('Taylor', $container->make('name')); } + public function testAbstractCanBeBoundFromConcreteReturnType() + { + $container = new Container; + $container->bind(function (): IContainerContractStub|ContainerImplementationStub { + return new ContainerImplementationStub; + }); + $container->singleton(function (): ContainerConcreteStub { + return new ContainerConcreteStub; + }); + + $this->assertInstanceOf( + IContainerContractStub::class, + $container->make(IContainerContractStub::class) + ); + + $this->assertTrue($container->isShared(ContainerConcreteStub::class)); + } + public function testBindIfDoesntRegisterIfServiceAlreadyRegistered() { $container = new Container; @@ -289,6 +307,29 @@ public function testResolutionOfDefaultParameters() $this->assertSame('taylor', $instance->default); } + public function testResolutionOfClassWithDefaultParameters() + { + $container = new Container; + $instance = $container->make(ContainerClassWithDefaultValueStub::class); + $this->assertInstanceOf(ContainerConcreteStub::class, $instance->noDefault); + $this->assertSame(null, $instance->default); + + $container->bind(ContainerConcreteStub::class, fn () => new ContainerConcreteStub); + $instance = $container->make(ContainerClassWithDefaultValueStub::class); + $this->assertInstanceOf(ContainerConcreteStub::class, $instance->default); + } + + public function testResolutionOfClassWithDefaultParametersAndContextualBindings() + { + $container = new Container; + + $container->when(ContainerClassWithDefaultValueStub::class) + ->needs(ContainerConcreteStub::class) + ->give(fn () => new ContainerConcreteStub); + $instance = $container->make(ContainerClassWithDefaultValueStub::class); + $this->assertInstanceOf(ContainerConcreteStub::class, $instance->default); + } + public function testBound() { $container = new Container; @@ -765,6 +806,15 @@ public function __construct(ContainerConcreteStub $stub, $default = 'taylor') } } +class ContainerClassWithDefaultValueStub +{ + public function __construct( + public ?ContainerConcreteStub $noDefault, + public ?ContainerConcreteStub $default = null, + ) { + } +} + class ContainerMixedPrimitiveStub { public $first; diff --git a/tests/Container/ContextualBindingTest.php b/tests/Container/ContextualBindingTest.php index 1dae46248d7e..e1db2ee1ac08 100644 --- a/tests/Container/ContextualBindingTest.php +++ b/tests/Container/ContextualBindingTest.php @@ -23,9 +23,7 @@ public function testContainerCanInjectDifferentImplementationsDependingOnContext $this->assertInstanceOf(ContainerContextImplementationStub::class, $one->impl); $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $two->impl); - /* - * Test With Closures - */ + // Test With Closures $container = new Container; $container->bind(IContainerContextContractStub::class, ContainerContextImplementationStub::class); @@ -41,9 +39,7 @@ public function testContainerCanInjectDifferentImplementationsDependingOnContext $this->assertInstanceOf(ContainerContextImplementationStub::class, $one->impl); $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $two->impl); - /* - * Test nesting to make the same 'abstract' in different context - */ + // Test nesting to make the same 'abstract' in different context $container = new Container; $container->bind(IContainerContextContractStub::class, ContainerContextImplementationStub::class); diff --git a/tests/Container/UtilTest.php b/tests/Container/UtilTest.php index b467e533432a..928e07ad8841 100644 --- a/tests/Container/UtilTest.php +++ b/tests/Container/UtilTest.php @@ -4,6 +4,7 @@ use Illuminate\Container\Util; use PHPUnit\Framework\TestCase; +use ReflectionParameter; use stdClass; class UtilTest extends TestCase @@ -40,4 +41,15 @@ public function testArrayWrap() $this->assertEquals([$obj], Util::arrayWrap($obj)); $this->assertSame($obj, Util::arrayWrap($obj)[0]); } + + public function testGetParameterClassName() + { + $parameter = new ReflectionParameter(function (stdClass $foo) { + }, 0); + $this->assertSame('stdClass', Util::getParameterClassName($parameter)); + + $parameter = new ReflectionParameter(function (string $foo) { + }, 0); + $this->assertNull(Util::getParameterClassName($parameter)); + } } diff --git a/tests/Database/DatabaseAbstractSchemaGrammarTest.php b/tests/Database/DatabaseAbstractSchemaGrammarTest.php index 04e23eb264be..87340615e399 100755 --- a/tests/Database/DatabaseAbstractSchemaGrammarTest.php +++ b/tests/Database/DatabaseAbstractSchemaGrammarTest.php @@ -4,7 +4,6 @@ use Illuminate\Database\Connection; use Illuminate\Database\Schema\Grammars\Grammar; -use LogicException; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -17,21 +16,19 @@ protected function tearDown(): void public function testCreateDatabase() { - $grammar = new class extends Grammar {}; + $connection = m::mock(Connection::class); + $grammar = new class($connection) extends Grammar { + }; - $this->expectException(LogicException::class); - $this->expectExceptionMessage('This database driver does not support creating databases.'); - - $grammar->compileCreateDatabase('foo', m::mock(Connection::class)); + $this->assertSame('create database "foo"', $grammar->compileCreateDatabase('foo')); } public function testDropDatabaseIfExists() { - $grammar = new class extends Grammar {}; - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('This database driver does not support dropping databases.'); + $connection = m::mock(Connection::class); + $grammar = new class($connection) extends Grammar { + }; - $grammar->compileDropDatabaseIfExists('foo'); + $this->assertSame('drop database if exists "foo"', $grammar->compileDropDatabaseIfExists('foo')); } } diff --git a/tests/Database/DatabaseConcernsPreventsCircularRecursionTest.php b/tests/Database/DatabaseConcernsPreventsCircularRecursionTest.php index a35a6cd19447..66ca4b0bb4b3 100644 --- a/tests/Database/DatabaseConcernsPreventsCircularRecursionTest.php +++ b/tests/Database/DatabaseConcernsPreventsCircularRecursionTest.php @@ -8,7 +8,7 @@ class DatabaseConcernsPreventsCircularRecursionTest extends TestCase { - public function setUp(): void + protected function setUp(): void { parent::setUp(); diff --git a/tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php b/tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php index 3ea77e45aedc..fc9597378357 100644 --- a/tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php +++ b/tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php @@ -5,7 +5,7 @@ namespace Illuminate\Tests\Database; use Exception; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -19,7 +19,7 @@ class DatabaseEloquentBelongsToManyCreateOrFirstTest extends TestCase { - public function setUp(): void + protected function setUp(): void { Carbon::setTestNow('2023-01-01 00:00:00'); } @@ -450,9 +450,11 @@ protected function mockConnectionForModels(array $models, string $database, arra { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; $processorClass = 'Illuminate\Database\Query\Processors\\'.$database.'Processor'; - $grammar = new $grammarClass; $processor = new $processorClass; - $connection = Mockery::mock(ConnectionInterface::class, ['getQueryGrammar' => $grammar, 'getPostProcessor' => $processor]); + $connection = Mockery::mock(Connection::class, ['getPostProcessor' => $processor]); + $grammar = new $grammarClass($connection); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) { return new BaseBuilder($connection, $grammar, $processor); }); diff --git a/tests/Database/DatabaseEloquentBelongsToManyWithAttributesPendingTest.php b/tests/Database/DatabaseEloquentBelongsToManyWithAttributesPendingTest.php new file mode 100644 index 000000000000..673cbc525f70 --- /dev/null +++ b/tests/Database/DatabaseEloquentBelongsToManyWithAttributesPendingTest.php @@ -0,0 +1,256 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + $db->bootEloquent(); + $db->setAsGlobal(); + $this->createSchema(); + } + + public function testCreatesPendingAttributesAndPivotValues(): void + { + $post = ManyToManyPendingAttributesPost::create(); + $tag = $post->metaTags()->create(['name' => 'long article']); + + $this->assertSame('long article', $tag->name); + $this->assertTrue($tag->visible); + + $pivot = DB::table('pending_attributes_pivot')->first(); + $this->assertSame('meta', $pivot->type); + $this->assertSame($post->id, $pivot->post_id); + $this->assertSame($tag->id, $pivot->tag_id); + } + + public function testQueriesPendingAttributesAndPivotValues(): void + { + $post = new ManyToManyPendingAttributesPost(['id' => 2]); + $wheres = $post->metaTags()->toBase()->wheres; + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_pivot.tag_id', + 'operator' => '=', + 'value' => 2, + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_pivot.type', + 'operator' => '=', + 'value' => 'meta', + 'boolean' => 'and', + ], $wheres); + + // Ensure no other wheres exist + $this->assertCount(2, $wheres); + } + + public function testMorphToManyPendingAttributes(): void + { + $post = new ManyToManyPendingAttributesPost(['id' => 2]); + $wheres = $post->morphedTags()->toBase()->wheres; + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.type', + 'operator' => '=', + 'value' => 'meta', + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.taggable_type', + 'operator' => '=', + 'value' => ManyToManyPendingAttributesPost::class, + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.taggable_id', + 'operator' => '=', + 'value' => 2, + 'boolean' => 'and', + ], $wheres); + + // Ensure no other wheres exist + $this->assertCount(3, $wheres); + + $tag = $post->morphedTags()->create(['name' => 'new tag']); + + $this->assertTrue($tag->visible); + $this->assertSame('new tag', $tag->name); + $this->assertSame($tag->id, $post->morphedTags()->first()->id); + } + + public function testMorphedByManyPendingAttributes(): void + { + $tag = new ManyToManyPendingAttributesTag(['id' => 4]); + $wheres = $tag->morphedPosts()->toBase()->wheres; + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.type', + 'operator' => '=', + 'value' => 'meta', + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.taggable_type', + 'operator' => '=', + 'value' => ManyToManyPendingAttributesPost::class, + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => 'pending_attributes_taggables.tag_id', + 'operator' => '=', + 'value' => 4, + 'boolean' => 'and', + ], $wheres); + + // Ensure no other wheres exist + $this->assertCount(3, $wheres); + + $post = $tag->morphedPosts()->create(); + $this->assertSame('Title!', $post->title); + $this->assertSame($post->id, $tag->morphedPosts()->first()->id); + } + + protected function createSchema() + { + $this->schema()->create('pending_attributes_posts', function ($table) { + $table->increments('id'); + $table->string('title')->nullable(); + $table->timestamps(); + }); + + $this->schema()->create('pending_attributes_tags', function ($table) { + $table->increments('id'); + $table->string('name'); + $table->boolean('visible')->nullable(); + $table->timestamps(); + }); + + $this->schema()->create('pending_attributes_pivot', function ($table) { + $table->integer('post_id'); + $table->integer('tag_id'); + $table->string('type'); + }); + + $this->schema()->create('pending_attributes_taggables', function ($table) { + $table->integer('tag_id'); + $table->integer('taggable_id'); + $table->string('taggable_type'); + $table->string('type'); + }); + } + + /** + * Tear down the database schema. + * + * @return void + */ + protected function tearDown(): void + { + $this->schema()->drop('pending_attributes_posts'); + $this->schema()->drop('pending_attributes_tags'); + $this->schema()->drop('pending_attributes_pivot'); + } + + /** + * Get a database connection instance. + * + * @return \Illuminate\Database\Connection + */ + protected function connection($connection = 'default') + { + return Model::getConnectionResolver()->connection($connection); + } + + /** + * Get a schema builder instance. + * + * @return \Illuminate\Database\Schema\Builder + */ + protected function schema($connection = 'default') + { + return $this->connection($connection)->getSchemaBuilder(); + } +} + +class ManyToManyPendingAttributesPost extends Model +{ + protected $guarded = []; + protected $table = 'pending_attributes_posts'; + + public function tags(): BelongsToMany + { + return $this->belongsToMany( + ManyToManyPendingAttributesTag::class, + 'pending_attributes_pivot', + 'tag_id', + 'post_id', + ); + } + + public function metaTags(): BelongsToMany + { + return $this->tags() + ->withAttributes('visible', true, asConditions: false) + ->withPivotValue('type', 'meta'); + } + + public function morphedTags(): MorphToMany + { + return $this + ->morphToMany( + ManyToManyPendingAttributesTag::class, + 'taggable', + 'pending_attributes_taggables', + relatedPivotKey: 'tag_id' + ) + ->withAttributes('visible', true, asConditions: false) + ->withPivotValue('type', 'meta'); + } +} + +class ManyToManyPendingAttributesTag extends Model +{ + protected $guarded = []; + protected $table = 'pending_attributes_tags'; + + public function morphedPosts(): MorphToMany + { + return $this + ->morphedByMany( + ManyToManyPendingAttributesPost::class, + 'taggable', + 'pending_attributes_taggables', + 'tag_id', + ) + ->withAttributes('title', 'Title!', asConditions: false) + ->withPivotValue('type', 'meta'); + } +} diff --git a/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php b/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php index c7cab6453dfb..08f1bd45a56e 100644 --- a/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php +++ b/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php @@ -27,6 +27,7 @@ public function testModelsAreProperlyMatchedToParents() $model1->shouldReceive('getAttribute')->with('foo')->passthru(); $model1->shouldReceive('hasGetMutator')->andReturn(false); $model1->shouldReceive('hasAttributeMutator')->andReturn(false); + $model1->shouldReceive('hasRelationAutoloadCallback')->andReturn(false); $model1->shouldReceive('getCasts')->andReturn([]); $model1->shouldReceive('getRelationValue', 'relationLoaded', 'relationResolver', 'setRelation', 'isRelation')->passthru(); @@ -36,6 +37,7 @@ public function testModelsAreProperlyMatchedToParents() $model2->shouldReceive('getAttribute')->with('foo')->passthru(); $model2->shouldReceive('hasGetMutator')->andReturn(false); $model2->shouldReceive('hasAttributeMutator')->andReturn(false); + $model2->shouldReceive('hasRelationAutoloadCallback')->andReturn(false); $model2->shouldReceive('getCasts')->andReturn([]); $model2->shouldReceive('getRelationValue', 'relationLoaded', 'relationResolver', 'setRelation', 'isRelation')->passthru(); diff --git a/tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php b/tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php index b629194b6ea6..87fc0c43e24f 100755 --- a/tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php +++ b/tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php @@ -3,7 +3,7 @@ namespace Illuminate\Tests\Database; use Exception; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Query\Builder; @@ -15,7 +15,7 @@ class DatabaseEloquentBuilderCreateOrFirstTest extends TestCase { - public function setUp(): void + protected function setUp(): void { Carbon::setTestNow('2023-01-01 00:00:00'); } @@ -473,9 +473,11 @@ protected function mockConnectionForModel(Model $model, string $database, array { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; $processorClass = 'Illuminate\Database\Query\Processors\\'.$database.'Processor'; - $grammar = new $grammarClass; $processor = new $processorClass; - $connection = Mockery::mock(ConnectionInterface::class, ['getQueryGrammar' => $grammar, 'getPostProcessor' => $processor]); + $connection = Mockery::mock(Connection::class, ['getPostProcessor' => $processor]); + $grammar = new $grammarClass($connection); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) { return new Builder($connection, $grammar, $processor); }); diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 60daa61f0bcc..96a6beed7e20 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -4,6 +4,7 @@ use BadMethodCallException; use Closure; +use Illuminate\Database\Connection; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Builder; @@ -20,6 +21,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Collection as BaseCollection; use Mockery as m; +use PDO; use PHPUnit\Framework\TestCase; use stdClass; @@ -47,6 +49,19 @@ public function testFindMethod() $this->assertSame('baz', $result); } + public function testFindSoleMethod() + { + $builder = m::mock(Builder::class.'[sole]', [$this->getMockQueryBuilder()]); + $model = $this->getMockModel(); + $builder->setModel($model); + $model->shouldReceive('getKeyType')->once()->andReturn('int'); + $builder->getQuery()->shouldReceive('where')->once()->with('foo_table.foo', '=', 'bar'); + $builder->shouldReceive('sole')->with(['column'])->andReturn('baz'); + + $result = $builder->findSole('bar', ['column']); + $this->assertSame('baz', $result); + } + public function testFindManyMethod() { // ids are not empty @@ -369,15 +384,19 @@ public function testValueOrFailMethodWithModelNotFoundThrowsModelNotFoundExcepti public function testChunkWithLastChunkComplete() { - $builder = m::mock(Builder::class.'[forPage,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,offset,limit,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; $chunk1 = new Collection(['foo1', 'foo2']); $chunk2 = new Collection(['foo3', 'foo4']); $chunk3 = new Collection([]); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(2, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(3, 2)->andReturnSelf(); + + $builder->shouldReceive('getOffset')->once()->andReturn(null); + $builder->shouldReceive('getLimit')->once()->andReturn(null); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(2)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(4)->andReturnSelf(); + $builder->shouldReceive('limit')->times(3)->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(3)->andReturn($chunk1, $chunk2, $chunk3); $callbackAssertor = m::mock(stdClass::class); @@ -392,13 +411,16 @@ public function testChunkWithLastChunkComplete() public function testChunkWithLastChunkPartial() { - $builder = m::mock(Builder::class.'[forPage,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,offset,limit,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; $chunk1 = new Collection(['foo1', 'foo2']); $chunk2 = new Collection(['foo3']); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(2, 2)->andReturnSelf(); + $builder->shouldReceive('getOffset')->once()->andReturn(null); + $builder->shouldReceive('getLimit')->once()->andReturn(null); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(2)->andReturnSelf(); + $builder->shouldReceive('limit')->twice()->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(2)->andReturn($chunk1, $chunk2); $callbackAssertor = m::mock(stdClass::class); @@ -412,13 +434,16 @@ public function testChunkWithLastChunkPartial() public function testChunkCanBeStoppedByReturningFalse() { - $builder = m::mock(Builder::class.'[forPage,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,offset,limit,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; $chunk1 = new Collection(['foo1', 'foo2']); $chunk2 = new Collection(['foo3']); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->never()->with(2, 2); + + $builder->shouldReceive('getOffset')->once()->andReturn(null); + $builder->shouldReceive('getLimit')->once()->andReturn(null); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('limit')->once()->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(1)->andReturn($chunk1); $callbackAssertor = m::mock(stdClass::class); @@ -434,29 +459,30 @@ public function testChunkCanBeStoppedByReturningFalse() public function testChunkWithCountZero() { - $builder = m::mock(Builder::class.'[forPage,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,offset,limit,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; - $chunk = new Collection([]); - $builder->shouldReceive('forPage')->once()->with(1, 0)->andReturnSelf(); - $builder->shouldReceive('get')->times(1)->andReturn($chunk); + $builder->shouldReceive('getOffset')->once()->andReturn(null); + $builder->shouldReceive('getLimit')->once()->andReturn(null); + $builder->shouldReceive('offset')->never(); + $builder->shouldReceive('limit')->never(); + $builder->shouldReceive('get')->never(); - $callbackAssertor = m::mock(stdClass::class); - $callbackAssertor->shouldReceive('doSomething')->never(); - - $builder->chunk(0, function ($results) use ($callbackAssertor) { - $callbackAssertor->doSomething($results); + $builder->chunk(0, function () { + $this->fail('Should not be called.'); }); } public function testChunkPaginatesUsingIdWithLastChunkComplete() { - $builder = m::mock(Builder::class.'[forPageAfterId,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,forPageAfterId,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; $chunk1 = new Collection([(object) ['someIdField' => 1], (object) ['someIdField' => 2]]); $chunk2 = new Collection([(object) ['someIdField' => 10], (object) ['someIdField' => 11]]); $chunk3 = new Collection([]); + $builder->shouldReceive('getOffset')->andReturnNull(); + $builder->shouldReceive('getLimit')->andReturnNull(); $builder->shouldReceive('forPageAfterId')->once()->with(2, 0, 'someIdField')->andReturnSelf(); $builder->shouldReceive('forPageAfterId')->once()->with(2, 2, 'someIdField')->andReturnSelf(); $builder->shouldReceive('forPageAfterId')->once()->with(2, 11, 'someIdField')->andReturnSelf(); @@ -474,11 +500,13 @@ public function testChunkPaginatesUsingIdWithLastChunkComplete() public function testChunkPaginatesUsingIdWithLastChunkPartial() { - $builder = m::mock(Builder::class.'[forPageAfterId,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,forPageAfterId,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; $chunk1 = new Collection([(object) ['someIdField' => 1], (object) ['someIdField' => 2]]); $chunk2 = new Collection([(object) ['someIdField' => 10]]); + $builder->shouldReceive('getOffset')->andReturnNull(); + $builder->shouldReceive('getLimit')->andReturnNull(); $builder->shouldReceive('forPageAfterId')->once()->with(2, 0, 'someIdField')->andReturnSelf(); $builder->shouldReceive('forPageAfterId')->once()->with(2, 2, 'someIdField')->andReturnSelf(); $builder->shouldReceive('get')->times(2)->andReturn($chunk1, $chunk2); @@ -494,18 +522,19 @@ public function testChunkPaginatesUsingIdWithLastChunkPartial() public function testChunkPaginatesUsingIdWithCountZero() { - $builder = m::mock(Builder::class.'[forPageAfterId,get]', [$this->getMockQueryBuilder()]); + $builder = m::mock(Builder::class.'[getOffset,getLimit,forPageAfterId,get]', [$this->getMockQueryBuilder()]); $builder->getQuery()->orders[] = ['column' => 'foobar', 'direction' => 'asc']; - $chunk = new Collection([]); - $builder->shouldReceive('forPageAfterId')->once()->with(0, 0, 'someIdField')->andReturnSelf(); - $builder->shouldReceive('get')->times(1)->andReturn($chunk); + $builder->shouldReceive('getOffset')->andReturnNull(); + $builder->shouldReceive('getLimit')->andReturnNull(); + $builder->shouldReceive('forPageAfterId')->never(); + $builder->shouldReceive('get')->never(); $callbackAssertor = m::mock(stdClass::class); $callbackAssertor->shouldReceive('doSomething')->never(); - $builder->chunkById(0, function ($results) use ($callbackAssertor) { - $callbackAssertor->doSomething($results); + $builder->chunkById(0, function () { + $this->fail('Should never be called.'); }, 'someIdField'); } @@ -986,11 +1015,6 @@ public function testQueryPassThru() $builder->getQuery()->shouldReceive('raw')->once()->with('bar')->andReturn('foo'); $this->assertSame('foo', $builder->raw('bar')); - - $builder = $this->getBuilder(); - $grammar = new Grammar; - $builder->getQuery()->shouldReceive('getGrammar')->once()->andReturn($grammar); - $this->assertSame($grammar, $builder->getGrammar()); } public function testQueryScopes() @@ -1254,6 +1278,29 @@ public function testWhereBelongsTo() $this->assertEquals($result, $builder); } + public function testWhereAttachedTo() + { + $related = new EloquentBuilderTestModelFarRelatedStub; + $related->id = 49; + + $builder = EloquentBuilderTestModelParentStub::whereAttachedTo($related, 'roles'); + + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where exists (select * from "eloquent_builder_test_model_far_related_stubs" inner join "user_role" on "eloquent_builder_test_model_far_related_stubs"."id" = "user_role"."related_id" where "eloquent_builder_test_model_parent_stubs"."id" = "user_role"."self_id" and "eloquent_builder_test_model_far_related_stubs"."id" in (49))', $builder->toSql()); + } + + public function testWhereAttachedToCollection() + { + $model1 = new EloquentBuilderTestModelParentStub; + $model1->id = 3; + + $model2 = new EloquentBuilderTestModelParentStub; + $model2->id = 4; + + $builder = EloquentBuilderTestModelFarRelatedStub::whereAttachedTo(new Collection([$model1, $model2]), 'roles'); + + $this->assertSame('select * from "eloquent_builder_test_model_far_related_stubs" where exists (select * from "eloquent_builder_test_model_parent_stubs" inner join "user_role" on "eloquent_builder_test_model_parent_stubs"."id" = "user_role"."self_id" where "eloquent_builder_test_model_far_related_stubs"."id" = "user_role"."related_id" and "eloquent_builder_test_model_parent_stubs"."id" in (3, 4))', $builder->toSql()); + } + public function testDeleteOverride() { $builder = $this->getBuilder(); @@ -1430,6 +1477,18 @@ public function testWithCountMultipleAndPartialRename() $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select count(*) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_bar", (select count(*) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_count" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); } + public function testWithAggregateAlias() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withAggregate('foo', new Expression('TIMESTAMPDIFF(SECOND, `created_at`, `updated_at`)'), 'sum'); + + $this->assertSame( + 'select "eloquent_builder_test_model_parent_stubs".*, (select sum(TIMESTAMPDIFF(SECOND, `created_at`, `updated_at`)) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_sum_timestampdiffsecond_created_at_updated_at" from "eloquent_builder_test_model_parent_stubs"', + $builder->toSql() + ); + } + public function testWithAggregateAndSelfRelationConstrain() { EloquentBuilderTestStub::resolveRelationUsing('children', function ($model) { @@ -1837,7 +1896,7 @@ public function testWhereNotMorphedTo() $builder = $model->whereNotMorphedTo('morph', $relatedModel); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql()); $this->assertEquals([$relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings()); } @@ -1854,7 +1913,7 @@ public function testWhereNotMorphedToCollection() $builder = $model->whereNotMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel])); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)))', $builder->toSql()); $this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings()); } @@ -1874,7 +1933,7 @@ public function testWhereNotMorphedToCollectionWithDifferentModels() $builder = $model->whereNotMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql()); $this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings()); } @@ -1950,7 +2009,7 @@ public function testOrWhereNotMorphedTo() $builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', $relatedModel); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql()); $this->assertEquals(['baz', $relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings()); } @@ -1967,7 +2026,7 @@ public function testOrWhereNotMorphedToCollection() $builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel])); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)))', $builder->toSql()); $this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings()); } @@ -1987,7 +2046,7 @@ public function testOrWhereNotMorphedToCollectionWithDifferentModels() $builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]); - $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql()); + $this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql()); $this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings()); } @@ -2297,7 +2356,9 @@ public function testUpdate() { Carbon::setTestNow($now = '2017-10-10 10:10:10'); - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStub; $this->mockConnectionForModel($model, ''); @@ -2311,7 +2372,9 @@ public function testUpdate() public function testUpdateWithTimestampValue() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStub; $this->mockConnectionForModel($model, ''); @@ -2325,7 +2388,9 @@ public function testUpdateWithTimestampValue() public function testUpdateWithQualifiedTimestampValue() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStub; $this->mockConnectionForModel($model, ''); @@ -2339,7 +2404,9 @@ public function testUpdateWithQualifiedTimestampValue() public function testUpdateWithoutTimestamp() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStubWithoutTimestamp; $this->mockConnectionForModel($model, ''); @@ -2355,7 +2422,9 @@ public function testUpdateWithAlias() { Carbon::setTestNow($now = '2017-10-10 10:10:10'); - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStub; $this->mockConnectionForModel($model, ''); @@ -2371,7 +2440,9 @@ public function testUpdateWithAliasWithQualifiedTimestampValue() { Carbon::setTestNow($now = '2017-10-10 10:10:10'); - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $model = new EloquentBuilderTestStub; $this->mockConnectionForModel($model, ''); @@ -2475,7 +2546,9 @@ public function testWithCastsMethod() public function testClone() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = new Builder($query); $builder->select('*')->from('users'); $clone = $builder->clone()->where('email', 'foo'); @@ -2487,7 +2560,9 @@ public function testClone() public function testCloneModelMakesAFreshCopyOfTheModel() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = (new Builder($query))->setModel(new EloquentBuilderTestStub); $builder->select('*')->from('users'); @@ -2564,13 +2639,40 @@ public function getPassthru(): array } } + public function testPipeCallback() + { + $query = new Builder(new BaseBuilder( + $connection = new Connection(new PDO('sqlite::memory:')), + new Grammar($connection), + new Processor, + )); + + $result = $query->pipe(fn (Builder $query) => 5); + $this->assertSame(5, $result); + + $result = $query->pipe(fn (Builder $query) => null); + $this->assertSame($query, $result); + + $result = $query->pipe(function (Builder $query) { + // + }); + $this->assertSame($query, $result); + + $this->assertCount(0, $query->getQuery()->wheres); + $result = $query->pipe(fn (Builder $query) => $query->where('foo', 'bar')); + $this->assertSame($query, $result); + $this->assertCount(1, $query->getQuery()->wheres); + } + protected function mockConnectionForModel($model, $database) { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; $processorClass = 'Illuminate\Database\Query\Processors\\'.$database.'Processor'; - $grammar = new $grammarClass; $processor = new $processorClass; - $connection = m::mock(ConnectionInterface::class, ['getQueryGrammar' => $grammar, 'getPostProcessor' => $processor]); + $connection = m::mock(Connection::class, ['getPostProcessor' => $processor]); + $grammar = new $grammarClass($connection); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) { return new BaseBuilder($connection, $grammar, $processor); }); @@ -2734,7 +2836,15 @@ public function baz() class EloquentBuilderTestModelFarRelatedStub extends Model { - // + public function roles() + { + return $this->belongsToMany( + EloquentBuilderTestModelParentStub::class, + 'user_role', + 'related_id', + 'self_id', + ); + } } class EloquentBuilderTestModelSelfRelatedStub extends Model diff --git a/tests/Database/DatabaseEloquentCollectionTest.php b/tests/Database/DatabaseEloquentCollectionTest.php index 2669921059f5..99f2ad363ac7 100755 --- a/tests/Database/DatabaseEloquentCollectionTest.php +++ b/tests/Database/DatabaseEloquentCollectionTest.php @@ -14,6 +14,8 @@ use PHPUnit\Framework\TestCase; use stdClass; +use function Orchestra\Testbench\phpunit_version_compare; + class DatabaseEloquentCollectionTest extends TestCase { /** @@ -579,6 +581,8 @@ public function testNonModelRelatedMethods() $this->assertEquals(BaseCollection::class, get_class($a->zip(['a', 'b'], ['c', 'd']))); $this->assertEquals(BaseCollection::class, get_class($a->countBy('foo'))); $this->assertEquals(BaseCollection::class, get_class($b->flip())); + $this->assertEquals(BaseCollection::class, get_class($a->partition('foo', '=', 'bar'))); + $this->assertEquals(BaseCollection::class, get_class($a->partition('foo', 'bar'))); } public function testMakeVisibleRemovesHiddenAndIncludesVisible() @@ -701,7 +705,12 @@ public function testLoadExistsShouldCastBool() $user = EloquentTestUserModel::with('articles')->first(); $user->articles->loadExists('comments'); $commentsExists = $user->articles->pluck('comments_exists')->toArray(); - $this->assertContainsOnly('bool', $commentsExists); + + if (phpunit_version_compare('11.5.0', '<')) { + $this->assertContainsOnly('bool', $commentsExists); + } else { + $this->assertContainsOnlyBool($commentsExists); + } } public function testWithNonScalarKey() diff --git a/tests/Database/DatabaseEloquentFactoryTest.php b/tests/Database/DatabaseEloquentFactoryTest.php index b0860d236b03..da5467794d77 100644 --- a/tests/Database/DatabaseEloquentFactoryTest.php +++ b/tests/Database/DatabaseEloquentFactoryTest.php @@ -850,6 +850,62 @@ public function test_factory_global_model_resolver() $this->assertEquals(FactoryTestGuessModelFactory::new()->modelName(), FactoryTestGuessModel::class); } + public function test_factory_model_has_many_relationship_has_pending_attributes() + { + FactoryTestUser::factory()->has(new FactoryTestPostFactory(), 'postsWithFooBarBazAsTitle')->create(); + + $this->assertEquals('foo bar baz', FactoryTestPost::first()->title); + } + + public function test_factory_model_has_many_relationship_has_pending_attributes_override() + { + FactoryTestUser::factory()->has((new FactoryTestPostFactory())->state(['title' => 'other title']), 'postsWithFooBarBazAsTitle')->create(); + + $this->assertEquals('other title', FactoryTestPost::first()->title); + } + + public function test_factory_model_has_one_relationship_has_pending_attributes() + { + FactoryTestUser::factory()->has(new FactoryTestPostFactory(), 'postWithFooBarBazAsTitle')->create(); + + $this->assertEquals('foo bar baz', FactoryTestPost::first()->title); + } + + public function test_factory_model_has_one_relationship_has_pending_attributes_override() + { + FactoryTestUser::factory()->has((new FactoryTestPostFactory())->state(['title' => 'other title']), 'postWithFooBarBazAsTitle')->create(); + + $this->assertEquals('other title', FactoryTestPost::first()->title); + } + + public function test_factory_model_belongs_to_many_relationship_has_pending_attributes() + { + FactoryTestUser::factory()->has(new FactoryTestRoleFactory(), 'rolesWithFooBarBazAsName')->create(); + + $this->assertEquals('foo bar baz', FactoryTestRole::first()->name); + } + + public function test_factory_model_belongs_to_many_relationship_has_pending_attributes_override() + { + FactoryTestUser::factory()->has((new FactoryTestRoleFactory())->state(['name' => 'other name']), 'rolesWithFooBarBazAsName')->create(); + + $this->assertEquals('other name', FactoryTestRole::first()->name); + } + + public function test_factory_model_morph_many_relationship_has_pending_attributes() + { + (new FactoryTestPostFactory())->has(new FactoryTestCommentFactory(), 'commentsWithFooBarBazAsBody')->create(); + + $this->assertEquals('foo bar baz', FactoryTestComment::first()->body); + } + + public function test_factory_model_morph_many_relationship_has_pending_attributes_override() + { + (new FactoryTestPostFactory())->has((new FactoryTestCommentFactory())->state(['body' => 'other body']), 'commentsWithFooBarBazAsBody')->create(); + + $this->assertEquals('other body', FactoryTestComment::first()->body); + } + /** * Get a database connection instance. * @@ -895,11 +951,26 @@ public function posts() return $this->hasMany(FactoryTestPost::class, 'user_id'); } + public function postsWithFooBarBazAsTitle() + { + return $this->hasMany(FactoryTestPost::class, 'user_id')->withAttributes(['title' => 'foo bar baz']); + } + + public function postWithFooBarBazAsTitle() + { + return $this->hasOne(FactoryTestPost::class, 'user_id')->withAttributes(['title' => 'foo bar baz']); + } + public function roles() { return $this->belongsToMany(FactoryTestRole::class, 'role_user', 'user_id', 'role_id')->withPivot('admin'); } + public function rolesWithFooBarBazAsName() + { + return $this->belongsToMany(FactoryTestRole::class, 'role_user', 'user_id', 'role_id')->withPivot('admin')->withAttributes(['name' => 'foo bar baz']); + } + public function factoryTestRoles() { return $this->belongsToMany(FactoryTestRole::class, 'role_user', 'user_id', 'role_id')->withPivot('admin'); @@ -944,6 +1015,11 @@ public function comments() { return $this->morphMany(FactoryTestComment::class, 'commentable'); } + + public function commentsWithFooBarBazAsBody() + { + return $this->morphMany(FactoryTestComment::class, 'commentable')->withAttributes(['body' => 'foo bar baz']); + } } class FactoryTestCommentFactory extends Factory diff --git a/tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php b/tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php index 09faed3f2654..b7a055a66958 100755 --- a/tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php +++ b/tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php @@ -3,7 +3,7 @@ namespace Illuminate\Tests\Database; use Exception; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -16,7 +16,7 @@ class DatabaseEloquentHasManyCreateOrFirstTest extends TestCase { - public function setUp(): void + protected function setUp(): void { Carbon::setTestNow('2023-01-01 00:00:00'); } @@ -320,9 +320,11 @@ protected function mockConnectionForModel(Model $model, string $database, array { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; $processorClass = 'Illuminate\Database\Query\Processors\\'.$database.'Processor'; - $grammar = new $grammarClass; $processor = new $processorClass; - $connection = Mockery::mock(ConnectionInterface::class, ['getQueryGrammar' => $grammar, 'getPostProcessor' => $processor]); + $connection = Mockery::mock(Connection::class, ['getPostProcessor' => $processor]); + $grammar = new $grammarClass($connection); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) { return new Builder($connection, $grammar, $processor); }); diff --git a/tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php b/tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php index 1b364e57b9f7..63c69a6175bb 100644 --- a/tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php +++ b/tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php @@ -5,7 +5,7 @@ namespace Illuminate\Tests\Database; use Exception; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasManyThrough; @@ -18,7 +18,7 @@ class DatabaseEloquentHasManyThroughCreateOrFirstTest extends TestCase { - public function setUp(): void + protected function setUp(): void { Carbon::setTestNow('2023-01-01 00:00:00'); } @@ -372,9 +372,11 @@ protected function mockConnectionForModel(Model $model, string $database, array { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; $processorClass = 'Illuminate\Database\Query\Processors\\'.$database.'Processor'; - $grammar = new $grammarClass; $processor = new $processorClass; - $connection = Mockery::mock(ConnectionInterface::class, ['getQueryGrammar' => $grammar, 'getPostProcessor' => $processor]); + $connection = Mockery::mock(Connection::class, ['getPostProcessor' => $processor]); + $grammar = new $grammarClass($connection); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) { return new Builder($connection, $grammar, $processor); }); diff --git a/tests/Database/DatabaseEloquentHasOneOfManyTest.php b/tests/Database/DatabaseEloquentHasOneOfManyTest.php index c5a47355431e..c47f0178c22a 100755 --- a/tests/Database/DatabaseEloquentHasOneOfManyTest.php +++ b/tests/Database/DatabaseEloquentHasOneOfManyTest.php @@ -99,6 +99,13 @@ public function testRelationNameCanBeSet() $this->assertSame('baz', $relation->getRelationName()); } + public function testCorrectLatestOfManyQuery(): void + { + $user = HasOneOfManyTestUser::create(); + $relation = $user->latest_login(); + $this->assertSame('select "logins".* from "logins" inner join (select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" = ? and "logins"."user_id" is not null group by "logins"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "logins"."user_id" where "logins"."user_id" = ? and "logins"."user_id" is not null', $relation->getQuery()->toSql()); + } + public function testEagerLoadingAppliesConstraintsToInnerJoinSubQuery() { $user = HasOneOfManyTestUser::create(); @@ -591,7 +598,7 @@ public function states() public function foo_state() { return $this->hasOne(HasOneOfManyTestState::class, 'user_id')->ofMany( - ['id' => 'max'], + [], // should automatically add 'id' => 'max' function ($q) { $q->where('type', 'foo'); } diff --git a/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesPendingTest.php b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesPendingTest.php new file mode 100644 index 000000000000..77fc715a4274 --- /dev/null +++ b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesPendingTest.php @@ -0,0 +1,307 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + $db->bootEloquent(); + $db->setAsGlobal(); + } + + public function testHasManyAddsAttributes(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes([$key => $value], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame($value, $relatedModel->$key); + } + + public function testHasOneAddsAttributes(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasOne(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes([$key => $value], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame($value, $relatedModel->$key); + } + + public function testMorphManyAddsAttributes(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->morphMany(RelatedPendingAttributesModel::class, 'relatable') + ->withAttributes([$key => $value], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->relatable_id); + $this->assertSame($parent::class, $relatedModel->relatable_type); + $this->assertSame($value, $relatedModel->$key); + } + + public function testMorphOneAddsAttributes(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->morphOne(RelatedPendingAttributesModel::class, 'relatable') + ->withAttributes([$key => $value], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->relatable_id); + $this->assertSame($parent::class, $relatedModel->relatable_type); + $this->assertSame($value, $relatedModel->$key); + } + + public function testPendingAttributesCanBeOverridden(): void + { + $key = 'a key'; + $defaultValue = 'a value'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'relatable') + ->withAttributes([$key => $defaultValue], asConditions: false); + + $relatedModel = $relationship->make([$key => $value]); + + $this->assertSame($value, $relatedModel->$key); + } + + public function testQueryingDoesNotBreakWither(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->where($key, $value) + ->withAttributes([$key => $value], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame($value, $relatedModel->$key); + } + + public function testAttributesCanBeAppended(): void + { + $parent = new RelatedPendingAttributesModel; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes(['a' => 'A'], asConditions: false) + ->withAttributes(['b' => 'B'], asConditions: false) + ->withAttributes(['a' => 'AA'], asConditions: false); + + $relatedModel = $relationship->make([ + 'b' => 'BB', + 'c' => 'C', + ]); + + $this->assertSame('AA', $relatedModel->a); + $this->assertSame('BB', $relatedModel->b); + $this->assertSame('C', $relatedModel->c); + } + + public function testSingleAttributeApi(): void + { + $parent = new RelatedPendingAttributesModel; + $key = 'attr'; + $value = 'Value'; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes($key, $value, asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($value, $relatedModel->$key); + } + + public function testWheresAreNotSet(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes([$key => $value], asConditions: false); + + $wheres = $relationship->toBase()->wheres; + + $this->assertContains([ + 'type' => 'Basic', + 'column' => $parent->qualifyColumn('parent_id'), + 'operator' => '=', + 'value' => $parentId, + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'NotNull', + 'column' => $parent->qualifyColumn('parent_id'), + 'boolean' => 'and', + ], $wheres); + + // Ensure no other wheres exist + $this->assertCount(2, $wheres); + } + + public function testNullValueIsAccepted(): void + { + $parentId = 123; + $key = 'a key'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes([$key => null], asConditions: false); + + $wheres = $relationship->toBase()->wheres; + $relatedModel = $relationship->make(); + + $this->assertNull($relatedModel->$key); + + $this->assertContains([ + 'type' => 'Basic', + 'column' => $parent->qualifyColumn('parent_id'), + 'operator' => '=', + 'value' => $parentId, + 'boolean' => 'and', + ], $wheres); + + $this->assertContains([ + 'type' => 'NotNull', + 'column' => $parent->qualifyColumn('parent_id'), + 'boolean' => 'and', + ], $wheres); + + // Ensure no other wheres exist + $this->assertCount(2, $wheres); + } + + public function testOneKeepsAttributesFromHasMany(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes([$key => $value], asConditions: false) + ->one(); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame($value, $relatedModel->$key); + } + + public function testOneKeepsAttributesFromMorphMany(): void + { + $parentId = 123; + $key = 'a key'; + $value = 'the value'; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->morphMany(RelatedPendingAttributesModel::class, 'relatable') + ->withAttributes([$key => $value], asConditions: false) + ->one(); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->relatable_id); + $this->assertSame($parent::class, $relatedModel->relatable_type); + $this->assertSame($value, $relatedModel->$key); + } + + public function testHasManyAddsCastedAttributes(): void + { + $parentId = 123; + + $parent = new RelatedPendingAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedPendingAttributesModel::class, 'parent_id') + ->withAttributes(['is_admin' => 1], asConditions: false); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame(true, $relatedModel->is_admin); + } +} + +class RelatedPendingAttributesModel extends Model +{ + protected $guarded = []; + + protected $casts = [ + 'is_admin' => 'boolean', + ]; +} diff --git a/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php index ed686e594121..93ff90f29fc5 100755 --- a/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php +++ b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php @@ -98,7 +98,7 @@ public function testMorphOneAddsAttributes(): void $this->assertSame($value, $relatedModel->$key); } - public function testWithAttributesCanBeOverriden(): void + public function testWithAttributesCanBeOverridden(): void { $key = 'a key'; $defaultValue = 'a value'; diff --git a/tests/Database/DatabaseEloquentHasOneThroughOfManyTest.php b/tests/Database/DatabaseEloquentHasOneThroughOfManyTest.php new file mode 100755 index 000000000000..b4959738a981 --- /dev/null +++ b/tests/Database/DatabaseEloquentHasOneThroughOfManyTest.php @@ -0,0 +1,756 @@ +addConnection(['driver' => 'sqlite', 'database' => ':memory:']); + $db->bootEloquent(); + $db->setAsGlobal(); + + $this->createSchema(); + } + + public function createSchema(): void + { + $this->schema()->create('users', function ($table) { + $table->increments('id'); + }); + + $this->schema()->create('intermediates', function ($table) { + $table->increments('id'); + $table->foreignId('user_id'); + }); + + $this->schema()->create('logins', function ($table) { + $table->increments('id'); + $table->foreignId('intermediate_id'); + $table->dateTime('deleted_at')->nullable(); + }); + + $this->schema()->create('states', function ($table) { + $table->increments('id'); + $table->string('state'); + $table->string('type'); + $table->foreignId('intermediate_id'); + $table->timestamps(); + }); + + $this->schema()->create('prices', function ($table) { + $table->increments('id'); + $table->dateTime('published_at'); + $table->foreignId('intermediate_id'); + }); + } + + protected function tearDown(): void + { + $this->schema()->drop('users'); + $this->schema()->drop('intermediates'); + $this->schema()->drop('logins'); + $this->schema()->drop('states'); + $this->schema()->drop('prices'); + } + + public function testItGuessesRelationName(): void + { + $user = HasOneThroughOfManyTestUser::make(); + $this->assertSame('latest_login', $user->latest_login()->getRelationName()); + } + + public function testItGuessesRelationNameAndAddsOfManyWhenTableNameIsRelationName(): void + { + $model = HasOneThroughOfManyTestModel::make(); + $this->assertSame('logins_of_many', $model->logins()->getRelationName()); + } + + public function testRelationNameCanBeSet(): void + { + $user = HasOneThroughOfManyTestUser::create(); + + $relation = $user->latest_login()->ofMany('id', 'max', 'foo'); + $this->assertSame('foo', $relation->getRelationName()); + + $relation = $user->latest_login()->latestOfMany('id', 'bar'); + $this->assertSame('bar', $relation->getRelationName()); + + $relation = $user->latest_login()->oldestOfMany('id', 'baz'); + $this->assertSame('baz', $relation->getRelationName()); + } + + public function testCorrectLatestOfManyQuery(): void + { + $user = HasOneThroughOfManyTestUser::create(); + $relation = $user->latest_login(); + $this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql()); + } + + public function testEagerLoadingAppliesConstraintsToInnerJoinSubQuery(): void + { + $user = HasOneThroughOfManyTestUser::create(); + $relation = $user->latest_login(); + $relation->addEagerConstraints([$user]); + $this->assertSame('select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id"', $relation->getOneOfManySubQuery()->toSql()); + } + + public function testEagerLoadingAppliesConstraintsToQuery(): void + { + $user = HasOneThroughOfManyTestUser::create(); + $relation = $user->latest_login(); + $relation->addEagerConstraints([$user]); + $this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql()); + } + + public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalScope(): void + { + HasOneThroughOfManyTestLogin::addGlobalScope('test', function ($query) { + $query->orderBy($query->qualifyColumn('id')); + }); + + $user = HasOneThroughOfManyTestUser::create(); + $relation = $user->latest_login_without_global_scope(); + $relation->addEagerConstraints([$user]); + $this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latestOfMany" on "latestOfMany"."id_aggregate" = "logins"."id" and "latestOfMany"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql()); + + HasOneThroughOfManyTestLogin::addGlobalScope('test', function ($query) { + }); + } + + public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalScopeWithComplexQuery(): void + { + HasOneThroughOfManyTestPrice::addGlobalScope('test', function ($query) { + $query->orderBy($query->qualifyColumn('id')); + }); + + $user = HasOneThroughOfManyTestUser::create(); + $relation = $user->price_without_global_scope(); + $this->assertSame('select "prices".* from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."id") as "id_aggregate", min("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" where "published_at" < ? and "intermediates"."user_id" = ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "published_at" < ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."id_aggregate" = "prices"."id" and "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql()); + + HasOneThroughOfManyTestPrice::addGlobalScope('test', function ($query) { + }); + } + + public function testQualifyingSubSelectColumn(): void + { + $user = HasOneThroughOfManyTestUser::make(); + $this->assertSame('latest_login.id', $user->latest_login()->qualifySubSelectColumn('id')); + } + + public function testItFailsWhenUsingInvalidAggregate(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid aggregate [count] used within ofMany relation. Available aggregates: MIN, MAX'); + $user = HasOneThroughOfManyTestUser::make(); + $user->latest_login_with_invalid_aggregate(); + } + + public function testItGetsCorrectResults(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->last()->logins()->create(); + $latestLogin = $user->intermediates->first()->logins()->create(); + + $result = $user->latest_login()->getResults(); + $this->assertNotNull($result); + $this->assertSame($latestLogin->id, $result->id); + } + + public function testResultDoesNotHaveAggregateColumn(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(1)->create(); + $user->intermediates->first()->logins()->create(); + + $result = $user->latest_login()->getResults(); + $this->assertNotNull($result); + $this->assertFalse(isset($result->id_aggregate)); + } + + public function testItGetsCorrectResultsUsingShortcutMethod(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->last()->logins()->create(); + $latestLogin = $user->intermediates->first()->logins()->create(); + + $result = $user->latest_login_with_shortcut()->getResults(); + $this->assertNotNull($result); + $this->assertSame($latestLogin->id, $result->id); + } + + public function testItGetsCorrectResultsUsingShortcutReceivingMultipleColumnsMethod(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $price = $user->intermediates->first()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + + $result = $user->price_with_shortcut()->getResults(); + $this->assertNotNull($result); + $this->assertSame($price->id, $result->id); + } + + public function testKeyIsAddedToAggregatesWhenMissing(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $price = $user->intermediates->first()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + + $result = $user->price_without_key_in_aggregates()->getResults(); + $this->assertNotNull($result); + $this->assertSame($price->id, $result->id); + } + + public function testItGetsWithConstraintsCorrectResults(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->last()->logins()->create(); + $user->intermediates->first()->logins()->create(); + + $result = $user->latest_login()->whereKey($previousLogin->getKey())->getResults(); + $this->assertNull($result); + } + + public function testItEagerLoadsCorrectModels(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->logins()->create(); + $latestLogin = $user->intermediates->first()->logins()->create(); + + $user = HasOneThroughOfManyTestUser::with('latest_login')->first(); + + $this->assertTrue($user->relationLoaded('latest_login')); + $this->assertSame($latestLogin->id, $user->latest_login->id); + } + + public function testItJoinsOtherTableInSubQuery(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->first()->logins()->create(); + + $this->assertNull($user->latest_login_with_foo_state); + + $user->unsetRelation('latest_login_with_foo_state'); + $user->intermediates->first()->states()->create([ + 'type' => 'foo', + 'state' => 'draft', + ]); + + $this->assertNotNull($user->latest_login_with_foo_state); + } + + public function testHasNested(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->first()->logins()->create(); + $latestLogin = $user->intermediates->last()->logins()->create(); + + $found = HasOneThroughOfManyTestUser::whereHas('latest_login', function ($query) use ($latestLogin) { + $query->where('logins.id', $latestLogin->id); + })->exists(); + $this->assertTrue($found); + + $found = HasOneThroughOfManyTestUser::whereHas('latest_login', function ($query) use ($previousLogin) { + $query->where('logins.id', $previousLogin->id); + })->exists(); + $this->assertFalse($found); + } + + public function testWithHasNested(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->first()->logins()->create(); + $latestLogin = $user->intermediates->last()->logins()->create(); + + $found = HasOneThroughOfManyTestUser::withWhereHas('latest_login', function ($query) use ($latestLogin) { + $query->where('logins.id', $latestLogin->id); + })->first(); + + $this->assertTrue((bool) $found); + $this->assertTrue($found->relationLoaded('latest_login')); + $this->assertEquals($found->latest_login->id, $latestLogin->id); + + $found = HasOneThroughOfManyTestUser::withWhereHas('latest_login', function ($query) use ($previousLogin) { + $query->where('logins.id', $previousLogin->id); + })->exists(); + + $this->assertFalse($found); + } + + public function testHasCount(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->logins()->create(); + $user->intermediates->first()->logins()->create(); + + $user = HasOneThroughOfManyTestUser::withCount('latest_login')->first(); + $this->assertEquals(1, $user->latest_login_count); + } + + public function testExists(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->last()->logins()->create(); + $latestLogin = $user->intermediates->first()->logins()->create(); + + $this->assertFalse($user->latest_login()->whereKey($previousLogin->getKey())->exists()); + $this->assertTrue($user->latest_login()->whereKey($latestLogin->getKey())->exists()); + } + + public function testIsMethod(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $login1 = $user->intermediates->last()->logins()->create(); + $login2 = $user->intermediates->first()->logins()->create(); + + $this->assertFalse($user->latest_login()->is($login1)); + $this->assertTrue($user->latest_login()->is($login2)); + } + + public function testIsNotMethod(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $login1 = $user->intermediates->last()->logins()->create(); + $login2 = $user->intermediates->first()->logins()->create(); + + $this->assertTrue($user->latest_login()->isNot($login1)); + $this->assertFalse($user->latest_login()->isNot($login2)); + } + + public function testGet(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $previousLogin = $user->intermediates->last()->logins()->create(); + $latestLogin = $user->intermediates->first()->logins()->create(); + + $latestLogins = $user->latest_login()->get(); + $this->assertCount(1, $latestLogins); + $this->assertSame($latestLogin->id, $latestLogins->first()->id); + + $latestLogins = $user->latest_login()->whereKey($previousLogin->getKey())->get(); + $this->assertCount(0, $latestLogins); + } + + public function testCount(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->logins()->create(); + $user->intermediates->first()->logins()->create(); + + $this->assertSame(1, $user->latest_login()->count()); + } + + public function testAggregate(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $firstLogin = $user->intermediates->first()->logins()->create(); + $user->intermediates->last()->logins()->create(); + + $user = HasOneThroughOfManyTestUser::first(); + $this->assertSame($firstLogin->id, $user->first_login->id); + } + + public function testJoinConstraints(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->states()->create([ + 'type' => 'foo', + 'state' => 'draft', + ]); + $currentForState = $user->intermediates->first()->states()->create([ + 'type' => 'foo', + 'state' => 'active', + ]); + $user->intermediates->first()->states()->create([ + 'type' => 'bar', + 'state' => 'baz', + ]); + + $user = HasOneThroughOfManyTestUser::first(); + $this->assertSame($currentForState->id, $user->foo_state->id); + } + + public function testMultipleAggregates(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user->intermediates->last()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $price = $user->intermediates->first()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + + $user = HasOneThroughOfManyTestUser::first(); + $this->assertSame($price->id, $user->price->id); + } + + public function testEagerLoadingWithMultipleAggregates(): void + { + $user1 = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + $user2 = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + + $user1->intermediates->last()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $user1Price = $user1->intermediates->first()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $user1->intermediates->first()->prices()->create([ + 'published_at' => '2021-04-01 00:00:00', + ]); + + $user2Price = $user2->intermediates->last()->prices()->create([ + 'published_at' => '2021-05-01 00:00:00', + ]); + $user2->intermediates->first()->prices()->create([ + 'published_at' => '2021-04-01 00:00:00', + ]); + + $users = HasOneThroughOfManyTestUser::with('price')->get(); + + $this->assertNotNull($users[0]->price); + $this->assertSame($user1Price->id, $users[0]->price->id); + + $this->assertNotNull($users[1]->price); + $this->assertSame($user2Price->id, $users[1]->price->id); + } + + public function testWithExists(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(1)->create(); + + $user = HasOneThroughOfManyTestUser::withExists('latest_login')->first(); + $this->assertFalse($user->latest_login_exists); + + $user->intermediates->first()->logins()->create(); + $user = HasOneThroughOfManyTestUser::withExists('latest_login')->first(); + $this->assertTrue($user->latest_login_exists); + } + + public function testWithExistsWithConstraintsInJoinSubSelect(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(1)->create(); + $user = HasOneThroughOfManyTestUser::withExists('foo_state')->first(); + + $this->assertFalse($user->foo_state_exists); + + $user->intermediates->first()->states()->create([ + 'type' => 'foo', + 'state' => 'bar', + ]); + $user = HasOneThroughOfManyTestUser::withExists('foo_state')->first(); + $this->assertTrue($user->foo_state_exists); + } + + public function testWithSoftDeletes(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(1)->create(); + $user->intermediates->first()->logins()->create(); + $user->latest_login_with_soft_deletes; + $this->assertNotNull($user->latest_login_with_soft_deletes); + } + + public function testWithConstraintNotInAggregate(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + + $previousFoo = $user->intermediates->last()->states()->create([ + 'type' => 'foo', + 'state' => 'bar', + 'updated_at' => '2020-01-01 00:00:00', + ]); + $newFoo = $user->intermediates->first()->states()->create([ + 'type' => 'foo', + 'state' => 'active', + 'updated_at' => '2021-01-01 12:00:00', + ]); + $newBar = $user->intermediates->first()->states()->create([ + 'type' => 'bar', + 'state' => 'active', + 'updated_at' => '2021-01-01 12:00:00', + ]); + + $this->assertSame($newFoo->id, $user->last_updated_foo_state->id); + } + + public function testItGetsCorrectResultUsingAtLeastTwoAggregatesDistinctFromId(): void + { + $user = HasOneThroughOfManyTestUser::factory()->hasIntermediates(2)->create(); + + $expectedState = $user->intermediates->last()->states()->create([ + 'state' => 'state', + 'type' => 'type', + 'created_at' => '2023-01-01', + 'updated_at' => '2023-01-03', + ]); + + $user->intermediates->first()->states()->create([ + 'state' => 'state', + 'type' => 'type', + 'created_at' => '2023-01-01', + 'updated_at' => '2023-01-02', + ]); + + $this->assertSame($user->latest_updated_latest_created_state->id, $expectedState->id); + } + + protected function connection(): Connection + { + return Eloquent::getConnectionResolver()->connection(); + } + + protected function schema(): Builder + { + return $this->connection()->getSchemaBuilder(); + } +} + +class HasOneThroughOfManyTestUser extends Eloquent +{ + use HasFactory; + protected $table = 'users'; + protected $guarded = []; + public $timestamps = false; + protected static string $factory = HasOneThroughOfManyTestUserFactory::class; + + public function intermediates(): HasMany + { + return $this->hasMany(HasOneThroughOfManyTestIntermediate::class, 'user_id'); + } + + public function logins(): HasManyThrough + { + return $this->through('intermediates')->has('logins'); + } + + public function latest_login(): HasOneThrough + { + return $this->hasOneThrough( + HasOneThroughOfManyTestLogin::class, + HasOneThroughOfManyTestIntermediate::class, + 'user_id', + 'intermediate_id' + )->ofMany(); + } + + public function latest_login_with_soft_deletes(): HasOneThrough + { + return $this->hasOneThrough( + HasOneThroughOfManyTestLoginWithSoftDeletes::class, + HasOneThroughOfManyTestIntermediate::class, + 'user_id', + 'intermediate_id', + )->ofMany(); + } + + public function latest_login_with_shortcut(): HasOneThrough + { + return $this->logins()->one()->latestOfMany(); + } + + public function latest_login_with_invalid_aggregate(): HasOneThrough + { + return $this->logins()->one()->ofMany('id', 'count'); + } + + public function latest_login_without_global_scope(): HasOneThrough + { + return $this->logins()->one()->withoutGlobalScopes()->latestOfMany(); + } + + public function first_login(): HasOneThrough + { + return $this->logins()->one()->ofMany('id', 'min'); + } + + public function latest_login_with_foo_state(): HasOneThrough + { + return $this->logins()->one()->ofMany( + ['id' => 'max'], + function ($query) { + $query->join('states', 'states.intermediate_id', 'logins.intermediate_id') + ->where('states.type', 'foo'); + } + ); + } + + public function states(): HasManyThrough + { + return $this->through($this->intermediates()) + ->has(fn ($intermediate) => $intermediate->states()); + } + + public function foo_state(): HasOneThrough + { + return $this->states()->one()->ofMany( + ['id' => 'max'], + function ($q) { + $q->where('type', 'foo'); + } + ); + } + + public function last_updated_foo_state(): HasOneThrough + { + return $this->states()->one()->ofMany([ + 'updated_at' => 'max', + 'id' => 'max', + ], function ($q) { + $q->where('type', 'foo'); + }); + } + + public function prices(): HasManyThrough + { + return $this->throughIntermediates()->hasPrices(); + } + + public function price(): HasOneThrough + { + return $this->prices()->one()->ofMany([ + 'published_at' => 'max', + 'id' => 'max', + ], function ($q) { + $q->where('published_at', '<', now()); + }); + } + + public function price_without_key_in_aggregates(): HasOneThrough + { + return $this->prices()->one()->ofMany(['published_at' => 'MAX']); + } + + public function price_with_shortcut(): HasOneThrough + { + return $this->prices()->one()->latestOfMany(['published_at', 'id']); + } + + public function price_without_global_scope(): HasOneThrough + { + return $this->prices()->one()->withoutGlobalScopes()->ofMany([ + 'published_at' => 'max', + 'id' => 'max', + ], function ($q) { + $q->where('published_at', '<', now()); + }); + } + + public function latest_updated_latest_created_state(): HasOneThrough + { + return $this->states()->one()->ofMany([ + 'updated_at' => 'max', + 'created_at' => 'max', + ]); + } +} + +class HasOneThroughOfManyTestIntermediate extends Eloquent +{ + use HasFactory; + protected $table = 'intermediates'; + protected $guarded = []; + public $timestamps = false; + protected static string $factory = HasOneThroughOfManyTestIntermediateFactory::class; + + public function logins(): HasMany + { + return $this->hasMany(HasOneThroughOfManyTestLogin::class, 'intermediate_id'); + } + + public function states(): HasMany + { + return $this->hasMany(HasOneThroughOfManyTestState::class, 'intermediate_id'); + } + + public function prices(): HasMany + { + return $this->hasMany(HasOneThroughOfManyTestPrice::class, 'intermediate_id'); + } +} + +class HasOneThroughOfManyTestModel extends Eloquent +{ + public function logins(): HasOneThrough + { + return $this->hasOneThrough( + HasOneThroughOfManyTestLogin::class, + HasOneThroughOfManyTestIntermediate::class, + 'user_id', + 'intermediate_id', + )->ofMany(); + } +} + +class HasOneThroughOfManyTestLogin extends Eloquent +{ + protected $table = 'logins'; + protected $guarded = []; + public $timestamps = false; +} + +class HasOneThroughOfManyTestLoginWithSoftDeletes extends Eloquent +{ + use SoftDeletes; + + protected $table = 'logins'; + protected $guarded = []; + public $timestamps = false; +} + +class HasOneThroughOfManyTestState extends Eloquent +{ + protected $table = 'states'; + protected $guarded = []; + public $timestamps = true; + protected $fillable = ['type', 'state', 'updated_at']; +} + +class HasOneThroughOfManyTestPrice extends Eloquent +{ + protected $table = 'prices'; + protected $guarded = []; + public $timestamps = false; + protected $fillable = ['published_at']; + protected $casts = ['published_at' => 'datetime']; +} + +class HasOneThroughOfManyTestUserFactory extends Factory +{ + protected $model = HasOneThroughOfManyTestUser::class; + + public function definition(): array + { + return []; + } +} + +class HasOneThroughOfManyTestIntermediateFactory extends Factory +{ + protected $model = HasOneThroughOfManyTestIntermediate::class; + + public function definition(): array + { + return ['user_id' => HasOneThroughOfManyTestUser::factory()]; + } +} diff --git a/tests/Database/DatabaseEloquentIntegrationTest.php b/tests/Database/DatabaseEloquentIntegrationTest.php index cc01fefa4bc6..1d176afa00f1 100644 --- a/tests/Database/DatabaseEloquentIntegrationTest.php +++ b/tests/Database/DatabaseEloquentIntegrationTest.php @@ -7,6 +7,7 @@ use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -16,6 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Database\QueryException; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Pagination\AbstractPaginator as Paginator; use Illuminate\Pagination\Cursor; @@ -23,6 +25,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Str; use Illuminate\Tests\Integration\Database\Fixtures\Post; use Illuminate\Tests\Integration\Database\Fixtures\User; use PHPUnit\Framework\TestCase; @@ -80,6 +83,14 @@ protected function createSchema() $table->timestamps(); }); + $this->schema()->create('users_having_uuids', function (Blueprint $table) { + $table->id(); + $table->uuid(); + $table->string('name'); + $table->tinyInteger('role'); + $table->string('role_string'); + }); + foreach (['default', 'second_connection'] as $connection) { $this->schema($connection)->create('users', function ($table) { $table->increments('id'); @@ -159,6 +170,15 @@ protected function createSchema() $table->integer('parent_id')->nullable(); $table->timestamps(); }); + + $this->schema($connection)->create('achievements', function ($table) { + $table->increments('id'); + }); + + $this->schema($connection)->create('eloquent_test_achievement_eloquent_test_user', function ($table) { + $table->integer('eloquent_test_achievement_id'); + $table->integer('eloquent_test_user_id'); + }); } $this->schema($connection)->create('non_incrementing_users', function ($table) { @@ -187,6 +207,8 @@ protected function tearDown(): void Eloquent::unsetConnectionResolver(); Carbon::setTestNow(null); + Str::createUuidsNormally(); + DB::flushQueryLog(); } /** @@ -676,6 +698,223 @@ public function testCreatingModelWithEmptyAttributes() $this->assertFalse($model->wasRecentlyCreated); } + public function testChunk() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->chunk(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('First', $users[0]->name); + $this->assertSame('Second', $users[1]->name); + } else { + $this->assertCount(1, $users); + $this->assertSame('Third', $users[0]->name); + } + + $chunks++; + }); + + $this->assertEquals(2, $chunks); + } + + public function testChunksWithLimitsWhereLimitIsLessThanTotal() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->limit(2)->chunk(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('First', $users[0]->name); + $this->assertSame('Second', $users[1]->name); + } else { + $this->fail('Should only have had one page.'); + } + + $chunks++; + }); + + $this->assertEquals(1, $chunks); + } + + public function testChunksWithLimitsWhereLimitIsMoreThanTotal() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->limit(10)->chunk(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('First', $users[0]->name); + $this->assertSame('Second', $users[1]->name); + } elseif ($page === 2) { + $this->assertCount(1, $users); + $this->assertSame('Third', $users[0]->name); + } else { + $this->fail('Should have had two pages.'); + } + + $chunks++; + }); + + $this->assertEquals(2, $chunks); + } + + public function testChunksWithOffset() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->offset(1)->chunk(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('Second', $users[0]->name); + $this->assertSame('Third', $users[1]->name); + } else { + $this->fail('Should only have had one page.'); + } + + $chunks++; + }); + + $this->assertEquals(1, $chunks); + } + + public function testChunksWithOffsetWhereMoreThanTotal() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->offset(3)->chunk(2, function () use (&$chunks) { + $chunks++; + }); + + $this->assertEquals(0, $chunks); + } + + public function testChunksWithLimitsAndOffsets() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + EloquentTestUser::create(['name' => 'Fourth', 'email' => 'fourth@example.com']); + EloquentTestUser::create(['name' => 'Fifth', 'email' => 'fifth@example.com']); + EloquentTestUser::create(['name' => 'Sixth', 'email' => 'sixth@example.com']); + EloquentTestUser::create(['name' => 'Seventh', 'email' => 'seventh@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->orderBy('id', 'asc')->offset(2)->limit(3)->chunk(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('Third', $users[0]->name); + $this->assertSame('Fourth', $users[1]->name); + } elseif ($page == 2) { + $this->assertCount(1, $users); + $this->assertSame('Fifth', $users[0]->name); + } else { + $this->fail('Should only have had two pages.'); + } + + $chunks++; + }); + + $this->assertEquals(2, $chunks); + } + + public function testChunkByIdWithLimits() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->limit(2)->chunkById(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('First', $users[0]->name); + $this->assertSame('Second', $users[1]->name); + } else { + $this->fail('Should only have had one page.'); + } + + $chunks++; + }); + + $this->assertEquals(1, $chunks); + } + + public function testChunkByIdWithOffsets() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->offset(1)->chunkById(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('Second', $users[0]->name); + $this->assertSame('Third', $users[1]->name); + } else { + $this->fail('Should only have had one page.'); + } + + $chunks++; + }); + + $this->assertEquals(1, $chunks); + } + + public function testChunkByIdWithLimitsAndOffsets() + { + EloquentTestUser::create(['name' => 'First', 'email' => 'first@example.com']); + EloquentTestUser::create(['name' => 'Second', 'email' => 'second@example.com']); + EloquentTestUser::create(['name' => 'Third', 'email' => 'third@example.com']); + EloquentTestUser::create(['name' => 'Fourth', 'email' => 'fourth@example.com']); + EloquentTestUser::create(['name' => 'Fifth', 'email' => 'fifth@example.com']); + EloquentTestUser::create(['name' => 'Sixth', 'email' => 'sixth@example.com']); + EloquentTestUser::create(['name' => 'Seventh', 'email' => 'seventh@example.com']); + + $chunks = 0; + + EloquentTestUser::query()->offset(2)->limit(3)->chunkById(2, function (Collection $users, $page) use (&$chunks) { + if ($page == 1) { + $this->assertCount(2, $users); + $this->assertSame('Third', $users[0]->name); + $this->assertSame('Fourth', $users[1]->name); + } elseif ($page == 2) { + $this->assertCount(1, $users); + $this->assertSame('Fifth', $users[0]->name); + } else { + $this->fail('Should only have had two pages.'); + } + + $chunks++; + }); + + $this->assertEquals(2, $chunks); + } + public function testChunkByIdWithNonIncrementingKey() { EloquentTestNonIncrementingSecond::create(['name' => ' First']); @@ -1253,6 +1492,34 @@ public function testBelongsToManyRelationshipModelsAreProperlyHydratedOverCursor } } + public function testWhereAttachedTo() + { + $user1 = EloquentTestUser::create(['email' => 'user1@gmail.com']); + $user2 = EloquentTestUser::create(['email' => 'user2@gmail.com']); + $user3 = EloquentTestUser::create(['email' => 'user3@gmail.com']); + $achievement1 = EloquentTestAchievement::create(); + $achievement2 = EloquentTestAchievement::create(); + $achievement3 = EloquentTestAchievement::create(); + + $user1->eloquentTestAchievements()->attach([$achievement1]); + $user2->eloquentTestAchievements()->attach([$achievement1, $achievement3]); + $user3->eloquentTestAchievements()->attach([$achievement2, $achievement3]); + + $achievedAchievement1 = EloquentTestUser::whereAttachedTo($achievement1)->get(); + + $this->assertSame(2, $achievedAchievement1->count()); + $this->assertTrue($achievedAchievement1->contains($user1)); + $this->assertTrue($achievedAchievement1->contains($user2)); + + $achievedByUser1or2 = EloquentTestAchievement::whereAttachedTo( + new Collection([$user1, $user2]) + )->get(); + + $this->assertSame(2, $achievedByUser1or2->count()); + $this->assertTrue($achievedByUser1or2->contains($achievement1)); + $this->assertTrue($achievedByUser1or2->contains($achievement3)); + } + public function testBasicHasManyEagerLoading() { $user = EloquentTestUser::create(['email' => 'taylorotwell@gmail.com']); @@ -2244,6 +2511,147 @@ public function testTouchingBiDirectionalChaperonedModelUpdatesAllRelatedTimesta } } + public function testCanFillAndInsert() + { + DB::enableQueryLog(); + Carbon::setTestNow('2025-03-15T07:32:00Z'); + + $this->assertTrue(EloquentTestUser::fillAndInsert([ + ['email' => 'taylor@laravel.com', 'birthday' => null], + ['email' => 'nuno@laravel.com', 'birthday' => new Carbon('1980-01-01')], + ['email' => 'tim@laravel.com', 'birthday' => '1987-11-01', 'created_at' => '2025-01-02T02:00:55', 'updated_at' => Carbon::parse('2025-02-19T11:41:13')], + ])); + + $this->assertCount(1, DB::getQueryLog()); + + $this->assertCount(3, $users = EloquentTestUser::get()); + + $users->take(2)->each(function (EloquentTestUser $user) { + $this->assertEquals(Carbon::parse('2025-03-15T07:32:00Z'), $user->created_at); + $this->assertEquals(Carbon::parse('2025-03-15T07:32:00Z'), $user->updated_at); + }); + + $tim = $users->firstWhere('email', 'tim@laravel.com'); + $this->assertEquals(Carbon::parse('2025-01-02T02:00:55'), $tim->created_at); + $this->assertEquals(Carbon::parse('2025-02-19T11:41:13'), $tim->updated_at); + + $this->assertNull($users[0]->birthday); + $this->assertInstanceOf(\DateTime::class, $users[1]->birthday); + $this->assertInstanceOf(\DateTime::class, $users[2]->birthday); + $this->assertEquals('1987-11-01', $users[2]->birthday->format('Y-m-d')); + + DB::flushQueryLog(); + + $this->assertTrue(EloquentTestWithJSON::fillAndInsert([ + ['id' => 1, 'json' => ['album' => 'Keep It Like a Secret', 'release_date' => '1999-02-02']], + ['id' => 2, 'json' => (object) ['album' => 'You In Reverse', 'release_date' => '2006-04-11']], + ])); + + $this->assertCount(1, DB::getQueryLog()); + + $this->assertCount(2, $testsWithJson = EloquentTestWithJSON::get()); + + $testsWithJson->each(function (EloquentTestWithJSON $testWithJson) { + $this->assertIsArray($testWithJson->json); + $this->assertArrayHasKey('album', $testWithJson->json); + }); + } + + public function testCanFillAndInsertWithUniqueStringIds() + { + Str::createUuidsUsingSequence([ + '00000000-0000-7000-0000-000000000000', + '11111111-0000-7000-0000-000000000000', + '22222222-0000-7000-0000-000000000000', + ]); + + $this->assertTrue(ModelWithUniqueStringIds::fillAndInsert([ + [ + 'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin, + ], + [ + 'name' => 'Nuno', 'role' => 3, 'role_string' => 'admin', + ], + [ + 'name' => 'Dries', 'uuid' => 'bbbb0000-0000-7000-0000-000000000000', + ], + [ + 'name' => 'Chris', + ], + ])); + + $models = ModelWithUniqueStringIds::get(); + + $taylor = $models->firstWhere('name', 'Taylor'); + $nuno = $models->firstWhere('name', 'Nuno'); + $dries = $models->firstWhere('name', 'Dries'); + $chris = $models->firstWhere('name', 'Chris'); + + $this->assertEquals(IntBackedRole::Admin, $taylor->role); + $this->assertEquals(StringBackedRole::Admin, $taylor->role_string); + $this->assertSame('00000000-0000-7000-0000-000000000000', $taylor->uuid); + + $this->assertEquals(IntBackedRole::Admin, $nuno->role); + $this->assertEquals(StringBackedRole::Admin, $nuno->role_string); + $this->assertSame('11111111-0000-7000-0000-000000000000', $nuno->uuid); + + $this->assertEquals(IntBackedRole::User, $dries->role); + $this->assertEquals(StringBackedRole::User, $dries->role_string); + $this->assertSame('bbbb0000-0000-7000-0000-000000000000', $dries->uuid); + + $this->assertEquals(IntBackedRole::User, $chris->role); + $this->assertEquals(StringBackedRole::User, $chris->role_string); + $this->assertSame('22222222-0000-7000-0000-000000000000', $chris->uuid); + } + + public function testFillAndInsertOrIgnore() + { + Str::createUuidsUsingSequence([ + '00000000-0000-7000-0000-000000000000', + '11111111-0000-7000-0000-000000000000', + '22222222-0000-7000-0000-000000000000', + ]); + + $this->assertEquals(1, ModelWithUniqueStringIds::fillAndInsertOrIgnore([ + [ + 'id' => 1, 'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin, + ], + ])); + + $this->assertSame(1, ModelWithUniqueStringIds::fillAndInsertOrIgnore([ + [ + 'id' => 1, 'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin, + ], + [ + 'id' => 2, 'name' => 'Nuno', + ], + ])); + + $models = ModelWithUniqueStringIds::get(); + $this->assertSame('00000000-0000-7000-0000-000000000000', $models->firstWhere('name', 'Taylor')->uuid); + $this->assertSame( + ['uuid' => '22222222-0000-7000-0000-000000000000', 'role' => IntBackedRole::User], + $models->firstWhere('name', 'Nuno')->only('uuid', 'role') + ); + } + + public function testFillAndInsertGetId() + { + Str::createUuidsUsingSequence([ + '00000000-0000-7000-0000-000000000000', + ]); + + DB::enableQueryLog(); + + $this->assertIsInt($newId = ModelWithUniqueStringIds::fillAndInsertGetId([ + 'name' => 'Taylor', + 'role' => IntBackedRole::Admin, + 'role_string' => StringBackedRole::Admin, + ])); + $this->assertCount(1, DB::getRawQueryLog()); + $this->assertSame($newId, ModelWithUniqueStringIds::sole()->id); + } + /** * Helpers... */ @@ -2315,6 +2723,11 @@ public function postWithPhotos() $join->where('photo.imageable_type', 'EloquentTestPost'); }); } + + public function eloquentTestAchievements() + { + return $this->belongsToMany(EloquentTestAchievement::class); + } } class EloquentTestUserWithCustomFriendPivot extends EloquentTestUser @@ -2569,3 +2982,54 @@ public function children() return $this->hasMany(EloquentTouchingCategory::class, 'parent_id')->chaperone(); } } + +class EloquentTestAchievement extends Eloquent +{ + public $timestamps = false; + + protected $table = 'achievements'; + + public function eloquentTestUsers() + { + return $this->belongsToMany(EloquentTestUser::class); + } +} + +class ModelWithUniqueStringIds extends Eloquent +{ + use HasUuids; + + public $timestamps = false; + + protected $table = 'users_having_uuids'; + + protected function casts() + { + return [ + 'role' => IntBackedRole::class, + 'role_string' => StringBackedRole::class, + ]; + } + + protected $attributes = [ + 'role' => IntBackedRole::User, + 'role_string' => StringBackedRole::User, + ]; + + public function uniqueIds() + { + return ['uuid']; + } +} + +enum IntBackedRole: int +{ + case User = 1; + case Admin = 3; +} + +enum StringBackedRole: string +{ + case User = 'user'; + case Admin = 'admin'; +} diff --git a/tests/Database/DatabaseEloquentInverseRelationTest.php b/tests/Database/DatabaseEloquentInverseRelationTest.php index f860e297a410..6f1e9606291e 100755 --- a/tests/Database/DatabaseEloquentInverseRelationTest.php +++ b/tests/Database/DatabaseEloquentInverseRelationTest.php @@ -288,7 +288,8 @@ public function testOnlyHydratesInverseRelationOnModels() [], new HasInverseRelationRelatedStub(), 'foo', - new class() {}, + new class() { + }, new HasInverseRelationRelatedStub(), ]); } @@ -378,7 +379,7 @@ public function exposeGetPossibleInverseRelations(): array return $this->getPossibleInverseRelations(); } - public function exposeGuessInverseRelation(): string|null + public function exposeGuessInverseRelation(): ?string { return $this->guessInverseRelation(); } diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 470164cfeec8..d7f6f1538b72 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -26,6 +26,7 @@ use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection; use Illuminate\Database\Eloquent\Casts\AsEnumArrayObject; use Illuminate\Database\Eloquent\Casts\AsEnumCollection; +use Illuminate\Database\Eloquent\Casts\AsHtmlString; use Illuminate\Database\Eloquent\Casts\AsStringable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; @@ -45,6 +46,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Collection as BaseCollection; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\HtmlString; use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Stringable; use InvalidArgumentException; @@ -264,6 +266,24 @@ public function testDirtyOnCastedStringable() $this->assertTrue($model->isDirty('asStringableAttribute')); } + public function testDirtyOnCastedHtmlString() + { + $model = new EloquentModelCastingStub; + $model->setRawAttributes([ + 'asHtmlStringAttribute' => '
foo bar
', + ]); + $model->syncOriginal(); + + $this->assertInstanceOf(HtmlString::class, $model->asHtmlStringAttribute); + $this->assertFalse($model->isDirty('asHtmlStringAttribute')); + + $model->asHtmlStringAttribute = new HtmlString('
foo bar
'); + $this->assertFalse($model->isDirty('asHtmlStringAttribute')); + + $model->asHtmlStringAttribute = new Stringable('
foo baz
'); + $this->assertTrue($model->isDirty('asHtmlStringAttribute')); + } + // public function testDirtyOnCastedEncryptedCollection() // { // $this->encrypter = m::mock(Encrypter::class); @@ -585,6 +605,18 @@ public function testOnly() $this->assertEquals(['first_name' => 'taylor', 'last_name' => 'otwell'], $model->only(['first_name', 'last_name'])); } + public function testExcept() + { + $model = new EloquentModelStub; + $model->first_name = 'taylor'; + $model->last_name = 'otwell'; + $model->project = 'laravel'; + + $this->assertEquals(['first_name' => 'taylor', 'last_name' => 'otwell'], $model->except('project')); + $this->assertEquals(['project' => 'laravel'], $model->except('first_name', 'last_name')); + $this->assertEquals(['project' => 'laravel'], $model->except(['first_name', 'last_name'])); + } + public function testNewInstanceReturnsNewInstanceWithAttributesSet() { $model = new EloquentModelStub; @@ -2261,6 +2293,35 @@ public function testModelIsBootedOnUnserialize() $this->assertTrue(EloquentModelBootingTestStub::isBooted()); } + public function testCallbacksCanBeRunAfterBootingHasFinished() + { + $this->assertFalse(EloquentModelBootingCallbackTestStub::$bootHasFinished); + + $model = new EloquentModelBootingCallbackTestStub(); + + $this->assertTrue($model::$bootHasFinished); + + EloquentModelBootingCallbackTestStub::unboot(); + } + + public function testBootedCallbacksAreSeparatedByClass() + { + $this->assertFalse(EloquentModelBootingCallbackTestStub::$bootHasFinished); + + $model = new EloquentModelBootingCallbackTestStub(); + + $this->assertTrue($model::$bootHasFinished); + + $this->assertFalse(EloquentChildModelBootingCallbackTestStub::$bootHasFinished); + + $model = new EloquentChildModelBootingCallbackTestStub(); + + $this->assertTrue($model::$bootHasFinished); + + EloquentModelBootingCallbackTestStub::unboot(); + EloquentChildModelBootingCallbackTestStub::unboot(); + } + public function testModelsTraitIsInitialized() { $model = new EloquentModelStubWithTrait; @@ -2472,6 +2533,7 @@ public function testModelAttributesAreCastedWhenPresentInCastsPropertyOrCastsMet $obj->foo = 'bar'; $model->arrayAttribute = $obj; $model->jsonAttribute = ['foo' => 'bar']; + $model->jsonAttributeWithUnicode = ['こんにちは' => '世界']; $model->dateAttribute = '1969-07-20'; $model->datetimeAttribute = '1969-07-20 22:56:00'; $model->timestampAttribute = '1969-07-20 22:56:00'; @@ -2486,12 +2548,15 @@ public function testModelAttributesAreCastedWhenPresentInCastsPropertyOrCastsMet $this->assertIsObject($model->objectAttribute); $this->assertIsArray($model->arrayAttribute); $this->assertIsArray($model->jsonAttribute); + $this->assertIsArray($model->jsonAttributeWithUnicode); $this->assertTrue($model->boolAttribute); $this->assertFalse($model->booleanAttribute); $this->assertEquals($obj, $model->objectAttribute); $this->assertEquals(['foo' => 'bar'], $model->arrayAttribute); $this->assertEquals(['foo' => 'bar'], $model->jsonAttribute); $this->assertSame('{"foo":"bar"}', $model->jsonAttributeValue()); + $this->assertEquals(['こんにちは' => '世界'], $model->jsonAttributeWithUnicode); + $this->assertSame('{"こんにちは":"世界"}', $model->jsonAttributeWithUnicodeValue()); $this->assertInstanceOf(Carbon::class, $model->dateAttribute); $this->assertInstanceOf(Carbon::class, $model->datetimeAttribute); $this->assertInstanceOf(BaseCollection::class, $model->collectionAttribute); @@ -2510,12 +2575,14 @@ public function testModelAttributesAreCastedWhenPresentInCastsPropertyOrCastsMet $this->assertIsObject($arr['objectAttribute']); $this->assertIsArray($arr['arrayAttribute']); $this->assertIsArray($arr['jsonAttribute']); + $this->assertIsArray($arr['jsonAttributeWithUnicode']); $this->assertIsArray($arr['collectionAttribute']); $this->assertTrue($arr['boolAttribute']); $this->assertFalse($arr['booleanAttribute']); $this->assertEquals($obj, $arr['objectAttribute']); $this->assertEquals(['foo' => 'bar'], $arr['arrayAttribute']); $this->assertEquals(['foo' => 'bar'], $arr['jsonAttribute']); + $this->assertEquals(['こんにちは' => '世界'], $arr['jsonAttributeWithUnicode']); $this->assertSame('1969-07-20 00:00:00', $arr['dateAttribute']); $this->assertSame('1969-07-20 22:56:00', $arr['datetimeAttribute']); $this->assertEquals(-14173440, $arr['timestampAttribute']); @@ -2544,6 +2611,7 @@ public function testModelAttributeCastingPreservesNull() $model->objectAttribute = null; $model->arrayAttribute = null; $model->jsonAttribute = null; + $model->jsonAttributeWithUnicode = null; $model->dateAttribute = null; $model->datetimeAttribute = null; $model->timestampAttribute = null; @@ -2559,6 +2627,7 @@ public function testModelAttributeCastingPreservesNull() $this->assertNull($attributes['objectAttribute']); $this->assertNull($attributes['arrayAttribute']); $this->assertNull($attributes['jsonAttribute']); + $this->assertNull($attributes['jsonAttributeWithUnicode']); $this->assertNull($attributes['dateAttribute']); $this->assertNull($attributes['datetimeAttribute']); $this->assertNull($attributes['timestampAttribute']); @@ -2572,6 +2641,7 @@ public function testModelAttributeCastingPreservesNull() $this->assertNull($model->objectAttribute); $this->assertNull($model->arrayAttribute); $this->assertNull($model->jsonAttribute); + $this->assertNull($model->jsonAttributeWithUnicode); $this->assertNull($model->dateAttribute); $this->assertNull($model->datetimeAttribute); $this->assertNull($model->timestampAttribute); @@ -2587,6 +2657,7 @@ public function testModelAttributeCastingPreservesNull() $this->assertNull($array['objectAttribute']); $this->assertNull($array['arrayAttribute']); $this->assertNull($array['jsonAttribute']); + $this->assertNull($array['jsonAttributeWithUnicode']); $this->assertNull($array['dateAttribute']); $this->assertNull($array['datetimeAttribute']); $this->assertNull($array['timestampAttribute']); @@ -2603,11 +2674,45 @@ public function testModelAttributeCastingFailsOnUnencodableData() $obj = new stdClass; $obj->foo = "b\xF8r"; $model->arrayAttribute = $obj; + + $model->getAttributes(); + } + + public function testModelJsonCastingFailsOnUnencodableData() + { + $this->expectException(JsonEncodingException::class); + $this->expectExceptionMessage('Unable to encode attribute [jsonAttribute] for model [Illuminate\Tests\Database\EloquentModelCastingStub] to JSON: Malformed UTF-8 characters, possibly incorrectly encoded.'); + + $model = new EloquentModelCastingStub; $model->jsonAttribute = ['foo' => "b\xF8r"]; $model->getAttributes(); } + public function testModelAttributeCastingFailsOnUnencodableDataWithUnicode() + { + $this->expectException(JsonEncodingException::class); + $this->expectExceptionMessage('Unable to encode attribute [jsonAttributeWithUnicode] for model [Illuminate\Tests\Database\EloquentModelCastingStub] to JSON: Malformed UTF-8 characters, possibly incorrectly encoded.'); + + $model = new EloquentModelCastingStub; + $model->jsonAttributeWithUnicode = ['foo' => "b\xF8r"]; + + $model->getAttributes(); + } + + public function testJsonCastingRespectsUnicodeOption() + { + $data = ['こんにちは' => '世界']; + $model = new EloquentModelCastingStub; + $model->jsonAttribute = $data; + $model->jsonAttributeWithUnicode = $data; + + $this->assertSame('{"\u3053\u3093\u306b\u3061\u306f":"\u4e16\u754c"}', $model->jsonAttributeValue()); + $this->assertSame('{"こんにちは":"世界"}', $model->jsonAttributeWithUnicodeValue()); + $this->assertSame(['こんにちは' => '世界'], $model->jsonAttribute); + $this->assertSame(['こんにちは' => '世界'], $model->jsonAttributeWithUnicode); + } + public function testModelAttributeCastingWithFloats() { $model = new EloquentModelCastingStub; @@ -3028,6 +3133,7 @@ public function testGetOriginalCastsAttributes() $collection = collect($array); $model->arrayAttribute = $array; $model->jsonAttribute = $array; + $model->jsonAttributeWithUnicode = $array; $model->collectionAttribute = $collection; $model->syncOriginal(); @@ -3044,6 +3150,9 @@ public function testGetOriginalCastsAttributes() $model->jsonAttribute = [ 'foo' => 'bar2', ]; + $model->jsonAttributeWithUnicode = [ + 'foo' => 'bar2', + ]; $model->collectionAttribute = collect([ 'foo' => 'bar2', ]); @@ -3080,6 +3189,10 @@ public function testGetOriginalCastsAttributes() $this->assertEquals(['foo' => 'bar'], $model->getOriginal('jsonAttribute')); $this->assertEquals(['foo' => 'bar2'], $model->getAttribute('jsonAttribute')); + $this->assertEquals($array, $model->getOriginal('jsonAttributeWithUnicode')); + $this->assertEquals(['foo' => 'bar'], $model->getOriginal('jsonAttributeWithUnicode')); + $this->assertEquals(['foo' => 'bar2'], $model->getAttribute('jsonAttributeWithUnicode')); + $this->assertEquals(['foo' => 'bar'], $model->getOriginal('collectionAttribute')->toArray()); $this->assertEquals(['foo' => 'bar2'], $model->getAttribute('collectionAttribute')->toArray()); } @@ -3110,7 +3223,7 @@ public function testCastsMethodIsTakenInConsiderationOnSerialization() $this->assertEquals(1, $model->getAttribute('duplicatedAttribute')); } - public function testsCastOnArrayFormatWithOneElement() + public function testCastOnArrayFormatWithOneElement() { $model = new EloquentModelCastingStub; $model->setRawAttributes([ @@ -3518,6 +3631,7 @@ class EloquentModelBootingTestStub extends Model public static function unboot() { unset(static::$booted[static::class]); + unset(static::$bootedCallbacks[static::class]); } public static function isBooted() @@ -3596,6 +3710,7 @@ class EloquentModelCastingStub extends Model 'boolAttribute' => 'bool', 'objectAttribute' => 'object', 'jsonAttribute' => 'json', + 'jsonAttributeWithUnicode' => 'json:unicode', 'dateAttribute' => 'date', 'timestampAttribute' => 'timestamp', 'ascollectionAttribute' => AsCollection::class, @@ -3617,6 +3732,7 @@ protected function casts(): array 'datetimeAttribute' => 'datetime', 'asarrayobjectAttribute' => AsArrayObject::class, 'asStringableAttribute' => AsStringable::class, + 'asHtmlStringAttribute' => AsHtmlString::class, 'asCustomCollectionAttribute' => AsCollection::using(CustomCollection::class), 'asEncryptedArrayObjectAttribute' => AsEncryptedArrayObject::class, 'asEncryptedCustomCollectionAttribute' => AsEncryptedCollection::using(CustomCollection::class), @@ -3633,6 +3749,11 @@ public function jsonAttributeValue() return $this->attributes['jsonAttribute']; } + public function jsonAttributeWithUnicodeValue() + { + return $this->attributes['jsonAttributeWithUnicode']; + } + protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d H:i:s'); @@ -4030,3 +4151,30 @@ class EloquentModelWithUseFactoryAttribute extends Model { use HasFactory; } + +trait EloquentTraitBootingCallbackTestStub +{ + public static function bootEloquentTraitBootingCallbackTestStub() + { + static::whenBooted(fn () => static::$bootHasFinished = true); + } +} + +class EloquentModelBootingCallbackTestStub extends Model +{ + use EloquentTraitBootingCallbackTestStub; + + public static bool $bootHasFinished = false; + + public static function unboot() + { + unset(static::$booted[static::class]); + unset(static::$bootedCallbacks[static::class]); + static::$bootHasFinished = false; + } +} + +class EloquentChildModelBootingCallbackTestStub extends EloquentModelBootingCallbackTestStub +{ + public static bool $bootHasFinished = false; +} diff --git a/tests/Database/DatabaseEloquentResourceCollectionTest.php b/tests/Database/DatabaseEloquentResourceCollectionTest.php new file mode 100644 index 000000000000..e2a1232c3694 --- /dev/null +++ b/tests/Database/DatabaseEloquentResourceCollectionTest.php @@ -0,0 +1,51 @@ +toResourceCollection(EloquentResourceCollectionTestResource::class); + + $this->assertInstanceOf(JsonResource::class, $resource); + } + + public function testItThrowsExceptionWhenResourceCannotBeFound() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to find resource class for model [Illuminate\Tests\Database\Fixtures\Models\EloquentResourceCollectionTestModel].'); + + $collection = new Collection([ + new EloquentResourceCollectionTestModel(), + ]); + $collection->toResourceCollection(); + } + + public function testItCanGuessResourceWhenNotProvided() + { + $collection = new Collection([ + new EloquentResourceCollectionTestModel(), + ]); + + class_alias(EloquentResourceCollectionTestResource::class, 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceCollectionTestModelResource'); + + $resource = $collection->toResourceCollection(); + + $this->assertInstanceOf(JsonResource::class, $resource); + } +} + +class EloquentResourceCollectionTestResource extends JsonResource +{ + // +} diff --git a/tests/Database/DatabaseEloquentResourceModelTest.php b/tests/Database/DatabaseEloquentResourceModelTest.php new file mode 100644 index 000000000000..0be4eb3ad83d --- /dev/null +++ b/tests/Database/DatabaseEloquentResourceModelTest.php @@ -0,0 +1,67 @@ +toResource(EloquentResourceTestJsonResource::class); + + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource); + $this->assertSame($model, $resource->resource); + } + + public function testItThrowsExceptionWhenResourceCannotBeFound() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to find resource class for model [Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModel].'); + + $model = new EloquentResourceTestResourceModel(); + $model->toResource(); + } + + public function testItCanGuessResourceWhenNotProvided() + { + $model = new EloquentResourceTestResourceModelWithGuessableResource(); + + class_alias(EloquentResourceTestJsonResource::class, 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModelWithGuessableResourceResource'); + + $resource = $model->toResource(); + + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource); + $this->assertSame($model, $resource->resource); + } + + public function testItCanGuessResourceWhenNotProvidedWithNonResourceSuffix() + { + $model = new EloquentResourceTestResourceModelWithGuessableResource(); + + class_alias(EloquentResourceTestJsonResource::class, 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModelWithGuessableResource'); + + $resource = $model->toResource(); + + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource); + $this->assertSame($model, $resource->resource); + } + + public function testItCanGuessResourceName() + { + $model = new EloquentResourceTestResourceModel(); + $this->assertEquals([ + 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModelResource', + 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModel', + ], $model::guessResourceName()); + } +} + +class EloquentResourceTestJsonResource extends JsonResource +{ + // +} diff --git a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php index c4a59964bce0..195e2dfd7e17 100644 --- a/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php +++ b/tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php @@ -957,9 +957,7 @@ public function testMorphToNonSoftDeletingModel() public function testSelfReferencingRelationshipWithSoftDeletes() { - /* - * https://github.com/laravel/framework/issues/42075 - */ + // https://github.com/laravel/framework/issues/42075 [$taylor, $abigail] = $this->createUsers(); $this->assertCount(1, $abigail->self_referencing); diff --git a/tests/Database/DatabaseEloquentWithAttributesPendingTest.php b/tests/Database/DatabaseEloquentWithAttributesPendingTest.php new file mode 100644 index 000000000000..a416e7702cdc --- /dev/null +++ b/tests/Database/DatabaseEloquentWithAttributesPendingTest.php @@ -0,0 +1,153 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + $db->bootEloquent(); + $db->setAsGlobal(); + } + + protected function tearDown(): void + { + $this->schema()->dropIfExists((new PendingAttributesModel)->getTable()); + } + + public function testAddsAttributes(): void + { + $key = 'a key'; + $value = 'the value'; + + $query = PendingAttributesModel::query() + ->withAttributes([$key => $value], asConditions: false); + + $model = $query->make(); + + $this->assertSame($value, $model->$key); + } + + public function testDoesNotAddWheres(): void + { + $key = 'a key'; + $value = 'the value'; + + $query = PendingAttributesModel::query() + ->withAttributes([$key => $value], asConditions: false); + + $wheres = $query->toBase()->wheres; + + // Ensure no wheres exist + $this->assertEmpty($wheres); + } + + public function testAddsWithCasts(): void + { + $query = PendingAttributesModel::query() + ->withAttributes([ + 'is_admin' => 1, + 'first_name' => 'FIRST', + 'last_name' => 'LAST', + 'type' => PendingAttributesEnum::internal, + ], asConditions: false); + + $model = $query->make(); + + $this->assertSame(true, $model->is_admin); + $this->assertSame('First', $model->first_name); + $this->assertSame('Last', $model->last_name); + $this->assertSame(PendingAttributesEnum::internal, $model->type); + + $this->assertEqualsCanonicalizing([ + 'is_admin' => 1, + 'first_name' => 'first', + 'last_name' => 'last', + 'type' => 'int', + ], $model->getAttributes()); + } + + public function testAddsWithCastsViaDb(): void + { + $this->bootTable(); + + $query = PendingAttributesModel::query() + ->withAttributes([ + 'is_admin' => 1, + 'first_name' => 'FIRST', + 'last_name' => 'LAST', + 'type' => PendingAttributesEnum::internal, + ], asConditions: false); + + $query->create(); + + $model = PendingAttributesModel::first(); + + $this->assertSame(true, $model->is_admin); + $this->assertSame('First', $model->first_name); + $this->assertSame('Last', $model->last_name); + $this->assertSame(PendingAttributesEnum::internal, $model->type); + } + + protected function bootTable(): void + { + $this->schema()->create((new PendingAttributesModel)->getTable(), function ($table) { + $table->id(); + $table->boolean('is_admin'); + $table->string('first_name'); + $table->string('last_name'); + $table->string('type'); + $table->timestamps(); + }); + } + + protected function schema(): Builder + { + return PendingAttributesModel::getConnectionResolver()->connection()->getSchemaBuilder(); + } +} + +class PendingAttributesModel extends Model +{ + protected $guarded = []; + + protected $casts = [ + 'is_admin' => 'boolean', + 'type' => PendingAttributesEnum::class, + ]; + + public function setFirstNameAttribute(string $value): void + { + $this->attributes['first_name'] = strtolower($value); + } + + public function getFirstNameAttribute(?string $value): string + { + return ucfirst($value); + } + + protected function lastName(): Attribute + { + return Attribute::make( + get: fn (string $value) => ucfirst($value), + set: fn (string $value) => strtolower($value), + ); + } +} + +enum PendingAttributesEnum: string +{ + case internal = 'int'; +} diff --git a/tests/Database/DatabaseMariaDbBuilderTest.php b/tests/Database/DatabaseMariaDbBuilderTest.php index 9f26f35ed6a3..35d260407861 100644 --- a/tests/Database/DatabaseMariaDbBuilderTest.php +++ b/tests/Database/DatabaseMariaDbBuilderTest.php @@ -1,6 +1,6 @@ shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8mb4'); $connection->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8mb4_unicode_ci'); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); @@ -33,9 +33,9 @@ public function testCreateDatabase() public function testDropDatabaseIfExists() { - $grammar = new MariaDbGrammar; - $connection = m::mock(Connection::class); + $grammar = new MariaDbGrammar($connection); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( 'drop database if exists `my_database_a`' diff --git a/tests/Database/DatabaseMariaDbQueryGrammarTest.php b/tests/Database/DatabaseMariaDbQueryGrammarTest.php index f5a5a4dc8975..e86e5fd9193e 100755 --- a/tests/Database/DatabaseMariaDbQueryGrammarTest.php +++ b/tests/Database/DatabaseMariaDbQueryGrammarTest.php @@ -18,8 +18,7 @@ public function testToRawSql() { $connection = m::mock(Connection::class); $connection->shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); - $grammar = new MariaDbGrammar; - $grammar->setConnection($connection); + $grammar = new MariaDbGrammar($connection); $query = $grammar->substituteBindingsIntoRawSql( 'select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = ?', diff --git a/tests/Database/DatabaseMariaDbSchemaBuilderTest.php b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php index d8d7a64c17cc..eed08f0d5d48 100755 --- a/tests/Database/DatabaseMariaDbSchemaBuilderTest.php +++ b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php @@ -38,7 +38,7 @@ public function testGetColumnListing() $connection->shouldReceive('getDatabaseName')->andReturn('db'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileColumns')->with('db', 'prefix_table')->once()->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'prefix_table')->once()->andReturn('sql'); $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'column']]); $builder = new MariaDbBuilder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); diff --git a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php index 85d0de26f311..1224247e20de 100755 --- a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php +++ b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php @@ -1,12 +1,13 @@ create(); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ @@ -50,14 +51,15 @@ public function testBasicCreateTable() 'alter table `users` add `email` varchar(255) not null', ], $statements); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->uuid('id')->primary(); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); + $conn->shouldReceive('getServerVersion')->andReturn('10.7.0'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->uuid('id')->primary(); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table `users` (`id` uuid not null, primary key (`id`))', $statements[0]); @@ -65,17 +67,17 @@ public function testBasicCreateTable() public function testAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id')->startingValue(1000); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id')->startingValue(1000); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); @@ -84,10 +86,10 @@ public function testAutoIncrementStartingValue() public function testAddColumnsWithMultipleAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id()->from(100); $blueprint->string('name')->from(200); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertEquals([ 'alter table `users` add `id` bigint unsigned not null auto_increment primary key', @@ -98,32 +100,32 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() public function testEngineCreateTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); $blueprint->engine('InnoDB'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); - $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn('InnoDB'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); @@ -131,32 +133,32 @@ public function testEngineCreateTable() public function testCharsetCollationCreateTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); $blueprint->charset('utf8mb4'); $blueprint->collation('utf8mb4_unicode_ci'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id'); - $blueprint->string('email')->charset('utf8mb4')->collation('utf8mb4_unicode_ci'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email')->charset('utf8mb4')->collation('utf8mb4_unicode_ci'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) character set utf8mb4 collate 'utf8mb4_unicode_ci' not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); @@ -164,17 +166,15 @@ public function testCharsetCollationCreateTable() public function testBasicCreateTableWithPrefix() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(prefix: 'prefix_'); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $grammar = $this->getGrammar(); - $grammar->setTablePrefix('prefix_'); - - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $grammar); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table `prefix_users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); @@ -182,16 +182,16 @@ public function testBasicCreateTableWithPrefix() public function testCreateTemporaryTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->temporary(); $blueprint->increments('id'); $blueprint->string('email'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create temporary table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); @@ -199,9 +199,9 @@ public function testCreateTemporaryTable() public function testDropTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table `users`', $statements[0]); @@ -209,9 +209,9 @@ public function testDropTable() public function testDropTableIfExists() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table if exists `users`', $statements[0]); @@ -219,23 +219,23 @@ public function testDropTableIfExists() public function testDropColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn(['foo', 'bar']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); @@ -243,9 +243,9 @@ public function testDropColumn() public function testDropPrimary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropPrimary(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop primary key', $statements[0]); @@ -253,9 +253,9 @@ public function testDropPrimary() public function testDropUnique() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropUnique('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop index `foo`', $statements[0]); @@ -263,9 +263,9 @@ public function testDropUnique() public function testDropIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIndex('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop index `foo`', $statements[0]); @@ -273,9 +273,9 @@ public function testDropIndex() public function testDropSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->dropSpatialIndex(['coordinates']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` drop index `geo_coordinates_spatialindex`', $statements[0]); @@ -283,9 +283,9 @@ public function testDropSpatialIndex() public function testDropForeign() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropForeign('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop foreign key `foo`', $statements[0]); @@ -293,9 +293,9 @@ public function testDropForeign() public function testDropTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); @@ -303,9 +303,9 @@ public function testDropTimestamps() public function testDropTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); @@ -313,9 +313,9 @@ public function testDropTimestampsTz() public function testDropMorphs() { - $blueprint = new Blueprint('photos'); + $blueprint = new Blueprint($this->getConnection(), 'photos'); $blueprint->dropMorphs('imageable'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `photos` drop index `photos_imageable_type_imageable_id_index`', $statements[0]); @@ -324,9 +324,9 @@ public function testDropMorphs() public function testRenameTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rename('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('rename table `users` to `foo`', $statements[0]); @@ -334,9 +334,9 @@ public function testRenameTable() public function testRenameIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->renameIndex('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` rename index `foo` to `bar`', $statements[0]); @@ -344,9 +344,9 @@ public function testRenameIndex() public function testAddingPrimaryKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add primary key (`foo`)', $statements[0]); @@ -354,9 +354,9 @@ public function testAddingPrimaryKey() public function testAddingPrimaryKeyWithAlgorithm() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo', 'bar', 'hash'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add primary key using hash(`foo`)', $statements[0]); @@ -364,9 +364,9 @@ public function testAddingPrimaryKeyWithAlgorithm() public function testAddingUniqueKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->unique('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add unique `bar`(`foo`)', $statements[0]); @@ -374,9 +374,9 @@ public function testAddingUniqueKey() public function testAddingIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `baz`(`foo`, `bar`)', $statements[0]); @@ -384,9 +384,9 @@ public function testAddingIndex() public function testAddingIndexWithAlgorithm() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz', 'hash'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `baz` using hash(`foo`, `bar`)', $statements[0]); @@ -394,9 +394,9 @@ public function testAddingIndexWithAlgorithm() public function testAddingFulltextIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->fulltext('body'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add fulltext `users_body_fulltext`(`body`)', $statements[0]); @@ -404,9 +404,9 @@ public function testAddingFulltextIndex() public function testAddingSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->spatialIndex('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[0]); @@ -414,9 +414,9 @@ public function testAddingSpatialIndex() public function testAddingFluentSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point')->spatialIndex(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[1]); @@ -424,9 +424,9 @@ public function testAddingFluentSpatialIndex() public function testAddingRawIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rawIndex('(function(column))', 'raw_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `raw_index`((function(column)))', $statements[0]); @@ -434,23 +434,23 @@ public function testAddingRawIndex() public function testAddingForeignKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnDelete(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on delete cascade', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on update cascade', $statements[0]); @@ -458,9 +458,9 @@ public function testAddingForeignKey() public function testAddingIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key', $statements[0]); @@ -468,9 +468,9 @@ public function testAddingIncrementingID() public function testAddingSmallIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` smallint unsigned not null auto_increment primary key', $statements[0]); @@ -478,16 +478,16 @@ public function testAddingSmallIncrementingID() public function testAddingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -495,14 +495,14 @@ public function testAddingID() public function testAddingForeignID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignId = $blueprint->foreignId('foo'); $blueprint->foreignId('company_id')->constrained(); $blueprint->foreignId('laravel_idea_id')->constrained(); $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -520,9 +520,9 @@ public function testAddingForeignID() public function testAddingForeignIdSpecifyingIndexNameInConstraint() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertSame([ 'alter table `users` add `company_id` bigint unsigned not null', 'alter table `users` add constraint `my_index` foreign key (`company_id`) references `companies` (`id`)', @@ -531,9 +531,9 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() public function testAddingBigIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -541,9 +541,9 @@ public function testAddingBigIncrementingID() public function testAddingColumnInTableFirst() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('name')->first(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `name` varchar(255) not null first', $statements[0]); @@ -551,9 +551,9 @@ public function testAddingColumnInTableFirst() public function testAddingColumnAfterAnotherColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('name')->after('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `name` varchar(255) not null after `foo`', $statements[0]); @@ -561,13 +561,13 @@ public function testAddingColumnAfterAnotherColumn() public function testAddingMultipleColumnsAfterAnotherColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->after('foo', function ($blueprint) { $blueprint->string('one'); $blueprint->string('two'); }); $blueprint->string('three'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ 'alter table `users` add `one` varchar(255) not null after `foo`', @@ -578,11 +578,11 @@ public function testAddingMultipleColumnsAfterAnotherColumn() public function testAddingGeneratedColumn() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('price - 5'); $blueprint->integer('discounted_stored')->storedAs('price - 5'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -591,11 +591,11 @@ public function testAddingGeneratedColumn() 'alter table `products` add `discounted_stored` int as (price - 5) stored', ], $statements); - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('price - 5')->nullable(false); $blueprint->integer('discounted_stored')->storedAs('price - 5')->nullable(false); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -607,11 +607,11 @@ public function testAddingGeneratedColumn() public function testAddingGeneratedColumnWithCharset() { - $blueprint = new Blueprint('links'); + $blueprint = new Blueprint($this->getConnection(), 'links'); $blueprint->string('url', 2083)->charset('ascii'); $blueprint->string('url_hash_virtual', 64)->virtualAs('sha2(url, 256)')->charset('ascii'); $blueprint->string('url_hash_stored', 64)->storedAs('sha2(url, 256)')->charset('ascii'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -623,11 +623,11 @@ public function testAddingGeneratedColumnWithCharset() public function testAddingGeneratedColumnByExpression() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs(new Expression('price - 5')); $blueprint->integer('discounted_stored')->storedAs(new Expression('price - 5')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -639,9 +639,9 @@ public function testAddingGeneratedColumnByExpression() public function testAddingInvisibleColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('secret', 64)->nullable(false)->invisible(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `secret` varchar(64) not null invisible', $statements[0]); @@ -649,37 +649,37 @@ public function testAddingInvisibleColumn() public function testAddingString() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(255) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default(new Expression('CURRENT TIMESTAMP')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default CURRENT TIMESTAMP', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default(Foo::BAR); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); @@ -687,9 +687,9 @@ public function testAddingString() public function testAddingText() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->text('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` text not null', $statements[0]); @@ -697,16 +697,16 @@ public function testAddingText() public function testAddingBigInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint not null auto_increment primary key', $statements[0]); @@ -714,16 +714,16 @@ public function testAddingBigInteger() public function testAddingInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` int not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` int not null auto_increment primary key', $statements[0]); @@ -731,9 +731,9 @@ public function testAddingInteger() public function testAddingIncrementsWithStartingValues() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id()->startingValue(1000); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -742,16 +742,16 @@ public function testAddingIncrementsWithStartingValues() public function testAddingMediumInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` mediumint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` mediumint not null auto_increment primary key', $statements[0]); @@ -759,16 +759,16 @@ public function testAddingMediumInteger() public function testAddingSmallInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` smallint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` smallint not null auto_increment primary key', $statements[0]); @@ -776,16 +776,16 @@ public function testAddingSmallInteger() public function testAddingTinyInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint not null auto_increment primary key', $statements[0]); @@ -793,9 +793,9 @@ public function testAddingTinyInteger() public function testAddingFloat() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->float('foo', 5); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` float(5) not null', $statements[0]); @@ -803,9 +803,9 @@ public function testAddingFloat() public function testAddingDouble() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->double('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` double not null', $statements[0]); @@ -813,9 +813,9 @@ public function testAddingDouble() public function testAddingDecimal() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->decimal('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` decimal(5, 2) not null', $statements[0]); @@ -823,9 +823,9 @@ public function testAddingDecimal() public function testAddingBoolean() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->boolean('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint(1) not null', $statements[0]); @@ -833,9 +833,9 @@ public function testAddingBoolean() public function testAddingEnum() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->enum('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `role` enum(\'member\', \'admin\') not null', $statements[0]); @@ -843,9 +843,9 @@ public function testAddingEnum() public function testAddingSet() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->set('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `role` set(\'member\', \'admin\') not null', $statements[0]); @@ -853,9 +853,9 @@ public function testAddingSet() public function testAddingJson() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->json('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); @@ -863,9 +863,9 @@ public function testAddingJson() public function testAddingJsonb() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->jsonb('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); @@ -873,9 +873,9 @@ public function testAddingJsonb() public function testAddingDate() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->date('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); @@ -883,201 +883,201 @@ public function testAddingDate() public function testAddingYear() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->year('birth_year'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); } public function testAddingDateTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); } public function testAddingDateTimeWithDefaultCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrent(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithOnUpdateCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentAndOnUpdateCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentOnUpdateCurrentAndPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo', 3)->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(3) not null default CURRENT_TIMESTAMP(3) on update CURRENT_TIMESTAMP(3)', $statements[0]); } public function testAddingDateTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); } public function testAddingTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); } public function testAddingTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); } public function testAddingTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); } public function testAddingTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); } public function testAddingTimestamp() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); } public function testAddingTimestampWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); } public function testAddingTimestampWithDefault() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at')->default('2015-07-22 11:43:17'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); } public function testAddingTimestampWithDefaultCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrent(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithOnUpdateCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithDefaultCurrentAndOnUpdateCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1) on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); } public function testAddingTimestampTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); } public function testAddingTimeStampTzWithDefault() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at')->default('2015-07-22 11:43:17'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); } public function testAddingTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table `users` add `created_at` timestamp null', @@ -1087,9 +1087,9 @@ public function testAddingTimestamps() public function testAddingTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table `users` add `created_at` timestamp null', @@ -1099,9 +1099,9 @@ public function testAddingTimestampsTz() public function testAddingRememberToken() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rememberToken(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `remember_token` varchar(100) null', $statements[0]); @@ -1109,9 +1109,9 @@ public function testAddingRememberToken() public function testAddingBinary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->binary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` blob not null', $statements[0]); @@ -1119,19 +1119,38 @@ public function testAddingBinary() public function testAddingUuid() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getServerVersion')->andReturn('10.7.0'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->uuid('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` uuid not null', $statements[0]); } + public function testAddingUuidOn106() + { + $conn = $this->getConnection(); + $conn->shouldReceive('getServerVersion')->andReturn('10.6.21'); + + $blueprint = new Blueprint($conn, 'users'); + $blueprint->uuid('foo'); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` char(36) not null', $statements[0]); + } + public function testAddingUuidDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getServerVersion')->andReturn('10.7.0'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->uuid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `uuid` uuid not null', $statements[0]); @@ -1139,14 +1158,17 @@ public function testAddingUuidDefaultsColumnName() public function testAddingForeignUuid() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getServerVersion')->andReturn('10.7.0'); + + $blueprint = new Blueprint($conn, 'users'); $foreignUuid = $blueprint->foreignUuid('foo'); $blueprint->foreignUuid('company_id')->constrained(); $blueprint->foreignUuid('laravel_idea_id')->constrained(); $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ @@ -1164,9 +1186,9 @@ public function testAddingForeignUuid() public function testAddingIpAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(45) not null', $statements[0]); @@ -1174,9 +1196,9 @@ public function testAddingIpAddress() public function testAddingIpAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `ip_address` varchar(45) not null', $statements[0]); @@ -1184,9 +1206,9 @@ public function testAddingIpAddressDefaultsColumnName() public function testAddingMacAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(17) not null', $statements[0]); @@ -1194,9 +1216,9 @@ public function testAddingMacAddress() public function testAddingMacAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `mac_address` varchar(17) not null', $statements[0]); @@ -1204,9 +1226,9 @@ public function testAddingMacAddressDefaultsColumnName() public function testAddingGeometry() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometry not null', $statements[0]); @@ -1214,9 +1236,9 @@ public function testAddingGeometry() public function testAddingGeography() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geography('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometry ref_system_id=4326 not null', $statements[0]); @@ -1224,9 +1246,9 @@ public function testAddingGeography() public function testAddingPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point not null', $statements[0]); @@ -1234,9 +1256,9 @@ public function testAddingPoint() public function testAddingPointWithSrid() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point', 4326); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point ref_system_id=4326 not null', $statements[0]); @@ -1244,9 +1266,9 @@ public function testAddingPointWithSrid() public function testAddingPointWithSridColumn() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point', 4326)->after('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point ref_system_id=4326 not null after `id`', $statements[0]); @@ -1254,9 +1276,9 @@ public function testAddingPointWithSridColumn() public function testAddingLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'linestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` linestring not null', $statements[0]); @@ -1264,9 +1286,9 @@ public function testAddingLineString() public function testAddingPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'polygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` polygon not null', $statements[0]); @@ -1274,9 +1296,9 @@ public function testAddingPolygon() public function testAddingGeometryCollection() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'geometrycollection'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometrycollection not null', $statements[0]); @@ -1284,9 +1306,9 @@ public function testAddingGeometryCollection() public function testAddingMultiPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipoint'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multipoint not null', $statements[0]); @@ -1294,9 +1316,9 @@ public function testAddingMultiPoint() public function testAddingMultiLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multilinestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multilinestring not null', $statements[0]); @@ -1304,9 +1326,9 @@ public function testAddingMultiLineString() public function testAddingMultiPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipolygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multipolygon not null', $statements[0]); @@ -1314,9 +1336,9 @@ public function testAddingMultiPolygon() public function testAddingComment() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo')->comment("Escape ' when using words like it's"); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `foo` varchar(255) not null comment 'Escape \\' when using words like it\\'s'", $statements[0]); @@ -1328,7 +1350,7 @@ public function testCreateDatabase() $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_foo'); $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_foo'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_a', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_a'); $this->assertSame( 'create database `my_database_a` default character set `utf8mb4_foo` default collate `utf8mb4_unicode_ci_foo`', @@ -1339,7 +1361,7 @@ public function testCreateDatabase() $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_bar'); $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_bar'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_b', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_b'); $this->assertSame( 'create database `my_database_b` default character set `utf8mb4_bar` default collate `utf8mb4_unicode_ci_bar`', @@ -1354,38 +1376,38 @@ public function testCreateTableWithVirtualAsColumn() $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->virtualAs('my_column'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column)) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))))", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute->nested'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))))", $statements[0]); @@ -1393,14 +1415,14 @@ public function testCreateTableWithVirtualAsColumn() public function testCreateTableWithVirtualAsColumnWhenJsonColumnHasArrayKey() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"foo\"[0][1]'))))", $statements[0]); @@ -1413,38 +1435,38 @@ public function testCreateTableWithStoredAsColumn() $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->storedAs('my_column'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column) stored) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))) stored)", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute->nested'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))) stored)", $statements[0]); @@ -1469,16 +1491,17 @@ public function testDropDatabaseIfExists() public function testDropAllTables() { - $statement = $this->getGrammar()->compileDropAllTables(['alpha', 'beta', 'gamma']); + $connection = $this->getConnection(); + $statement = $this->getGrammar($connection)->compileDropAllTables(['alpha', 'beta', 'gamma']); - $this->assertSame('drop table `alpha`,`beta`,`gamma`', $statement); + $this->assertSame('drop table `alpha`, `beta`, `gamma`', $statement); } public function testDropAllViews() { $statement = $this->getGrammar()->compileDropAllViews(['alpha', 'beta', 'gamma']); - $this->assertSame('drop view `alpha`,`beta`,`gamma`', $statement); + $this->assertSame('drop view `alpha`, `beta`, `gamma`', $statement); } public function testGrammarsAreMacroable() @@ -1493,13 +1516,32 @@ public function testGrammarsAreMacroable() $this->assertTrue($c); } - protected function getConnection() + protected function getConnection( + ?MariaDbGrammar $grammar = null, + ?MariaDbBuilder $builder = null, + string $prefix = '' + ) { + $connection = m::mock(Connection::class) + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->with('prefix_indexes')->andReturn(null) + ->getMock(); + + $grammar ??= $this->getGrammar($connection); + $builder ??= $this->getBuilder(); + + return $connection + ->shouldReceive('getSchemaGrammar')->andReturn($grammar) + ->shouldReceive('getSchemaBuilder')->andReturn($builder) + ->getMock(); + } + + public function getGrammar(?Connection $connection = null) { - return m::mock(Connection::class); + return new MariaDbGrammar($connection ?? $this->getConnection()); } - public function getGrammar() + public function getBuilder() { - return new MariaDbGrammar; + return mock(MariaDbBuilder::class); } } diff --git a/tests/Database/DatabaseMigrationInstallCommandTest.php b/tests/Database/DatabaseMigrationInstallCommandTest.php index 165b0c39c545..37e180dc12d5 100755 --- a/tests/Database/DatabaseMigrationInstallCommandTest.php +++ b/tests/Database/DatabaseMigrationInstallCommandTest.php @@ -23,6 +23,17 @@ public function testFireCallsRepositoryToInstall() $command->setLaravel(new Application); $repo->shouldReceive('setSource')->once()->with('foo'); $repo->shouldReceive('createRepository')->once(); + $repo->shouldReceive('repositoryExists')->once()->andReturn(false); + + $this->runCommand($command, ['--database' => 'foo']); + } + + public function testFireCallsRepositoryToInstallExists() + { + $command = new InstallCommand($repo = m::mock(MigrationRepositoryInterface::class)); + $command->setLaravel(new Application); + $repo->shouldReceive('setSource')->once()->with('foo'); + $repo->shouldReceive('repositoryExists')->once()->andReturn(true); $this->runCommand($command, ['--database' => 'foo']); } diff --git a/tests/Database/DatabaseMySQLSchemaBuilderTest.php b/tests/Database/DatabaseMySQLSchemaBuilderTest.php index c5fa3ea273f2..78f3900317a2 100755 --- a/tests/Database/DatabaseMySQLSchemaBuilderTest.php +++ b/tests/Database/DatabaseMySQLSchemaBuilderTest.php @@ -38,7 +38,7 @@ public function testGetColumnListing() $connection->shouldReceive('getDatabaseName')->andReturn('db'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileColumns')->with('db', 'prefix_table')->once()->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'prefix_table')->once()->andReturn('sql'); $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'column']]); $builder = new MySqlBuilder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); diff --git a/tests/Database/DatabaseMySqlBuilderTest.php b/tests/Database/DatabaseMySqlBuilderTest.php index 464ce75c741f..3317af8f2b7c 100644 --- a/tests/Database/DatabaseMySqlBuilderTest.php +++ b/tests/Database/DatabaseMySqlBuilderTest.php @@ -17,9 +17,9 @@ protected function tearDown(): void public function testCreateDatabase() { - $grammar = new MySqlGrammar; - $connection = m::mock(Connection::class); + $grammar = new MySqlGrammar($connection); + $connection->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8mb4'); $connection->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8mb4_unicode_ci'); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); @@ -33,9 +33,9 @@ public function testCreateDatabase() public function testDropDatabaseIfExists() { - $grammar = new MySqlGrammar; - $connection = m::mock(Connection::class); + $grammar = new MySqlGrammar($connection); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( 'drop database if exists `my_database_a`' diff --git a/tests/Database/DatabaseMySqlQueryGrammarTest.php b/tests/Database/DatabaseMySqlQueryGrammarTest.php index 2bb285415f78..04f5c1222e96 100755 --- a/tests/Database/DatabaseMySqlQueryGrammarTest.php +++ b/tests/Database/DatabaseMySqlQueryGrammarTest.php @@ -18,8 +18,7 @@ public function testToRawSql() { $connection = m::mock(Connection::class); $connection->shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); - $grammar = new MySqlGrammar; - $grammar->setConnection($connection); + $grammar = new MySqlGrammar($connection); $query = $grammar->substituteBindingsIntoRawSql( 'select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = ?', diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 5a9b7e11ed99..09082dab62df 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -7,6 +7,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\ForeignIdColumnDefinition; use Illuminate\Database\Schema\Grammars\MySqlGrammar; +use Illuminate\Database\Schema\MySqlBuilder; use Illuminate\Tests\Database\Fixtures\Enums\Foo; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -20,29 +21,29 @@ protected function tearDown(): void public function testBasicCreateTable() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ @@ -50,14 +51,14 @@ public function testBasicCreateTable() 'alter table `users` add `email` varchar(255) not null', ], $statements); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->uuid('id')->primary(); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->uuid('id')->primary(); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table `users` (`id` char(36) not null, primary key (`id`))', $statements[0]); @@ -65,17 +66,17 @@ public function testBasicCreateTable() public function testAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id')->startingValue(1000); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id')->startingValue(1000); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); @@ -84,10 +85,10 @@ public function testAutoIncrementStartingValue() public function testAddColumnsWithMultipleAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id()->from(100); $blueprint->string('name')->from(200); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertEquals([ 'alter table `users` add `id` bigint unsigned not null auto_increment primary key', @@ -98,32 +99,32 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() public function testEngineCreateTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); $blueprint->engine('InnoDB'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); - $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id'); - $blueprint->string('email'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn('InnoDB'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); @@ -131,32 +132,32 @@ public function testEngineCreateTable() public function testCharsetCollationCreateTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); $blueprint->charset('utf8mb4'); $blueprint->collation('utf8mb4_unicode_ci'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->increments('id'); - $blueprint->string('email')->charset('utf8mb4')->collation('utf8mb4_unicode_ci'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email')->charset('utf8mb4')->collation('utf8mb4_unicode_ci'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) character set utf8mb4 collate 'utf8mb4_unicode_ci' not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); @@ -164,17 +165,15 @@ public function testCharsetCollationCreateTable() public function testBasicCreateTableWithPrefix() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(prefix: 'prefix_'); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $grammar = $this->getGrammar(); - $grammar->setTablePrefix('prefix_'); - - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $grammar); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table `prefix_users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); @@ -182,16 +181,16 @@ public function testBasicCreateTableWithPrefix() public function testCreateTemporaryTable() { - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->temporary(); $blueprint->increments('id'); $blueprint->string('email'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create temporary table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); @@ -199,9 +198,9 @@ public function testCreateTemporaryTable() public function testDropTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table `users`', $statements[0]); @@ -209,9 +208,9 @@ public function testDropTable() public function testDropTableIfExists() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table if exists `users`', $statements[0]); @@ -219,23 +218,23 @@ public function testDropTableIfExists() public function testDropColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn(['foo', 'bar']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); @@ -243,9 +242,9 @@ public function testDropColumn() public function testDropPrimary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropPrimary(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop primary key', $statements[0]); @@ -253,9 +252,9 @@ public function testDropPrimary() public function testDropUnique() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropUnique('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop index `foo`', $statements[0]); @@ -263,9 +262,9 @@ public function testDropUnique() public function testDropIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIndex('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop index `foo`', $statements[0]); @@ -273,9 +272,9 @@ public function testDropIndex() public function testDropSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->dropSpatialIndex(['coordinates']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` drop index `geo_coordinates_spatialindex`', $statements[0]); @@ -283,9 +282,9 @@ public function testDropSpatialIndex() public function testDropForeign() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropForeign('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop foreign key `foo`', $statements[0]); @@ -293,9 +292,9 @@ public function testDropForeign() public function testDropTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); @@ -303,9 +302,9 @@ public function testDropTimestamps() public function testDropTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); @@ -313,9 +312,9 @@ public function testDropTimestampsTz() public function testDropMorphs() { - $blueprint = new Blueprint('photos'); + $blueprint = new Blueprint($this->getConnection(), 'photos'); $blueprint->dropMorphs('imageable'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `photos` drop index `photos_imageable_type_imageable_id_index`', $statements[0]); @@ -324,9 +323,9 @@ public function testDropMorphs() public function testRenameTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rename('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('rename table `users` to `foo`', $statements[0]); @@ -334,9 +333,9 @@ public function testRenameTable() public function testRenameIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->renameIndex('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` rename index `foo` to `bar`', $statements[0]); @@ -344,9 +343,9 @@ public function testRenameIndex() public function testAddingPrimaryKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add primary key (`foo`)', $statements[0]); @@ -354,9 +353,9 @@ public function testAddingPrimaryKey() public function testAddingPrimaryKeyWithAlgorithm() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo', 'bar', 'hash'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add primary key using hash(`foo`)', $statements[0]); @@ -364,9 +363,9 @@ public function testAddingPrimaryKeyWithAlgorithm() public function testAddingUniqueKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->unique('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add unique `bar`(`foo`)', $statements[0]); @@ -374,9 +373,9 @@ public function testAddingUniqueKey() public function testAddingIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `baz`(`foo`, `bar`)', $statements[0]); @@ -384,9 +383,9 @@ public function testAddingIndex() public function testAddingIndexWithAlgorithm() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz', 'hash'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `baz` using hash(`foo`, `bar`)', $statements[0]); @@ -394,9 +393,9 @@ public function testAddingIndexWithAlgorithm() public function testAddingFulltextIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->fulltext('body'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add fulltext `users_body_fulltext`(`body`)', $statements[0]); @@ -404,9 +403,9 @@ public function testAddingFulltextIndex() public function testAddingSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->spatialIndex('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[0]); @@ -414,9 +413,9 @@ public function testAddingSpatialIndex() public function testAddingFluentSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point')->spatialIndex(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[1]); @@ -424,9 +423,9 @@ public function testAddingFluentSpatialIndex() public function testAddingRawIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rawIndex('(function(column))', 'raw_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add index `raw_index`((function(column)))', $statements[0]); @@ -434,23 +433,23 @@ public function testAddingRawIndex() public function testAddingForeignKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnDelete(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on delete cascade', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on update cascade', $statements[0]); @@ -458,9 +457,9 @@ public function testAddingForeignKey() public function testAddingIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key', $statements[0]); @@ -468,9 +467,9 @@ public function testAddingIncrementingID() public function testAddingSmallIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` smallint unsigned not null auto_increment primary key', $statements[0]); @@ -478,16 +477,16 @@ public function testAddingSmallIncrementingID() public function testAddingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -495,14 +494,14 @@ public function testAddingID() public function testAddingForeignID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignId = $blueprint->foreignId('foo'); $blueprint->foreignId('company_id')->constrained(); $blueprint->foreignId('laravel_idea_id')->constrained(); $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -520,9 +519,9 @@ public function testAddingForeignID() public function testAddingForeignIdSpecifyingIndexNameInConstraint() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertSame([ 'alter table `users` add `company_id` bigint unsigned not null', 'alter table `users` add constraint `my_index` foreign key (`company_id`) references `companies` (`id`)', @@ -531,9 +530,9 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() public function testAddingBigIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -541,9 +540,9 @@ public function testAddingBigIncrementingID() public function testAddingColumnInTableFirst() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('name')->first(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `name` varchar(255) not null first', $statements[0]); @@ -551,9 +550,9 @@ public function testAddingColumnInTableFirst() public function testAddingColumnAfterAnotherColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('name')->after('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `name` varchar(255) not null after `foo`', $statements[0]); @@ -561,13 +560,13 @@ public function testAddingColumnAfterAnotherColumn() public function testAddingMultipleColumnsAfterAnotherColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->after('foo', function ($blueprint) { $blueprint->string('one'); $blueprint->string('two'); }); $blueprint->string('three'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ 'alter table `users` add `one` varchar(255) not null after `foo`', @@ -578,11 +577,11 @@ public function testAddingMultipleColumnsAfterAnotherColumn() public function testAddingGeneratedColumn() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('price - 5'); $blueprint->integer('discounted_stored')->storedAs('price - 5'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -591,11 +590,11 @@ public function testAddingGeneratedColumn() 'alter table `products` add `discounted_stored` int as (price - 5) stored', ], $statements); - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('price - 5')->nullable(false); $blueprint->integer('discounted_stored')->storedAs('price - 5')->nullable(false); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -607,11 +606,11 @@ public function testAddingGeneratedColumn() public function testAddingGeneratedColumnWithCharset() { - $blueprint = new Blueprint('links'); + $blueprint = new Blueprint($this->getConnection(), 'links'); $blueprint->string('url', 2083)->charset('ascii'); $blueprint->string('url_hash_virtual', 64)->virtualAs('sha2(url, 256)')->charset('ascii'); $blueprint->string('url_hash_stored', 64)->storedAs('sha2(url, 256)')->charset('ascii'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -623,11 +622,11 @@ public function testAddingGeneratedColumnWithCharset() public function testAddingGeneratedColumnByExpression() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs(new Expression('price - 5')); $blueprint->integer('discounted_stored')->storedAs(new Expression('price - 5')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ @@ -639,9 +638,9 @@ public function testAddingGeneratedColumnByExpression() public function testAddingInvisibleColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('secret', 64)->nullable(false)->invisible(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `secret` varchar(64) not null invisible', $statements[0]); @@ -649,37 +648,37 @@ public function testAddingInvisibleColumn() public function testAddingString() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(255) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default(new Expression('CURRENT TIMESTAMP')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default CURRENT TIMESTAMP', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default(Foo::BAR); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); @@ -687,9 +686,9 @@ public function testAddingString() public function testAddingText() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->text('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` text not null', $statements[0]); @@ -697,16 +696,16 @@ public function testAddingText() public function testAddingBigInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` bigint not null auto_increment primary key', $statements[0]); @@ -714,16 +713,16 @@ public function testAddingBigInteger() public function testAddingInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` int not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` int not null auto_increment primary key', $statements[0]); @@ -731,9 +730,9 @@ public function testAddingInteger() public function testAddingIncrementsWithStartingValues() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id()->startingValue(1000); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); @@ -742,16 +741,16 @@ public function testAddingIncrementsWithStartingValues() public function testAddingMediumInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` mediumint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` mediumint not null auto_increment primary key', $statements[0]); @@ -759,16 +758,16 @@ public function testAddingMediumInteger() public function testAddingSmallInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` smallint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` smallint not null auto_increment primary key', $statements[0]); @@ -776,16 +775,16 @@ public function testAddingSmallInteger() public function testAddingTinyInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint not null auto_increment primary key', $statements[0]); @@ -793,9 +792,9 @@ public function testAddingTinyInteger() public function testAddingFloat() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->float('foo', 5); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` float(5) not null', $statements[0]); @@ -803,9 +802,9 @@ public function testAddingFloat() public function testAddingDouble() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->double('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` double not null', $statements[0]); @@ -813,9 +812,9 @@ public function testAddingDouble() public function testAddingDecimal() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->decimal('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` decimal(5, 2) not null', $statements[0]); @@ -823,9 +822,9 @@ public function testAddingDecimal() public function testAddingBoolean() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->boolean('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` tinyint(1) not null', $statements[0]); @@ -833,9 +832,9 @@ public function testAddingBoolean() public function testAddingEnum() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->enum('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `role` enum(\'member\', \'admin\') not null', $statements[0]); @@ -843,9 +842,9 @@ public function testAddingEnum() public function testAddingSet() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->set('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `role` set(\'member\', \'admin\') not null', $statements[0]); @@ -853,9 +852,9 @@ public function testAddingSet() public function testAddingJson() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->json('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); @@ -863,9 +862,9 @@ public function testAddingJson() public function testAddingJsonb() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->jsonb('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); @@ -873,9 +872,9 @@ public function testAddingJsonb() public function testAddingDate() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->date('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); @@ -883,201 +882,201 @@ public function testAddingDate() public function testAddingYear() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->year('birth_year'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); } public function testAddingDateTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); } public function testAddingDateTimeWithDefaultCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrent(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithOnUpdateCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentAndOnUpdateCurrent() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo')->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentOnUpdateCurrentAndPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('foo', 3)->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(3) not null default CURRENT_TIMESTAMP(3) on update CURRENT_TIMESTAMP(3)', $statements[0]); } public function testAddingDateTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); } public function testAddingTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); } public function testAddingTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); } public function testAddingTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); } public function testAddingTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); } public function testAddingTimestamp() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); } public function testAddingTimestampWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); } public function testAddingTimestampWithDefault() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at')->default('2015-07-22 11:43:17'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); } public function testAddingTimestampWithDefaultCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrent(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithOnUpdateCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithDefaultCurrentAndOnUpdateCurrentSpecifyingPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1)->useCurrent()->useCurrentOnUpdate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1) on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); } public function testAddingTimestampTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); } public function testAddingTimeStampTzWithDefault() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at')->default('2015-07-22 11:43:17'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); } public function testAddingTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table `users` add `created_at` timestamp null', @@ -1087,9 +1086,9 @@ public function testAddingTimestamps() public function testAddingTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table `users` add `created_at` timestamp null', @@ -1099,9 +1098,9 @@ public function testAddingTimestampsTz() public function testAddingRememberToken() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rememberToken(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `remember_token` varchar(100) null', $statements[0]); @@ -1109,9 +1108,9 @@ public function testAddingRememberToken() public function testAddingBinary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->binary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` blob not null', $statements[0]); @@ -1119,9 +1118,9 @@ public function testAddingBinary() public function testAddingUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` char(36) not null', $statements[0]); @@ -1129,9 +1128,9 @@ public function testAddingUuid() public function testAddingUuidDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `uuid` char(36) not null', $statements[0]); @@ -1139,14 +1138,14 @@ public function testAddingUuidDefaultsColumnName() public function testAddingForeignUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignUuid = $blueprint->foreignUuid('foo'); $blueprint->foreignUuid('company_id')->constrained(); $blueprint->foreignUuid('laravel_idea_id')->constrained(); $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ @@ -1164,9 +1163,9 @@ public function testAddingForeignUuid() public function testAddingIpAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(45) not null', $statements[0]); @@ -1174,9 +1173,9 @@ public function testAddingIpAddress() public function testAddingIpAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `ip_address` varchar(45) not null', $statements[0]); @@ -1184,9 +1183,9 @@ public function testAddingIpAddressDefaultsColumnName() public function testAddingMacAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `foo` varchar(17) not null', $statements[0]); @@ -1194,9 +1193,9 @@ public function testAddingMacAddress() public function testAddingMacAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `mac_address` varchar(17) not null', $statements[0]); @@ -1204,9 +1203,9 @@ public function testAddingMacAddressDefaultsColumnName() public function testAddingGeometry() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometry not null', $statements[0]); @@ -1214,9 +1213,9 @@ public function testAddingGeometry() public function testAddingGeography() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geography('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometry srid 4326 not null', $statements[0]); @@ -1224,9 +1223,9 @@ public function testAddingGeography() public function testAddingPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point not null', $statements[0]); @@ -1234,9 +1233,9 @@ public function testAddingPoint() public function testAddingPointWithSrid() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point', 4326); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point srid 4326 not null', $statements[0]); @@ -1244,9 +1243,9 @@ public function testAddingPointWithSrid() public function testAddingPointWithSridColumn() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point', 4326)->after('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` point srid 4326 not null after `id`', $statements[0]); @@ -1254,9 +1253,9 @@ public function testAddingPointWithSridColumn() public function testAddingLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'linestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` linestring not null', $statements[0]); @@ -1264,9 +1263,9 @@ public function testAddingLineString() public function testAddingPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'polygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` polygon not null', $statements[0]); @@ -1274,9 +1273,9 @@ public function testAddingPolygon() public function testAddingGeometryCollection() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'geometrycollection'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` geometrycollection not null', $statements[0]); @@ -1284,9 +1283,9 @@ public function testAddingGeometryCollection() public function testAddingMultiPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipoint'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multipoint not null', $statements[0]); @@ -1294,9 +1293,9 @@ public function testAddingMultiPoint() public function testAddingMultiLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multilinestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multilinestring not null', $statements[0]); @@ -1304,9 +1303,9 @@ public function testAddingMultiLineString() public function testAddingMultiPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipolygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table `geo` add `coordinates` multipolygon not null', $statements[0]); @@ -1314,9 +1313,9 @@ public function testAddingMultiPolygon() public function testAddingComment() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo')->comment("Escape ' when using words like it's"); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("alter table `users` add `foo` varchar(255) not null comment 'Escape \\' when using words like it\\'s'", $statements[0]); @@ -1324,7 +1323,7 @@ public function testAddingComment() public function testAddingVector() { - $blueprint = new Blueprint('embeddings'); + $blueprint = new Blueprint($this->getConnection(), 'embeddings'); $blueprint->vector('embedding', 384); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); @@ -1338,7 +1337,7 @@ public function testCreateDatabase() $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_foo'); $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_foo'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_a', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_a'); $this->assertSame( 'create database `my_database_a` default character set `utf8mb4_foo` default collate `utf8mb4_unicode_ci_foo`', @@ -1349,7 +1348,7 @@ public function testCreateDatabase() $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_bar'); $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_bar'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_b', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_b'); $this->assertSame( 'create database `my_database_b` default character set `utf8mb4_bar` default collate `utf8mb4_unicode_ci_bar`', @@ -1364,38 +1363,38 @@ public function testCreateTableWithVirtualAsColumn() $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->virtualAs('my_column'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column)) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))))", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute->nested'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))))", $statements[0]); @@ -1403,14 +1402,14 @@ public function testCreateTableWithVirtualAsColumn() public function testCreateTableWithVirtualAsColumnWhenJsonColumnHasArrayKey() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"foo\"[0][1]'))))", $statements[0]); @@ -1423,38 +1422,38 @@ public function testCreateTableWithStoredAsColumn() $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->storedAs('my_column'); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column) stored) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))) stored)", $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute->nested'); - $conn = $this->getConnection(); - $conn->shouldReceive('getConfig')->andReturn(null); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))) stored)", $statements[0]); @@ -1481,14 +1480,30 @@ public function testDropAllTables() { $statement = $this->getGrammar()->compileDropAllTables(['alpha', 'beta', 'gamma']); - $this->assertSame('drop table `alpha`,`beta`,`gamma`', $statement); + $this->assertSame('drop table `alpha`, `beta`, `gamma`', $statement); } public function testDropAllViews() { $statement = $this->getGrammar()->compileDropAllViews(['alpha', 'beta', 'gamma']); - $this->assertSame('drop view `alpha`,`beta`,`gamma`', $statement); + $this->assertSame('drop view `alpha`, `beta`, `gamma`', $statement); + } + + public function testDropAllTablesWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllTables(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop table `schema`.`alpha`, `schema`.`beta`, `schema`.`gamma`', $statement); + } + + public function testDropAllViewsWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllViews(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop view `schema`.`alpha`, `schema`.`beta`, `schema`.`gamma`', $statement); } public function testGrammarsAreMacroable() @@ -1503,13 +1518,33 @@ public function testGrammarsAreMacroable() $this->assertTrue($c); } - protected function getConnection() + protected function getConnection( + ?MySqlGrammar $grammar = null, + ?MySqlBuilder $builder = null, + string $prefix = '' + ) { + $connection = m::mock(Connection::class) + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->with('prefix_indexes')->andReturn(null) + ->shouldReceive('isMaria')->andReturn(false) + ->getMock(); + + $grammar ??= $this->getGrammar($connection); + $builder ??= $this->getBuilder(); + + return $connection + ->shouldReceive('getSchemaGrammar')->andReturn($grammar) + ->shouldReceive('getSchemaBuilder')->andReturn($builder) + ->getMock(); + } + + public function getGrammar(?Connection $connection = null) { - return m::mock(Connection::class); + return new MySqlGrammar($connection ?? $this->getConnection()); } - public function getGrammar() + public function getBuilder() { - return new MySqlGrammar; + return mock(MySqlBuilder::class); } } diff --git a/tests/Database/DatabasePostgresBuilderTest.php b/tests/Database/DatabasePostgresBuilderTest.php index c6f79c130704..466817e48c5e 100644 --- a/tests/Database/DatabasePostgresBuilderTest.php +++ b/tests/Database/DatabasePostgresBuilderTest.php @@ -18,9 +18,9 @@ protected function tearDown(): void public function testCreateDatabase() { - $grammar = new PostgresGrammar; - $connection = m::mock(Connection::class); + $grammar = new PostgresGrammar($connection); + $connection->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( @@ -33,9 +33,9 @@ public function testCreateDatabase() public function testDropDatabaseIfExists() { - $grammar = new PostgresGrammar; - $connection = m::mock(Connection::class); + $grammar = new PostgresGrammar($connection); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( 'drop database if exists "my_database_a"' @@ -142,7 +142,7 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathMissing() $connection->shouldReceive('getConfig')->with('schema')->andReturn(null); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileColumns')->with('public', 'foo')->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'foo')->andReturn('sql'); $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]); $connection->shouldReceive('getTablePrefix'); $processor = m::mock(PostgresProcessor::class); @@ -159,7 +159,7 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathFilled() $connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileColumns')->with('myapp', 'foo')->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'foo')->andReturn('sql'); $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]); $connection->shouldReceive('getTablePrefix'); $processor = m::mock(PostgresProcessor::class); @@ -177,7 +177,7 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathIsUserVari $connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileColumns')->with('foouser', 'foo')->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'foo')->andReturn('sql'); $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]); $connection->shouldReceive('getTablePrefix'); $processor = m::mock(PostgresProcessor::class); @@ -228,8 +228,8 @@ public function testDropAllTablesWhenSearchPathIsString() $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); $grammar->shouldReceive('compileTables')->andReturn('sql'); - $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'public']]); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'public']]); + $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'public', 'schema_qualified_name' => 'public.users']]); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'public', 'schema_qualified_name' => 'public.users']]); $grammar->shouldReceive('compileDropAllTables')->with(['public.users'])->andReturn('drop table "public"."users" cascade'); $connection->shouldReceive('statement')->with('drop table "public"."users" cascade'); $builder = $this->getBuilder($connection); @@ -247,9 +247,9 @@ public function testDropAllTablesWhenSearchPathIsStringOfMany() $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser']]); + $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser', 'schema_qualified_name' => 'foouser.users']]); $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser']]); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser', 'schema_qualified_name' => 'foouser.users']]); $grammar->shouldReceive('compileDropAllTables')->with(['foouser.users'])->andReturn('drop table "foouser"."users" cascade'); $connection->shouldReceive('statement')->with('drop table "foouser"."users" cascade'); $builder = $this->getBuilder($connection); @@ -272,9 +272,9 @@ public function testDropAllTablesWhenSearchPathIsArrayOfMany() $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser']]); + $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser', 'schema_qualified_name' => 'foouser.users']]); $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser']]); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser', 'schema_qualified_name' => 'foouser.users']]); $grammar->shouldReceive('compileDropAllTables')->with(['foouser.users'])->andReturn('drop table "foouser"."users" cascade'); $connection->shouldReceive('statement')->with('drop table "foouser"."users" cascade'); $builder = $this->getBuilder($connection); diff --git a/tests/Database/DatabasePostgresQueryGrammarTest.php b/tests/Database/DatabasePostgresQueryGrammarTest.php index 2fc526946ef5..46b39e991f72 100755 --- a/tests/Database/DatabasePostgresQueryGrammarTest.php +++ b/tests/Database/DatabasePostgresQueryGrammarTest.php @@ -19,8 +19,7 @@ public function testToRawSql() { $connection = m::mock(Connection::class); $connection->shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); - $grammar = new PostgresGrammar; - $grammar->setConnection($connection); + $grammar = new PostgresGrammar($connection); $query = $grammar->substituteBindingsIntoRawSql( 'select * from "users" where \'{}\' ?? \'Hello\\\'\\\'World?\' AND "email" = ?', @@ -36,8 +35,7 @@ public function testCustomOperators() PostgresGrammar::customOperators(['@@>', 1]); $connection = m::mock(Connection::class); - $grammar = new PostgresGrammar; - $grammar->setConnection($connection); + $grammar = new PostgresGrammar($connection); $operators = $grammar->getOperators(); @@ -51,7 +49,10 @@ public function testCustomOperators() public function testCompileTruncate() { - $postgres = new PostgresGrammar; + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + + $postgres = new PostgresGrammar($connection); $builder = m::mock(Builder::class); $builder->from = 'users'; @@ -59,13 +60,13 @@ public function testCompileTruncate() 'truncate "users" restart identity cascade' => [], ], $postgres->compileTruncate($builder)); - PostgresGrammar::cascadeOnTrucate(false); + PostgresGrammar::cascadeOnTruncate(false); $this->assertEquals([ 'truncate "users" restart identity' => [], ], $postgres->compileTruncate($builder)); - PostgresGrammar::cascadeOnTrucate(); + PostgresGrammar::cascadeOnTruncate(); $this->assertEquals([ 'truncate "users" restart identity cascade' => [], diff --git a/tests/Database/DatabasePostgresSchemaBuilderTest.php b/tests/Database/DatabasePostgresSchemaBuilderTest.php index c38c7d183614..0b1124463981 100755 --- a/tests/Database/DatabasePostgresSchemaBuilderTest.php +++ b/tests/Database/DatabasePostgresSchemaBuilderTest.php @@ -21,8 +21,6 @@ public function testHasTable() $connection = m::mock(Connection::class); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getConfig')->with('schema')->andReturn('schema'); - $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $builder = new PostgresBuilder($connection); $grammar->shouldReceive('compileTableExists')->twice()->andReturn('sql'); $connection->shouldReceive('getTablePrefix')->twice()->andReturn('prefix_'); @@ -39,10 +37,7 @@ public function testGetColumnListing() $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $connection->shouldReceive('getConfig')->with('database')->andReturn('db'); - $connection->shouldReceive('getConfig')->with('schema')->andReturn('schema'); - $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); - $grammar->shouldReceive('compileColumns')->with('public', 'prefix_table')->once()->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->with(null, 'prefix_table')->once()->andReturn('sql'); $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'column']]); $builder = new PostgresBuilder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index 097f1a7f7c3d..0c31a6d2d06a 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -8,7 +8,10 @@ use Illuminate\Database\Schema\Builder; use Illuminate\Database\Schema\ForeignIdColumnDefinition; use Illuminate\Database\Schema\Grammars\PostgresGrammar; +use Illuminate\Database\Schema\PostgresBuilder; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; class DatabasePostgresSchemaGrammarTest extends TestCase @@ -20,20 +23,20 @@ protected function tearDown(): void public function testBasicCreateTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); $blueprint->string('name')->collation('nb_NO.utf8'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("id" serial not null primary key, "email" varchar(255) not null, "name" varchar(255) collate "nb_NO.utf8" not null)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ @@ -44,7 +47,7 @@ public function testBasicCreateTable() public function testAddingVector() { - $blueprint = new Blueprint('embeddings'); + $blueprint = new Blueprint($this->getConnection(), 'embeddings'); $blueprint->vector('embedding', 384); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); @@ -54,12 +57,15 @@ public function testAddingVector() public function testCreateTableWithAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); + $connection = $this->getConnection(); + $connection->getSchemaBuilder()->shouldReceive('parseSchemaAndTable')->andReturn([null, 'users']); + + $blueprint = new Blueprint($connection, 'users'); $blueprint->create(); $blueprint->increments('id')->startingValue(1000); $blueprint->string('email'); $blueprint->string('name')->collation('nb_NO.utf8'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('create table "users" ("id" serial not null primary key, "email" varchar(255) not null, "name" varchar(255) collate "nb_NO.utf8" not null)', $statements[0]); @@ -68,11 +74,14 @@ public function testCreateTableWithAutoIncrementStartingValue() public function testAddColumnsWithMultipleAutoIncrementStartingValue() { - $blueprint = new Blueprint('users'); + $builder = $this->getBuilder(); + $builder->shouldReceive('parseSchemaAndTable')->andReturn([null, 'users']); + + $blueprint = new Blueprint($this->getConnection(builder: $builder), 'users'); $blueprint->id()->from(100); $blueprint->increments('code')->from(200); $blueprint->string('name')->from(300); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertEquals([ 'alter table "users" add column "id" bigserial not null primary key', @@ -85,11 +94,11 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() public function testCreateTableAndCommentColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email')->comment('my first comment'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('create table "users" ("id" serial not null primary key, "email" varchar(255) not null)', $statements[0]); @@ -98,12 +107,12 @@ public function testCreateTableAndCommentColumn() public function testCreateTemporaryTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->temporary(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create temporary table "users" ("id" serial not null primary key, "email" varchar(255) not null)', $statements[0]); @@ -111,9 +120,9 @@ public function testCreateTemporaryTable() public function testDropTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table "users"', $statements[0]); @@ -121,9 +130,9 @@ public function testDropTable() public function testDropTableIfExists() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table if exists "users"', $statements[0]); @@ -131,23 +140,23 @@ public function testDropTableIfExists() public function testDropColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop column "foo"', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn(['foo', 'bar']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop column "foo", drop column "bar"', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop column "foo", drop column "bar"', $statements[0]); @@ -155,9 +164,12 @@ public function testDropColumn() public function testDropPrimary() { - $blueprint = new Blueprint('users'); + $connection = $this->getConnection(); + $connection->getSchemaBuilder()->shouldReceive('parseSchemaAndTable')->andReturn([null, 'users']); + + $blueprint = new Blueprint($connection, 'users'); $blueprint->dropPrimary(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop constraint "users_pkey"', $statements[0]); @@ -165,9 +177,9 @@ public function testDropPrimary() public function testDropUnique() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropUnique('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop constraint "foo"', $statements[0]); @@ -175,9 +187,9 @@ public function testDropUnique() public function testDropIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIndex('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "foo"', $statements[0]); @@ -185,9 +197,9 @@ public function testDropIndex() public function testDropSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->dropSpatialIndex(['coordinates']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "geo_coordinates_spatialindex"', $statements[0]); @@ -195,9 +207,9 @@ public function testDropSpatialIndex() public function testDropForeign() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropForeign('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop constraint "foo"', $statements[0]); @@ -205,9 +217,9 @@ public function testDropForeign() public function testDropTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop column "created_at", drop column "updated_at"', $statements[0]); @@ -215,9 +227,9 @@ public function testDropTimestamps() public function testDropTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop column "created_at", drop column "updated_at"', $statements[0]); @@ -225,9 +237,9 @@ public function testDropTimestampsTz() public function testDropMorphs() { - $blueprint = new Blueprint('photos'); + $blueprint = new Blueprint($this->getConnection(), 'photos'); $blueprint->dropMorphs('imageable'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('drop index "photos_imageable_type_imageable_id_index"', $statements[0]); @@ -236,9 +248,9 @@ public function testDropMorphs() public function testRenameTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rename('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" rename to "foo"', $statements[0]); @@ -246,9 +258,9 @@ public function testRenameTable() public function testRenameIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->renameIndex('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter index "foo" rename to "bar"', $statements[0]); @@ -256,9 +268,9 @@ public function testRenameIndex() public function testAddingPrimaryKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add primary key ("foo")', $statements[0]); @@ -266,19 +278,39 @@ public function testAddingPrimaryKey() public function testAddingUniqueKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->unique('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "bar" unique ("foo")', $statements[0]); } + public function testAddingUniqueKeyWithNullsNotDistinct() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->unique('foo', 'bar')->nullsNotDistinct(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add constraint "bar" unique nulls not distinct ("foo")', $statements[0]); + } + + public function testAddingUniqueKeyWithNullsDistinct() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->unique('foo', 'bar')->nullsNotDistinct(false); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add constraint "bar" unique nulls distinct ("foo")', $statements[0]); + } + public function testAddingIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "baz" on "users" ("foo", "bar")', $statements[0]); @@ -286,9 +318,9 @@ public function testAddingIndex() public function testAddingIndexWithAlgorithm() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz', 'hash'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "baz" on "users" using hash ("foo", "bar")', $statements[0]); @@ -296,9 +328,9 @@ public function testAddingIndexWithAlgorithm() public function testAddingFulltextIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->fulltext('body'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "users_body_fulltext" on "users" using gin ((to_tsvector(\'english\', "body")))', $statements[0]); @@ -306,9 +338,9 @@ public function testAddingFulltextIndex() public function testAddingFulltextIndexMultipleColumns() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->fulltext(['body', 'title']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "users_body_title_fulltext" on "users" using gin ((to_tsvector(\'english\', "body") || to_tsvector(\'english\', "title")))', $statements[0]); @@ -316,9 +348,9 @@ public function testAddingFulltextIndexMultipleColumns() public function testAddingFulltextIndexWithLanguage() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->fulltext('body')->language('spanish'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "users_body_fulltext" on "users" using gin ((to_tsvector(\'spanish\', "body")))', $statements[0]); @@ -326,9 +358,9 @@ public function testAddingFulltextIndexWithLanguage() public function testAddingFulltextIndexWithFluency() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('body')->fulltext(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('create index "users_body_fulltext" on "users" using gin ((to_tsvector(\'english\', "body")))', $statements[1]); @@ -336,9 +368,9 @@ public function testAddingFulltextIndexWithFluency() public function testAddingSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->spatialIndex('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "geo_coordinates_spatialindex" on "geo" using gist ("coordinates")', $statements[0]); @@ -346,9 +378,9 @@ public function testAddingSpatialIndex() public function testAddingFluentSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point')->spatialIndex(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('create index "geo_coordinates_spatialindex" on "geo" using gist ("coordinates")', $statements[1]); @@ -356,9 +388,9 @@ public function testAddingFluentSpatialIndex() public function testAddingRawIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rawIndex('(function(column))', 'raw_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "raw_index" on "users" ((function(column)))', $statements[0]); @@ -366,9 +398,9 @@ public function testAddingRawIndex() public function testAddingIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" serial not null primary key', $statements[0]); @@ -376,9 +408,9 @@ public function testAddingIncrementingID() public function testAddingSmallIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" smallserial not null primary key', $statements[0]); @@ -386,9 +418,9 @@ public function testAddingSmallIncrementingID() public function testAddingMediumIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" serial not null primary key', $statements[0]); @@ -396,16 +428,16 @@ public function testAddingMediumIncrementingID() public function testAddingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" bigserial not null primary key', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" bigserial not null primary key', $statements[0]); @@ -413,14 +445,14 @@ public function testAddingID() public function testAddingForeignID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignId = $blueprint->foreignId('foo'); $blueprint->foreignId('company_id')->constrained(); $blueprint->foreignId('laravel_idea_id')->constrained(); $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -438,9 +470,9 @@ public function testAddingForeignID() public function testAddingForeignIdSpecifyingIndexNameInConstraint() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertSame([ 'alter table "users" add column "company_id" bigint not null', 'alter table "users" add constraint "my_index" foreign key ("company_id") references "companies" ("id")', @@ -449,9 +481,9 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() public function testAddingBigIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" bigserial not null primary key', $statements[0]); @@ -459,23 +491,23 @@ public function testAddingBigIncrementingID() public function testAddingString() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar(255) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar(100) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar(100) null default \'bar\'', $statements[0]); @@ -483,18 +515,18 @@ public function testAddingString() public function testAddingStringWithoutLengthLimit() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar(255) not null', $statements[0]); Builder::$defaultStringLength = null; - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); try { $this->assertCount(1, $statements); @@ -506,18 +538,18 @@ public function testAddingStringWithoutLengthLimit() public function testAddingCharWithoutLengthLimit() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->char('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" char(255) not null', $statements[0]); Builder::$defaultStringLength = null; - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->char('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); try { $this->assertCount(1, $statements); @@ -529,9 +561,9 @@ public function testAddingCharWithoutLengthLimit() public function testAddingText() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->text('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" text not null', $statements[0]); @@ -539,16 +571,16 @@ public function testAddingText() public function testAddingBigInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" bigint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" bigserial not null primary key', $statements[0]); @@ -556,16 +588,16 @@ public function testAddingBigInteger() public function testAddingInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" serial not null primary key', $statements[0]); @@ -573,16 +605,16 @@ public function testAddingInteger() public function testAddingMediumInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" serial not null primary key', $statements[0]); @@ -590,16 +622,16 @@ public function testAddingMediumInteger() public function testAddingTinyInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" smallint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" smallserial not null primary key', $statements[0]); @@ -607,16 +639,16 @@ public function testAddingTinyInteger() public function testAddingSmallInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" smallint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" smallserial not null primary key', $statements[0]); @@ -624,9 +656,9 @@ public function testAddingSmallInteger() public function testAddingFloat() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->float('foo', 5); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" float(5) not null', $statements[0]); @@ -634,9 +666,9 @@ public function testAddingFloat() public function testAddingDouble() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->double('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" double precision not null', $statements[0]); @@ -644,9 +676,9 @@ public function testAddingDouble() public function testAddingDecimal() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->decimal('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" decimal(5, 2) not null', $statements[0]); @@ -654,9 +686,9 @@ public function testAddingDecimal() public function testAddingBoolean() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->boolean('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" boolean not null', $statements[0]); @@ -664,9 +696,9 @@ public function testAddingBoolean() public function testAddingEnum() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->enum('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "role" varchar(255) check ("role" in (\'member\', \'admin\')) not null', $statements[0]); @@ -674,9 +706,9 @@ public function testAddingEnum() public function testAddingDate() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->date('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" date not null', $statements[0]); @@ -684,18 +716,18 @@ public function testAddingDate() public function testAddingYear() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->year('birth_year'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "birth_year" integer not null', $statements[0]); } public function testAddingJson() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->json('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" json not null', $statements[0]); @@ -703,205 +735,48 @@ public function testAddingJson() public function testAddingJsonb() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->jsonb('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" jsonb not null', $statements[0]); } - public function testAddingDateTime() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTime('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) without time zone not null', $statements[0]); - } - - public function testAddingDateTimeWithPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTime('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(1) without time zone not null', $statements[0]); - } - - public function testAddingDateTimeWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTime('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp without time zone not null', $statements[0]); - } - - public function testAddingDateTimeTz() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTimeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) with time zone not null', $statements[0]); - } - - public function testAddingDateTimeTzWithPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTimeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(1) with time zone not null', $statements[0]); - } - - public function testAddingDateTimeTzWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->dateTimeTz('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp with time zone not null', $statements[0]); - } - - public function testAddingTime() - { - $blueprint = new Blueprint('users'); - $blueprint->time('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time(0) without time zone not null', $statements[0]); - } - - public function testAddingTimeWithPrecision() + #[DataProvider('datetimeAndPrecisionProvider')] + public function testAddingDatetimeMethods(string $method, string $type, ?int $userPrecision, false|int|null $grammarPrecision, ?int $expected) { - $blueprint = new Blueprint('users'); - $blueprint->time('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time(1) without time zone not null', $statements[0]); - } - - public function testAddingTimeWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->time('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time without time zone not null', $statements[0]); - } - - public function testAddingTimeTz() - { - $blueprint = new Blueprint('users'); - $blueprint->timeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time(0) with time zone not null', $statements[0]); - } - - public function testAddingTimeTzWithPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time(1) with time zone not null', $statements[0]); - } - - public function testAddingTimeTzWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timeTz('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + PostgresBuilder::defaultTimePrecision($grammarPrecision); + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->{$method}('created_at', $userPrecision); + $statements = $blueprint->toSql(); + $type = is_null($expected) ? $type : "{$type}({$expected})"; + $with = str_contains($method, 'Tz') ? 'with' : 'without'; $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" time with time zone not null', $statements[0]); + $this->assertSame("alter table \"users\" add column \"created_at\" {$type} {$with} time zone not null", $statements[0]); } - public function testAddingTimestamp() + #[TestWith(['timestamps'])] + #[TestWith(['timestampsTz'])] + public function testAddingTimestamps(string $method) { - $blueprint = new Blueprint('users'); - $blueprint->timestamp('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) without time zone not null', $statements[0]); - } - - public function testAddingTimestampWithPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timestamp('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(1) without time zone not null', $statements[0]); - } - - public function testAddingTimestampWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timestamp('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp without time zone not null', $statements[0]); - } - - public function testAddingTimestampTz() - { - $blueprint = new Blueprint('users'); - $blueprint->timestampTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) with time zone not null', $statements[0]); - } - - public function testAddingTimestampTzWithPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timestampTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(1) with time zone not null', $statements[0]); - } - - public function testAddingTimestampTzWithNullPrecision() - { - $blueprint = new Blueprint('users'); - $blueprint->timestampTz('created_at', null); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp with time zone not null', $statements[0]); - } - - public function testAddingTimestamps() - { - $blueprint = new Blueprint('users'); - $blueprint->timestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(2, $statements); - $this->assertSame([ - 'alter table "users" add column "created_at" timestamp(0) without time zone null', - 'alter table "users" add column "updated_at" timestamp(0) without time zone null', - ], $statements); - } - - public function testAddingTimestampsTz() - { - $blueprint = new Blueprint('users'); - $blueprint->timestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + PostgresBuilder::defaultTimePrecision(0); + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->{$method}(); + $statements = $blueprint->toSql(); + $with = str_contains($method, 'Tz') ? 'with' : 'without'; $this->assertCount(2, $statements); $this->assertSame([ - 'alter table "users" add column "created_at" timestamp(0) with time zone null', - 'alter table "users" add column "updated_at" timestamp(0) with time zone null', + "alter table \"users\" add column \"created_at\" timestamp(0) {$with} time zone null", + "alter table \"users\" add column \"updated_at\" timestamp(0) {$with} time zone null", ], $statements); } public function testAddingBinary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->binary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" bytea not null', $statements[0]); @@ -909,9 +784,9 @@ public function testAddingBinary() public function testAddingUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" uuid not null', $statements[0]); @@ -919,9 +794,9 @@ public function testAddingUuid() public function testAddingUuidDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "uuid" uuid not null', $statements[0]); @@ -929,14 +804,14 @@ public function testAddingUuidDefaultsColumnName() public function testAddingForeignUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignUuid = $blueprint->foreignUuid('foo'); $blueprint->foreignUuid('company_id')->constrained(); $blueprint->foreignUuid('laravel_idea_id')->constrained(); $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ @@ -954,47 +829,47 @@ public function testAddingForeignUuid() public function testAddingGeneratedAs() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('foo')->generatedAs(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null generated by default as identity primary key', $statements[0]); // With always modifier - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('foo')->generatedAs()->always(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null generated always as identity primary key', $statements[0]); // With sequence options - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('foo')->generatedAs('increment by 10 start with 100'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null generated by default as identity (increment by 10 start with 100) primary key', $statements[0]); // Not a primary key - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo')->generatedAs(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null generated by default as identity', $statements[0]); } public function testAddingVirtualAs() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->virtualAs('foo is not null'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add column "foo" integer null', 'alter table "users" add column "bar" boolean not null generated always as (foo is not null)', ], $statements); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->virtualAs(new Expression('foo is not null')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add column "foo" integer null', @@ -1004,20 +879,20 @@ public function testAddingVirtualAs() public function testAddingStoredAs() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->storedAs('foo is not null'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add column "foo" integer null', 'alter table "users" add column "bar" boolean not null generated always as (foo is not null) stored', ], $statements); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->storedAs(new Expression('foo is not null')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add column "foo" integer null', @@ -1027,9 +902,9 @@ public function testAddingStoredAs() public function testAddingIpAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" inet not null', $statements[0]); @@ -1037,9 +912,9 @@ public function testAddingIpAddress() public function testAddingIpAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "ip_address" inet not null', $statements[0]); @@ -1047,9 +922,9 @@ public function testAddingIpAddressDefaultsColumnName() public function testAddingMacAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" macaddr not null', $statements[0]); @@ -1057,9 +932,9 @@ public function testAddingMacAddress() public function testAddingMacAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "mac_address" macaddr not null', $statements[0]); @@ -1067,30 +942,30 @@ public function testAddingMacAddressDefaultsColumnName() public function testCompileForeign() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('parent_id')->references('id')->on('parents')->onDelete('cascade')->deferrable(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "users_parent_id_foreign" foreign key ("parent_id") references "parents" ("id") on delete cascade deferrable', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('parent_id')->references('id')->on('parents')->onDelete('cascade')->deferrable(false)->initiallyImmediate(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "users_parent_id_foreign" foreign key ("parent_id") references "parents" ("id") on delete cascade not deferrable', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('parent_id')->references('id')->on('parents')->onDelete('cascade')->deferrable()->initiallyImmediate(false); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "users_parent_id_foreign" foreign key ("parent_id") references "parents" ("id") on delete cascade deferrable initially deferred', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreign('parent_id')->references('id')->on('parents')->onDelete('cascade')->deferrable()->notValid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "users_parent_id_foreign" foreign key ("parent_id") references "parents" ("id") on delete cascade deferrable not valid', $statements[0]); @@ -1098,9 +973,9 @@ public function testCompileForeign() public function testAddingGeometry() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry not null', $statements[0]); @@ -1108,9 +983,9 @@ public function testAddingGeometry() public function testAddingGeography() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geography('coordinates', 'pointzm', 4269); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geography(pointzm,4269) not null', $statements[0]); @@ -1118,9 +993,9 @@ public function testAddingGeography() public function testAddingPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(point) not null', $statements[0]); @@ -1128,9 +1003,9 @@ public function testAddingPoint() public function testAddingPointWithSrid() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point', 4269); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(point,4269) not null', $statements[0]); @@ -1138,9 +1013,9 @@ public function testAddingPointWithSrid() public function testAddingLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'linestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(linestring) not null', $statements[0]); @@ -1148,9 +1023,9 @@ public function testAddingLineString() public function testAddingPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'polygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(polygon) not null', $statements[0]); @@ -1158,9 +1033,9 @@ public function testAddingPolygon() public function testAddingGeometryCollection() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'geometrycollection'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(geometrycollection) not null', $statements[0]); @@ -1168,9 +1043,9 @@ public function testAddingGeometryCollection() public function testAddingMultiPoint() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipoint'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(multipoint) not null', $statements[0]); @@ -1178,9 +1053,9 @@ public function testAddingMultiPoint() public function testAddingMultiLineString() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multilinestring'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(multilinestring) not null', $statements[0]); @@ -1188,9 +1063,9 @@ public function testAddingMultiLineString() public function testAddingMultiPolygon() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'multipolygon'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry(multipolygon) not null', $statements[0]); @@ -1200,7 +1075,7 @@ public function testCreateDatabase() { $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8_foo'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_a', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_a'); $this->assertSame( 'create database "my_database_a" encoding "utf8_foo"', @@ -1209,7 +1084,7 @@ public function testCreateDatabase() $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8_bar'); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_b', $connection); + $statement = $this->getGrammar($connection)->compileCreateDatabase('my_database_b'); $this->assertSame( 'create database "my_database_b" encoding "utf8_bar"', @@ -1238,44 +1113,120 @@ public function testDropAllTablesEscapesTableNames() { $statement = $this->getGrammar()->compileDropAllTables(['alpha', 'beta', 'gamma']); - $this->assertSame('drop table "alpha","beta","gamma" cascade', $statement); + $this->assertSame('drop table "alpha", "beta", "gamma" cascade', $statement); } public function testDropAllViewsEscapesTableNames() { $statement = $this->getGrammar()->compileDropAllViews(['alpha', 'beta', 'gamma']); - $this->assertSame('drop view "alpha","beta","gamma" cascade', $statement); + $this->assertSame('drop view "alpha", "beta", "gamma" cascade', $statement); } public function testDropAllTypesEscapesTableNames() { $statement = $this->getGrammar()->compileDropAllTypes(['alpha', 'beta', 'gamma']); - $this->assertSame('drop type "alpha","beta","gamma" cascade', $statement); + $this->assertSame('drop type "alpha", "beta", "gamma" cascade', $statement); + } + + public function testDropAllTablesWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllTables(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop table "schema"."alpha", "schema"."beta", "schema"."gamma" cascade', $statement); + } + + public function testDropAllViewsWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllViews(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop view "schema"."alpha", "schema"."beta", "schema"."gamma" cascade', $statement); + } + + public function testDropAllTypesWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllTypes(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop type "schema"."alpha", "schema"."beta", "schema"."gamma" cascade', $statement); + } + + public function testDropAllDomainsWithPrefixAndSchema() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $statement = $this->getGrammar($connection)->compileDropAllDomains(['schema.alpha', 'schema.beta', 'schema.gamma']); + + $this->assertSame('drop domain "schema"."alpha", "schema"."beta", "schema"."gamma" cascade', $statement); } public function testCompileColumns() { $connection = $this->getConnection(); - $grammar = $this->getGrammar(); - $connection->shouldReceive('getServerVersion')->once()->andReturn('12.0.0'); - $grammar->setConnection($connection); - $statement = $grammar->compileColumns('public', 'table'); + $statement = $connection->getSchemaGrammar()->compileColumns('public', 'table'); $this->assertStringContainsString("where c.relname = 'table' and n.nspname = 'public'", $statement); } - protected function getConnection() + protected function getConnection( + ?PostgresGrammar $grammar = null, + ?PostgresBuilder $builder = null, + string $prefix = '' + ) { + $connection = m::mock(Connection::class) + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->with('prefix_indexes')->andReturn(null) + ->getMock(); + + $grammar ??= $this->getGrammar($connection); + $builder ??= $this->getBuilder(); + + return $connection + ->shouldReceive('getSchemaGrammar')->andReturn($grammar) + ->shouldReceive('getSchemaBuilder')->andReturn($builder) + ->getMock(); + } + + public function getGrammar(?Connection $connection = null) { - return m::mock(Connection::class); + return new PostgresGrammar($connection ?? $this->getConnection()); } - public function getGrammar() + public function getBuilder() { - return new PostgresGrammar; + return mock(PostgresBuilder::class); + } + + /** @return list */ + public static function datetimeAndPrecisionProvider(): array + { + $methods = [ + ['method' => 'datetime', 'type' => 'timestamp'], + ['method' => 'datetimeTz', 'type' => 'timestamp'], + ['method' => 'timestamp', 'type' => 'timestamp'], + ['method' => 'timestampTz', 'type' => 'timestamp'], + ['method' => 'time', 'type' => 'time'], + ['method' => 'timeTz', 'type' => 'time'], + ]; + $precisions = [ + 'user can override grammar default' => ['userPrecision' => 1, 'grammarPrecision' => null, 'expected' => 1], + 'fallback to grammar default' => ['userPrecision' => null, 'grammarPrecision' => 5, 'expected' => 5], + 'fallback to database default' => ['userPrecision' => null, 'grammarPrecision' => null, 'expected' => null], + ]; + + $result = []; + + foreach ($methods as $datetime) { + foreach ($precisions as $precision) { + $result[] = array_merge($datetime, $precision); + } + } + + return $result; } public function testGrammarsAreMacroable() diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 47b2e37c5806..9c89ef66eac9 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -7,7 +7,7 @@ use Closure; use DateTime; use Illuminate\Contracts\Database\Query\ConditionExpression; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression as Raw; @@ -121,8 +121,7 @@ public function testAddingSelects() public function testBasicSelectWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getBuilder(prefix: 'prefix_'); $builder->select('*')->from('users'); $this->assertSame('select * from "prefix_users"', $builder->toSql()); } @@ -154,16 +153,14 @@ public function testBasicAlias() public function testAliasWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getBuilder(prefix: 'prefix_'); $builder->select('*')->from('users as people'); $this->assertSame('select * from "prefix_users" as "prefix_people"', $builder->toSql()); } public function testJoinAliasesWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getBuilder(prefix: 'prefix_'); $builder->select('*')->from('services')->join('translations AS t', 't.item_id', '=', 'services.id'); $this->assertSame('select * from "prefix_services" inner join "prefix_translations" as "prefix_t" on "prefix_t"."item_id" = "prefix_services"."id"', $builder->toSql()); } @@ -304,6 +301,27 @@ public function testTapCallback() $this->assertSame('select * from "users" where "id" = ? and "email" = ?', $builder->toSql()); } + public function testPipeCallback() + { + $query = $this->getBuilder(); + + $result = $query->pipe(fn (Builder $query) => 5); + $this->assertSame(5, $result); + + $result = $query->pipe(fn (Builder $query) => null); + $this->assertSame($query, $result); + + $result = $query->pipe(function (Builder $query) { + // + }); + $this->assertSame($query, $result); + + $this->assertCount(0, $query->wheres); + $result = $query->pipe(fn (Builder $query) => $query->where('foo', 'bar')); + $this->assertSame($query, $result); + $this->assertCount(1, $query->wheres); + } + public function testBasicWheres() { $builder = $this->getBuilder(); @@ -596,6 +614,10 @@ public function testWhereDatePostgres() $builder = $this->getPostgresBuilder(); $builder->select('*')->from('users')->whereDate('created_at', new Raw('NOW()')); $this->assertSame('select * from "users" where "created_at"::date = NOW()', $builder->toSql()); + + $builder = $this->getPostgresBuilder(); + $builder->select('*')->from('users')->whereDate('result->created_at', new Raw('NOW()')); + $this->assertSame('select * from "users" where ("result"->>\'created_at\')::date = NOW()', $builder->toSql()); } public function testWhereDayPostgres() @@ -628,6 +650,11 @@ public function testWhereTimePostgres() $builder->select('*')->from('users')->whereTime('created_at', '>=', '22:00'); $this->assertSame('select * from "users" where "created_at"::time >= ?', $builder->toSql()); $this->assertEquals([0 => '22:00'], $builder->getBindings()); + + $builder = $this->getPostgresBuilder(); + $builder->select('*')->from('users')->whereTime('result->created_at', '>=', '22:00'); + $this->assertSame('select * from "users" where ("result"->>\'created_at\')::time >= ?', $builder->toSql()); + $this->assertEquals([0 => '22:00'], $builder->getBindings()); } public function testWherePast() @@ -2363,6 +2390,28 @@ public function testForPage() $this->assertSame('select * from "users" limit 0 offset 0', $builder->toSql()); } + public function testForPageBeforeId() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->forPageBeforeId(15, null); + $this->assertSame('select * from "users" where "id" is not null order by "id" desc limit 15', $builder->toSql()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->forPageBeforeId(15, 0); + $this->assertSame('select * from "users" where "id" < ? order by "id" desc limit 15', $builder->toSql()); + } + + public function testForPageAfterId() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->forPageAfterId(15, null); + $this->assertSame('select * from "users" where "id" is not null order by "id" asc limit 15', $builder->toSql()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->forPageAfterId(15, 0); + $this->assertSame('select * from "users" where "id" > ? order by "id" asc limit 15', $builder->toSql()); + } + public function testGetCountForPaginationWithBindings() { $builder = $this->getBuilder(); @@ -2478,9 +2527,7 @@ public function testOrWheresHaveConsistentResults() public function testWhereWithArrayConditions() { - /* - * where(key, value) - */ + // where(key, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where([['foo', 1], ['bar', 2]]); @@ -2512,9 +2559,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = ? and "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); - /* - * where(key, <, value) - */ + // where(key, <, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where([['foo', 1], ['bar', '<', 2]]); @@ -2531,9 +2576,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = ? and "bar" < ?)', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); - /* - * whereNot(key, value) - */ + // whereNot(key, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereNot([['foo', 1], ['bar', 2]]); @@ -2565,9 +2608,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where not (("foo" = ? and "bar" = ?))', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); - /* - * whereNot(key, <, value) - */ + // whereNot(key, <, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereNot([['foo', 1], ['bar', '<', 2]]); @@ -2584,9 +2625,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where not (("foo" = ? and "bar" < ?))', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); - /* - * whereColumn(col1, col2) - */ + // whereColumn(col1, col2) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereColumn([['foo', '_foo'], ['bar', '_bar']]); @@ -2618,9 +2657,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = "_foo" and "bar" = "_bar")', $builder->toSql()); $this->assertEquals([], $builder->getBindings()); - /* - * whereColumn(col1, <, col2) - */ + // whereColumn(col1, <, col2) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereColumn([['foo', '_foo'], ['bar', '<', '_bar']]); @@ -2637,9 +2674,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = "_foo" and "bar" < "_bar")', $builder->toSql()); $this->assertEquals([], $builder->getBindings()); - /* - * whereAll([...keys], value) - */ + // whereAll([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereAll(['foo', 'bar'], 2); @@ -2651,9 +2686,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = ? and "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 2, 1 => 2], $builder->getBindings()); - /* - * whereAny([...keys], value) - */ + // whereAny([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereAny(['foo', 'bar'], 2); @@ -2665,9 +2698,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where ("foo" = ? or "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 2, 1 => 2], $builder->getBindings()); - /* - * whereNone([...keys], value) - */ + // whereNone([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereNone(['foo', 'bar'], 2); @@ -2679,9 +2710,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where not ("foo" = ? or "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 2, 1 => 2], $builder->getBindings()); - /* - * where()->orWhere(key, value) - */ + // where()->orWhere(key, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhere([['foo', 1], ['bar', 2]]); @@ -2693,18 +2722,14 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = ? or "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 1, 2 => 2], $builder->getBindings()); - /* - * where()->orWhere(key, <, value) - */ + // where()->orWhere(key, <, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhere([['foo', 1], ['bar', '<', 2]]); $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = ? or "bar" < ?)', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 1, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereColumn(col1, col2) - */ + // where()->orWhereColumn(col1, col2) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereColumn([['foo', '_foo'], ['bar', '_bar']]); @@ -2716,18 +2741,14 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = "_foo" or "bar" = "_bar")', $builder->toSql()); $this->assertEquals([0 => 'xxxx'], $builder->getBindings()); - /* - * where()->orWhere(key, <, value) - */ + // where()->orWhere(key, <, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhere([['foo', 1], ['bar', '<', 2]]); $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = ? or "bar" < ?)', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 1, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereNot(key, value) - */ + // where()->orWhereNot(key, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereNot([['foo', 1], ['bar', 2]]); @@ -2739,18 +2760,14 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where "xxxx" = ? or not (("foo" = ? or "bar" = ?))', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 1, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereNot(key, <, value) - */ + // where()->orWhereNot(key, <, value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereNot([['foo', 1], ['bar', '<', 2]]); $this->assertSame('select * from "users" where "xxxx" = ? or not (("foo" = ? or "bar" < ?))', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 1, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereAll([...keys], value) - */ + // where()->orWhereAll([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereAll(['foo', 'bar'], 2); @@ -2762,9 +2779,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = ? and "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 2, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereAny([...keys], value) - */ + // where()->orWhereAny([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereAny(['foo', 'bar'], 2); @@ -2776,9 +2791,7 @@ public function testWhereWithArrayConditions() $this->assertSame('select * from "users" where "xxxx" = ? or ("foo" = ? or "bar" = ?)', $builder->toSql()); $this->assertEquals([0 => 'xxxx', 1 => 2, 2 => 2], $builder->getBindings()); - /* - * where()->orWhereNone([...keys], value) - */ + // where()->orWhereNone([...keys], value) $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('xxxx', 'xxxx')->orWhereNone(['foo', 'bar'], 2); @@ -3259,8 +3272,7 @@ public function testJoinSub() public function testJoinSubWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getBuilder(prefix: 'prefix_'); $builder->from('users')->joinSub('select * from "contacts"', 'sub', 'users.id', '=', 'sub.id'); $this->assertSame('select * from "prefix_users" inner join (select * from "contacts") as "prefix_sub" on "prefix_users"."id" = "prefix_sub"."id"', $builder->toSql()); } @@ -3375,9 +3387,7 @@ public function testJoinLateralSqlServer() public function testJoinLateralWithPrefix() { - $builder = $this->getMySqlBuilder(); - $builder->getConnection()->shouldReceive('getDatabaseName'); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getMySqlBuilder(prefix: 'prefix_'); $builder->from('users')->joinLateral('select * from `contacts` where `contracts`.`user_id` = `users`.`id`', 'sub'); $this->assertSame('select * from `prefix_users` inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `prefix_sub` on true', $builder->toSql()); } @@ -4241,8 +4251,8 @@ public function testUpdateMethodWorksWithQueryAsValue() public function testUpdateOrInsertMethod() { $builder = m::mock(Builder::class.'[where,exists,insert]', [ - m::mock(ConnectionInterface::class), - new Grammar, + $connection = m::mock(Connection::class), + new Grammar($connection), m::mock(Processor::class), ]); @@ -4253,8 +4263,8 @@ public function testUpdateOrInsertMethod() $this->assertTrue($builder->updateOrInsert(['email' => 'foo'], ['name' => 'bar'])); $builder = m::mock(Builder::class.'[where,exists,update]', [ - m::mock(ConnectionInterface::class), - new Grammar, + $connection = m::mock(Connection::class), + new Grammar($connection), m::mock(Processor::class), ]); @@ -4269,8 +4279,8 @@ public function testUpdateOrInsertMethod() public function testUpdateOrInsertMethodWorksWithEmptyUpdateValues() { $builder = m::spy(Builder::class.'[where,exists,update]', [ - m::mock(ConnectionInterface::class), - new Grammar, + $connection = m::mock(Connection::class), + new Grammar($connection), m::mock(Processor::class), ]); @@ -4395,33 +4405,49 @@ public function testDeleteWithJoinMethod() public function testTruncateMethod() { $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('statement')->once()->with('truncate table "users"', []); + $connection = $builder->getConnection(); + $connection->shouldReceive('statement')->once()->with('truncate table "users"', []); $builder->from('users')->truncate(); - $sqlite = new SQLiteGrammar; - $builder = $this->getBuilder(); + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('getSchemaBuilder->parseSchemaAndTable')->andReturn([null, 'users']); $builder->from('users'); $this->assertEquals([ 'delete from sqlite_sequence where name = ?' => ['users'], 'delete from "users"' => [], - ], $sqlite->compileTruncate($builder)); + ], $builder->getGrammar()->compileTruncate($builder)); } public function testTruncateMethodWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); - $builder->getConnection()->shouldReceive('statement')->once()->with('truncate table "prefix_users"', []); + $builder = $this->getBuilder(prefix: 'prefix_'); + $connection = $builder->getConnection(); + $connection->shouldReceive('statement')->once()->with('truncate table "prefix_users"', []); $builder->from('users')->truncate(); - $sqlite = new SQLiteGrammar; - $sqlite->setTablePrefix('prefix_'); - $builder = $this->getBuilder(); + $builder = $this->getSQLiteBuilder(prefix: 'prefix_'); + $builder->getConnection()->shouldReceive('getSchemaBuilder->parseSchemaAndTable')->andReturn([null, 'users']); $builder->from('users'); $this->assertEquals([ 'delete from sqlite_sequence where name = ?' => ['prefix_users'], 'delete from "prefix_users"' => [], - ], $sqlite->compileTruncate($builder)); + ], $builder->getGrammar()->compileTruncate($builder)); + } + + public function testTruncateMethodWithPrefixAndSchema() + { + $builder = $this->getBuilder(prefix: 'prefix_'); + $connection = $builder->getConnection(); + $connection->shouldReceive('statement')->once()->with('truncate table "my_schema"."prefix_users"', []); + $builder->from('my_schema.users')->truncate(); + + $builder = $this->getSQLiteBuilder(prefix: 'prefix_'); + $builder->getConnection()->shouldReceive('getSchemaBuilder->parseSchemaAndTable')->andReturn(['my_schema', 'users']); + $builder->from('my_schema.users'); + $this->assertEquals([ + 'delete from "my_schema".sqlite_sequence where name = ?' => ['prefix_users'], + 'delete from "my_schema"."prefix_users"' => [], + ], $builder->getGrammar()->compileTruncate($builder)); } public function testPreserveAddsClosureToArray() @@ -4562,10 +4588,10 @@ public function testMySqlWrapping() public function testMySqlUpdateWrappingJson() { - $grammar = new MySqlGrammar; + $connection = $this->createMock(Connection::class); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - $connection = $this->createMock(ConnectionInterface::class); $connection->expects($this->once()) ->method('update') ->with( @@ -4580,10 +4606,10 @@ public function testMySqlUpdateWrappingJson() public function testMySqlUpdateWrappingNestedJson() { - $grammar = new MySqlGrammar; + $connection = $this->createMock(Connection::class); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - $connection = $this->createMock(ConnectionInterface::class); $connection->expects($this->once()) ->method('update') ->with( @@ -4598,10 +4624,10 @@ public function testMySqlUpdateWrappingNestedJson() public function testMySqlUpdateWrappingJsonArray() { - $grammar = new MySqlGrammar; + $connection = $this->createMock(Connection::class); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - $connection = $this->createMock(ConnectionInterface::class); $connection->expects($this->once()) ->method('update') ->with( @@ -4625,10 +4651,10 @@ public function testMySqlUpdateWrappingJsonArray() public function testMySqlUpdateWrappingJsonPathArrayIndex() { - $grammar = new MySqlGrammar; + $connection = $this->createMock(Connection::class); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - $connection = $this->createMock(ConnectionInterface::class); $connection->expects($this->once()) ->method('update') ->with( @@ -4648,10 +4674,10 @@ public function testMySqlUpdateWrappingJsonPathArrayIndex() public function testMySqlUpdateWithJsonPreparesBindingsCorrectly() { - $grammar = new MySqlGrammar; + $connection = $this->getConnection(); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - $connection = m::mock(ConnectionInterface::class); $connection->shouldReceive('update') ->once() ->with( @@ -5056,6 +5082,10 @@ public function testProvidingNullWithOperatorsBuildsCorrectly() $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('foo', '<>', null); $this->assertSame('select * from "users" where "foo" is not null', $builder->toSql()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('foo', '<=>', null); + $this->assertSame('select * from "users" where "foo" is null', $builder->toSql()); } public function testDynamicWhere() @@ -5326,9 +5356,13 @@ public function testChunkWithLastChunkComplete() $chunk1 = collect(['foo1', 'foo2']); $chunk2 = collect(['foo3', 'foo4']); $chunk3 = collect([]); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(2, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(3, 2)->andReturnSelf(); + + $builder->shouldReceive('getOffset')->once()->andReturnNull(); + $builder->shouldReceive('getLimit')->once()->andReturnNull(); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(2)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(4)->andReturnSelf(); + $builder->shouldReceive('limit')->times(3)->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(3)->andReturn($chunk1, $chunk2, $chunk3); $callbackAssertor = m::mock(stdClass::class); @@ -5348,8 +5382,12 @@ public function testChunkWithLastChunkPartial() $chunk1 = collect(['foo1', 'foo2']); $chunk2 = collect(['foo3']); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->once()->with(2, 2)->andReturnSelf(); + + $builder->shouldReceive('getOffset')->once()->andReturnNull(); + $builder->shouldReceive('getLimit')->once()->andReturnNull(); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('offset')->once()->with(2)->andReturnSelf(); + $builder->shouldReceive('limit')->twice()->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(2)->andReturn($chunk1, $chunk2); $callbackAssertor = m::mock(stdClass::class); @@ -5368,8 +5406,10 @@ public function testChunkCanBeStoppedByReturningFalse() $chunk1 = collect(['foo1', 'foo2']); $chunk2 = collect(['foo3']); - $builder->shouldReceive('forPage')->once()->with(1, 2)->andReturnSelf(); - $builder->shouldReceive('forPage')->never()->with(2, 2); + $builder->shouldReceive('getOffset')->once()->andReturnNull(); + $builder->shouldReceive('getLimit')->once()->andReturnNull(); + $builder->shouldReceive('offset')->once()->with(0)->andReturnSelf(); + $builder->shouldReceive('limit')->once()->with(2)->andReturnSelf(); $builder->shouldReceive('get')->times(1)->andReturn($chunk1); $callbackAssertor = m::mock(stdClass::class); @@ -5388,15 +5428,14 @@ public function testChunkWithCountZero() $builder = $this->getMockQueryBuilder(); $builder->orders[] = ['column' => 'foobar', 'direction' => 'asc']; - $chunk = collect([]); - $builder->shouldReceive('forPage')->once()->with(1, 0)->andReturnSelf(); - $builder->shouldReceive('get')->times(1)->andReturn($chunk); + $builder->shouldReceive('getOffset')->once()->andReturnNull(); + $builder->shouldReceive('getLimit')->once()->andReturnNull(); + $builder->shouldReceive('offset')->never(); + $builder->shouldReceive('limit')->never(); + $builder->shouldReceive('get')->never(); - $callbackAssertor = m::mock(stdClass::class); - $callbackAssertor->shouldReceive('doSomething')->never(); - - $builder->chunk(0, function ($results) use ($callbackAssertor) { - $callbackAssertor->doSomething($results); + $builder->chunk(0, function () { + $this->fail('Should never be called.'); }); } @@ -5471,15 +5510,11 @@ public function testChunkPaginatesUsingIdWithCountZero() $builder = $this->getMockQueryBuilder(); $builder->orders[] = ['column' => 'foobar', 'direction' => 'asc']; - $chunk = collect([]); - $builder->shouldReceive('forPageAfterId')->once()->with(0, 0, 'someIdField')->andReturnSelf(); - $builder->shouldReceive('get')->times(1)->andReturn($chunk); - - $callbackAssertor = m::mock(stdClass::class); - $callbackAssertor->shouldReceive('doSomething')->never(); + $builder->shouldReceive('forPageAfterId')->never(); + $builder->shouldReceive('get')->never(); - $builder->chunkById(0, function ($results) use ($callbackAssertor) { - $callbackAssertor->doSomething($results); + $builder->chunkById(0, function () { + $this->fail('Should never be called.'); }, 'someIdField'); } @@ -6135,7 +6170,7 @@ public function testCursorPaginateWithUnionMultipleWheresMultipleOrders() '(select "id", "start_time" as "created_at", "type" from "videos" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "news" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "podcasts" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) order by "id" asc, "created_at" desc, "type" asc limit 17', $builder->toSql()); $this->assertEquals(['first', 1, 1, $ts, $ts, 'news'], $builder->bindings['where']); - $this->assertEquals(['second', 1, 1, $ts, $ts, 'news', 'third', 1, 1, $ts, $ts, 'news'], $builder->bindings ['union']); + $this->assertEquals(['second', 1, 1, $ts, $ts, 'news', 'third', 1, 1, $ts, $ts, 'news'], $builder->bindings['union']); return $results; }); @@ -6798,8 +6833,7 @@ public function testFromSub() public function testFromSubWithPrefix() { - $builder = $this->getBuilder(); - $builder->getGrammar()->setTablePrefix('prefix_'); + $builder = $this->getBuilder(prefix: 'prefix_'); $builder->fromSub(function ($query) { $query->select(new Raw('max(last_seen_at) as last_seen_at'))->from('user_sessions')->where('foo', '=', '1'); }, 'sessions')->where('bar', '<', '10'); @@ -6956,11 +6990,11 @@ public function testCloneWithoutBindings() public function testToRawSql() { - $connection = m::mock(ConnectionInterface::class); + $connection = $this->getConnection(); $connection->shouldReceive('prepareBindings') ->with(['foo']) ->andReturn(['foo']); - $grammar = m::mock(Grammar::class)->makePartial(); + $grammar = m::mock(Grammar::class, [$connection])->makePartial(); $grammar->shouldReceive('substituteBindingsIntoRawSql') ->with('select * from "users" where "email" = ?', ['foo']) ->andReturn('select * from "users" where "email" = \'foo\''); @@ -6970,76 +7004,85 @@ public function testToRawSql() $this->assertSame('select * from "users" where "email" = \'foo\'', $builder->toRawSql()); } - protected function getConnection() + protected function getConnection(string $prefix = '') { - $connection = m::mock(ConnectionInterface::class); + $connection = m::mock(Connection::class); $connection->shouldReceive('getDatabaseName')->andReturn('database'); + $connection->shouldReceive('getTablePrefix')->andReturn($prefix); return $connection; } - protected function getBuilder() + protected function getBuilder(string $prefix = '') { - $grammar = new Grammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new Grammar($connection); $processor = m::mock(Processor::class); - return new Builder($this->getConnection(), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getPostgresBuilder() + protected function getPostgresBuilder(string $prefix = '') { - $grammar = new PostgresGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new PostgresGrammar($connection); $processor = m::mock(Processor::class); - return new Builder($this->getConnection(), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getMySqlBuilder() + protected function getMySqlBuilder(string $prefix = '') { - $grammar = new MySqlGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new MySqlGrammar($connection); $processor = m::mock(Processor::class); - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getMariaDbBuilder() + protected function getMariaDbBuilder(string $prefix = '') { - $grammar = new MariaDbGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new MariaDbGrammar($connection); $processor = m::mock(Processor::class); - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getSQLiteBuilder() + protected function getSQLiteBuilder(string $prefix = '') { - $grammar = new SQLiteGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new SQLiteGrammar($connection); $processor = m::mock(Processor::class); - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getSqlServerBuilder() + protected function getSqlServerBuilder(string $prefix = '') { - $grammar = new SqlServerGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new SqlServerGrammar($connection); $processor = m::mock(Processor::class); - return new Builder($this->getConnection(), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getMySqlBuilderWithProcessor() + protected function getMySqlBuilderWithProcessor(string $prefix = '') { - $grammar = new MySqlGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new MySqlGrammar($connection); $processor = new MySqlProcessor; - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } - protected function getPostgresBuilderWithProcessor() + protected function getPostgresBuilderWithProcessor(string $prefix = '') { - $grammar = new PostgresGrammar; + $connection = $this->getConnection(prefix: $prefix); + $grammar = new PostgresGrammar($connection); $processor = new PostgresProcessor; - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + return new Builder($connection, $grammar, $processor); } /** @@ -7048,8 +7091,8 @@ protected function getPostgresBuilderWithProcessor() protected function getMockQueryBuilder() { return m::mock(Builder::class, [ - m::mock(ConnectionInterface::class), - new Grammar, + $connection = $this->getConnection(), + new Grammar($connection), m::mock(Processor::class), ])->makePartial(); } diff --git a/tests/Database/DatabaseQueryExceptionTest.php b/tests/Database/DatabaseQueryExceptionTest.php new file mode 100755 index 000000000000..0a0c719718cc --- /dev/null +++ b/tests/Database/DatabaseQueryExceptionTest.php @@ -0,0 +1,64 @@ +getConnection(); + + $sql = 'SELECT * FROM huehue WHERE a = ? and hue = ?'; + $bindings = [1, 'br']; + + $expectedSql = "SELECT * FROM huehue WHERE a = 1 and hue = 'br'"; + + $pdoException = new PDOException('Mock SQL error'); + $exception = new QueryException($connection->getName(), $sql, $bindings, $pdoException); + + DB::shouldReceive('connection')->andReturn($connection); + $result = $exception->getRawSql(); + + $this->assertSame($expectedSql, $result); + } + + public function testIfItReturnsSameSqlWhenThereAreNoBindings() + { + $connection = $this->getConnection(); + + $sql = "SELECT * FROM huehue WHERE a = 1 and hue = 'br'"; + $bindings = []; + + $expectedSql = $sql; + + $pdoException = new PDOException('Mock SQL error'); + $exception = new QueryException($connection->getName(), $sql, $bindings, $pdoException); + + DB::shouldReceive('connection')->andReturn($connection); + $result = $exception->getRawSql(); + + $this->assertSame($expectedSql, $result); + } + + protected function getConnection() + { + $connection = m::mock(Connection::class); + + $grammar = new Grammar($connection); + + $connection->shouldReceive('getName')->andReturn('default'); + $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); + $connection->shouldReceive('escape')->with(1, false)->andReturn(1); + $connection->shouldReceive('escape')->with('br', false)->andReturn("'br'"); + + return $connection; + } +} diff --git a/tests/Database/DatabaseQueryGrammarTest.php b/tests/Database/DatabaseQueryGrammarTest.php index aee7f822caba..ced4930d3f98 100644 --- a/tests/Database/DatabaseQueryGrammarTest.php +++ b/tests/Database/DatabaseQueryGrammarTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Database; +use Illuminate\Database\Connection; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Grammars\Grammar; @@ -19,7 +20,7 @@ protected function tearDown(): void public function testWhereRawReturnsStringWhenExpressionPassed() { $builder = m::mock(Builder::class); - $grammar = new Grammar; + $grammar = new Grammar(m::mock(Connection::class)); $reflection = new ReflectionClass($grammar); $method = $reflection->getMethod('whereRaw'); $expressionArray = ['sql' => new Expression('select * from "users"')]; @@ -32,7 +33,7 @@ public function testWhereRawReturnsStringWhenExpressionPassed() public function testWhereRawReturnsStringWhenStringPassed() { $builder = m::mock(Builder::class); - $grammar = new Grammar; + $grammar = new Grammar(m::mock(Connection::class)); $reflection = new ReflectionClass($grammar); $method = $reflection->getMethod('whereRaw'); $stringArray = ['sql' => 'select * from "users"']; diff --git a/tests/Database/DatabaseSQLiteQueryGrammarTest.php b/tests/Database/DatabaseSQLiteQueryGrammarTest.php index 1cc5d77ed442..c94377352571 100755 --- a/tests/Database/DatabaseSQLiteQueryGrammarTest.php +++ b/tests/Database/DatabaseSQLiteQueryGrammarTest.php @@ -18,8 +18,7 @@ public function testToRawSql() { $connection = m::mock(Connection::class); $connection->shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); - $grammar = new SQLiteGrammar; - $grammar->setConnection($connection); + $grammar = new SQLiteGrammar($connection); $query = $grammar->substituteBindingsIntoRawSql( 'select * from "users" where \'Hello\'\'World?\' IS NOT NULL AND "email" = ?', diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 6454a9d82c60..d9233f548fa9 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -23,19 +23,19 @@ protected function tearDown(): void public function testBasicCreateTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("id" integer primary key autoincrement not null, "email" varchar not null)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $expected = [ @@ -47,12 +47,12 @@ public function testBasicCreateTable() public function testCreateTemporaryTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->temporary(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create temporary table "users" ("id" integer primary key autoincrement not null, "email" varchar not null)', $statements[0]); @@ -60,9 +60,9 @@ public function testCreateTemporaryTable() public function testDropTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table "users"', $statements[0]); @@ -70,9 +70,9 @@ public function testDropTable() public function testDropTableIfExists() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table if exists "users"', $statements[0]); @@ -80,9 +80,9 @@ public function testDropTableIfExists() public function testDropUnique() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropUnique('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "foo"', $statements[0]); @@ -90,14 +90,24 @@ public function testDropUnique() public function testDropIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIndex('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "foo"', $statements[0]); } + public function testDropIndexWithSchema() + { + $blueprint = new Blueprint($this->getConnection(), 'my_schema.users'); + $blueprint->dropIndex('foo'); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('drop index "my_schema"."foo"', $statements[0]); + } + public function testDropColumn() { $db = new Manager; @@ -130,16 +140,16 @@ public function testDropSpatialIndex() $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The database driver in use does not support spatial indexes.'); - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->dropSpatialIndex(['coordinates']); - $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $blueprint->toSql(); } public function testRenameTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rename('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" rename to "foo"', $statements[0]); @@ -183,10 +193,10 @@ public function testRenameIndex() public function testAddingPrimaryKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('foo')->primary(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("foo" varchar not null, primary key ("foo"))', $statements[0]); @@ -194,12 +204,12 @@ public function testAddingPrimaryKey() public function testAddingForeignKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('foo')->primary(); $blueprint->string('order_id'); $blueprint->foreign('order_id')->references('id')->on('orders'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("foo" varchar not null, "order_id" varchar not null, foreign key("order_id") references "orders"("id"), primary key ("foo"))', $statements[0]); @@ -207,9 +217,9 @@ public function testAddingForeignKey() public function testAddingUniqueKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->unique('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create unique index "bar" on "users" ("foo")', $statements[0]); @@ -217,22 +227,38 @@ public function testAddingUniqueKey() public function testAddingIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "baz" on "users" ("foo", "bar")', $statements[0]); } + public function testAddingUniqueKeyWithSchema() + { + $blueprint = new Blueprint($this->getConnection(), 'foo.users'); + $blueprint->unique('foo', 'bar'); + + $this->assertSame(['create unique index "foo"."bar" on "users" ("foo")'], $blueprint->toSql()); + } + + public function testAddingIndexWithSchema() + { + $blueprint = new Blueprint($this->getConnection(), 'foo.users'); + $blueprint->index(['foo', 'bar'], 'baz'); + + $this->assertSame(['create index "foo"."baz" on "users" ("foo", "bar")'], $blueprint->toSql()); + } + public function testAddingSpatialIndex() { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The database driver in use does not support spatial indexes.'); - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->spatialIndex('coordinates'); - $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $blueprint->toSql(); } public function testAddingFluentSpatialIndex() @@ -240,16 +266,16 @@ public function testAddingFluentSpatialIndex() $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The database driver in use does not support spatial indexes.'); - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates')->spatialIndex(); - $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $blueprint->toSql(); } public function testAddingRawIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rawIndex('(function(column))', 'raw_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "raw_index" on "users" ((function(column)))', $statements[0]); @@ -257,9 +283,9 @@ public function testAddingRawIndex() public function testAddingIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" integer primary key autoincrement not null', $statements[0]); @@ -267,9 +293,9 @@ public function testAddingIncrementingID() public function testAddingSmallIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" integer primary key autoincrement not null', $statements[0]); @@ -277,9 +303,9 @@ public function testAddingSmallIncrementingID() public function testAddingMediumIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" integer primary key autoincrement not null', $statements[0]); @@ -287,16 +313,16 @@ public function testAddingMediumIncrementingID() public function testAddingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" integer primary key autoincrement not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -304,22 +330,20 @@ public function testAddingID() public function testAddingForeignID() { - $blueprint = new Blueprint('users'); + $connection = $this->getConnection(); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); + $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); + $connection->shouldReceive('scalar')->andReturn(''); + + $blueprint = new Blueprint($connection, 'users'); $foreignId = $blueprint->foreignId('foo'); $blueprint->foreignId('company_id')->constrained(); $blueprint->foreignId('laravel_idea_id')->constrained(); $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $grammar = $this->getGrammar(); - $connection = $this->getConnection(); - $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); - $connection->shouldReceive('getTablePrefix')->andReturn(''); - $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); - $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); - $connection->shouldReceive('scalar')->andReturn(''); - $statements = $blueprint->toSql($connection, $grammar); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -349,18 +373,16 @@ public function testAddingForeignID() public function testAddingForeignIdSpecifyingIndexNameInConstraint() { - $blueprint = new Blueprint('users'); - $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - - $grammar = $this->getGrammar(); $connection = $this->getConnection(); - $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); $connection->shouldReceive('getTablePrefix')->andReturn(''); $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); $connection->shouldReceive('scalar')->andReturn(''); - $statements = $blueprint->toSql($connection, $grammar); + + $blueprint = new Blueprint($connection, 'users'); + $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); + + $statements = $blueprint->toSql(); $this->assertSame([ 'alter table "users" add column "company_id" integer not null', @@ -373,9 +395,9 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() public function testAddingBigIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "id" integer primary key autoincrement not null', $statements[0]); @@ -383,23 +405,23 @@ public function testAddingBigIncrementingID() public function testAddingString() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar default \'bar\'', $statements[0]); @@ -407,9 +429,9 @@ public function testAddingString() public function testAddingText() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->text('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" text not null', $statements[0]); @@ -417,16 +439,16 @@ public function testAddingText() public function testAddingBigInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -434,16 +456,16 @@ public function testAddingBigInteger() public function testAddingInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -451,16 +473,16 @@ public function testAddingInteger() public function testAddingMediumInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -468,16 +490,16 @@ public function testAddingMediumInteger() public function testAddingTinyInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -485,16 +507,16 @@ public function testAddingTinyInteger() public function testAddingSmallInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" integer primary key autoincrement not null', $statements[0]); @@ -502,9 +524,9 @@ public function testAddingSmallInteger() public function testAddingFloat() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->float('foo', 5); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" float not null', $statements[0]); @@ -512,9 +534,9 @@ public function testAddingFloat() public function testAddingDouble() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->double('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" double not null', $statements[0]); @@ -522,9 +544,9 @@ public function testAddingDouble() public function testAddingDecimal() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->decimal('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" numeric not null', $statements[0]); @@ -532,9 +554,9 @@ public function testAddingDecimal() public function testAddingBoolean() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->boolean('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" tinyint(1) not null', $statements[0]); @@ -542,9 +564,9 @@ public function testAddingBoolean() public function testAddingEnum() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->enum('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "role" varchar check ("role" in (\'member\', \'admin\')) not null', $statements[0]); @@ -552,29 +574,67 @@ public function testAddingEnum() public function testAddingJson() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->json('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" text not null', $statements[0]); } + public function testAddingNativeJson() + { + $connection = m::mock(Connection::class); + $connection + ->shouldReceive('getTablePrefix')->andReturn('') + ->shouldReceive('getConfig')->once()->with('use_native_json')->andReturn(true) + ->shouldReceive('getSchemaGrammar')->andReturn($this->getGrammar($connection)) + ->shouldReceive('getSchemaBuilder')->andReturn($this->getBuilder()) + ->shouldReceive('getServerVersion')->andReturn('3.35') + ->getMock(); + + $blueprint = new Blueprint($connection, 'users'); + $blueprint->json('foo'); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "foo" json not null', $statements[0]); + } + public function testAddingJsonb() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->jsonb('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" text not null', $statements[0]); } + public function testAddingNativeJsonb() + { + $connection = m::mock(Connection::class); + $connection + ->shouldReceive('getTablePrefix')->andReturn('') + ->shouldReceive('getConfig')->once()->with('use_native_jsonb')->andReturn(true) + ->shouldReceive('getSchemaGrammar')->andReturn($this->getGrammar($connection)) + ->shouldReceive('getSchemaBuilder')->andReturn($this->getBuilder()) + ->shouldReceive('getServerVersion')->andReturn('3.35') + ->getMock(); + + $blueprint = new Blueprint($connection, 'users'); + $blueprint->jsonb('foo'); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('alter table "users" add column "foo" jsonb not null', $statements[0]); + } + public function testAddingDate() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->date('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" date not null', $statements[0]); @@ -582,126 +642,126 @@ public function testAddingDate() public function testAddingYear() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->year('birth_year'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "birth_year" integer not null', $statements[0]); } public function testAddingDateTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingDateTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingDateTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingDateTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" time not null', $statements[0]); } public function testAddingTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" time not null', $statements[0]); } public function testAddingTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" time not null', $statements[0]); } public function testAddingTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" time not null', $statements[0]); } public function testAddingTimestamp() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingTimestampWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingTimestampTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingTimestampTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "created_at" datetime not null', $statements[0]); } public function testAddingTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertEquals([ 'alter table "users" add column "created_at" datetime', @@ -711,9 +771,9 @@ public function testAddingTimestamps() public function testAddingTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertEquals([ 'alter table "users" add column "created_at" datetime', @@ -723,9 +783,9 @@ public function testAddingTimestampsTz() public function testAddingRememberToken() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rememberToken(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "remember_token" varchar', $statements[0]); @@ -733,9 +793,9 @@ public function testAddingRememberToken() public function testAddingBinary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->binary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" blob not null', $statements[0]); @@ -743,9 +803,9 @@ public function testAddingBinary() public function testAddingUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar not null', $statements[0]); @@ -753,9 +813,9 @@ public function testAddingUuid() public function testAddingUuidDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "uuid" varchar not null', $statements[0]); @@ -763,22 +823,20 @@ public function testAddingUuidDefaultsColumnName() public function testAddingForeignUuid() { - $blueprint = new Blueprint('users'); + $connection = $this->getConnection(); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); + $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); + $connection->shouldReceive('scalar')->andReturn(''); + + $blueprint = new Blueprint($connection, 'users'); $foreignUuid = $blueprint->foreignUuid('foo'); $blueprint->foreignUuid('company_id')->constrained(); $blueprint->foreignUuid('laravel_idea_id')->constrained(); $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $grammar = $this->getGrammar(); - $connection = $this->getConnection(); - $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); - $connection->shouldReceive('getTablePrefix')->andReturn(''); - $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); - $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); - $connection->shouldReceive('scalar')->andReturn(''); - $statements = $blueprint->toSql($connection, $grammar); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ @@ -808,9 +866,9 @@ public function testAddingForeignUuid() public function testAddingIpAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar not null', $statements[0]); @@ -818,9 +876,9 @@ public function testAddingIpAddress() public function testAddingIpAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "ip_address" varchar not null', $statements[0]); @@ -828,9 +886,9 @@ public function testAddingIpAddressDefaultsColumnName() public function testAddingMacAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "foo" varchar not null', $statements[0]); @@ -838,9 +896,9 @@ public function testAddingMacAddress() public function testAddingMacAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add column "mac_address" varchar not null', $statements[0]); @@ -848,9 +906,9 @@ public function testAddingMacAddressDefaultsColumnName() public function testAddingGeometry() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add column "coordinates" geometry not null', $statements[0]); @@ -858,21 +916,21 @@ public function testAddingGeometry() public function testAddingGeneratedColumn() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->create(); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('"price" - 5'); $blueprint->integer('discounted_stored')->storedAs('"price" - 5'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "products" ("price" integer not null, "discounted_virtual" integer as ("price" - 5), "discounted_stored" integer as ("price" - 5) stored)', $statements[0]); - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs('"price" - 5')->nullable(false); $blueprint->integer('discounted_stored')->storedAs('"price" - 5')->nullable(false); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $expected = [ @@ -885,12 +943,12 @@ public function testAddingGeneratedColumn() public function testAddingGeneratedColumnByExpression() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->create(); $blueprint->integer('price'); $blueprint->integer('discounted_virtual')->virtualAs(new Expression('"price" - 5')); $blueprint->integer('discounted_stored')->storedAs(new Expression('"price" - 5')); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "products" ("price" integer not null, "discounted_virtual" integer as ("price" - 5), "discounted_stored" integer as ("price" - 5) stored)', $statements[0]); @@ -910,32 +968,32 @@ public function testGrammarsAreMacroable() public function testCreateTableWithVirtualAsColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->virtualAs('my_column'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_column" varchar not null, "my_other_column" varchar as (my_column))', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_json_column" varchar not null, "my_other_column" varchar as (json_extract("my_json_column", \'$."some_attribute"\')))', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute->nested'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_json_column" varchar not null, "my_other_column" varchar as (json_extract("my_json_column", \'$."some_attribute"."nested"\')))', $statements[0]); @@ -943,14 +1001,14 @@ public function testCreateTableWithVirtualAsColumn() public function testCreateTableWithVirtualAsColumnWhenJsonColumnHasArrayKey() { - $blueprint = new Blueprint('users'); - $blueprint->create(); - $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); - $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->andReturn(null); - $statements = $blueprint->toSql($conn, $this->getGrammar()); + $blueprint = new Blueprint($conn, 'users'); + $blueprint->create(); + $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); + + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("create table \"users\" (\"my_json_column\" varchar as (json_extract(\"my_json_column\", '$.\"foo\"[0][1]')))", $statements[0]); @@ -958,32 +1016,32 @@ public function testCreateTableWithVirtualAsColumnWhenJsonColumnHasArrayKey() public function testCreateTableWithStoredAsColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_column'); $blueprint->string('my_other_column')->storedAs('my_column'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_column" varchar not null, "my_other_column" varchar as (my_column) stored)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_json_column" varchar not null, "my_other_column" varchar as (json_extract("my_json_column", \'$."some_attribute"\')) stored)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->string('my_json_column'); $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute->nested'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("my_json_column" varchar not null, "my_other_column" varchar as (json_extract("my_json_column", \'$."some_attribute"."nested"\')) stored)', $statements[0]); @@ -991,23 +1049,99 @@ public function testCreateTableWithStoredAsColumn() public function testDroppingColumnsWorks() { - $blueprint = new Blueprint('users', function ($table) { + $blueprint = new Blueprint($this->getConnection(), 'users', function ($table) { $table->dropColumn('name'); }); - $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($this->getConnection(), $this->getGrammar())); + $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql()); } - protected function getConnection() + public function testRenamingAndChangingColumnsWork() { + $builder = mock(SQLiteBuilder::class) + ->makePartial() + ->shouldReceive('getColumns')->andReturn([ + ['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => false, 'default' => null, 'auto_increment' => false, 'comment' => null, 'generation' => null], + ['name' => 'age', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => false, 'default' => null, 'auto_increment' => false, 'comment' => null, 'generation' => null], + ]) + ->shouldReceive('getIndexes')->andReturn([]) + ->shouldReceive('getForeignKeys')->andReturn([]) + ->getMock(); + + $connection = $this->getConnection(builder: $builder); + $connection->shouldReceive('scalar')->with('pragma foreign_keys')->andReturn(false); + + $blueprint = new Blueprint($connection, 'users'); + $blueprint->renameColumn('name', 'first_name'); + $blueprint->integer('age')->change(); + + $this->assertEquals([ + 'alter table "users" rename column "name" to "first_name"', + 'create table "__temp__users" ("first_name" varchar not null, "age" integer not null)', + 'insert into "__temp__users" ("first_name", "age") select "first_name", "age" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', + ], $blueprint->toSql()); + } + + public function testRenamingAndChangingColumnsWorkWithSchema() + { + $builder = mock(SQLiteBuilder::class) + ->makePartial() + ->shouldReceive('getColumns')->andReturn([ + ['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => false, 'default' => null, 'auto_increment' => false, 'comment' => null, 'generation' => null], + ['name' => 'age', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => false, 'default' => null, 'auto_increment' => false, 'comment' => null, 'generation' => null], + ]) + ->shouldReceive('getIndexes')->andReturn([]) + ->shouldReceive('getForeignKeys')->andReturn([]) + ->getMock(); + + $connection = $this->getConnection(builder: $builder); + $connection->shouldReceive('scalar')->with('pragma foreign_keys')->andReturn(false); + + $blueprint = new Blueprint($connection, 'my_schema.users'); + $blueprint->renameColumn('name', 'first_name'); + $blueprint->integer('age')->change(); + + $this->assertEquals([ + 'alter table "my_schema"."users" rename column "name" to "first_name"', + 'create table "my_schema"."__temp__users" ("first_name" varchar not null, "age" integer not null)', + 'insert into "my_schema"."__temp__users" ("first_name", "age") select "first_name", "age" from "my_schema"."users"', + 'drop table "my_schema"."users"', + 'alter table "my_schema"."__temp__users" rename to "users"', + ], $blueprint->toSql()); + } + + protected function getConnection( + ?SQLiteGrammar $grammar = null, + ?SQLiteBuilder $builder = null, + $prefix = '' + ) { $connection = m::mock(Connection::class); - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); + $grammar ??= $this->getGrammar($connection); + $builder ??= $this->getBuilder(); + + return $connection + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->andReturn(null) + ->shouldReceive('getSchemaGrammar')->andReturn($grammar) + ->shouldReceive('getSchemaBuilder')->andReturn($builder) + ->shouldReceive('getServerVersion')->andReturn('3.35') + ->getMock(); + } - return $connection; + public function getGrammar(?Connection $connection = null) + { + return new SQLiteGrammar($connection ?? $this->getConnection()); } - public function getGrammar() + public function getBuilder() { - return new SQLiteGrammar; + return mock(SQLiteBuilder::class) + ->makePartial() + ->shouldReceive('getColumns')->andReturn([]) + ->shouldReceive('getIndexes')->andReturn([]) + ->shouldReceive('getForeignKeys')->andReturn([]) + ->getMock(); } } diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index f92672b2f63e..e8546270597b 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -2,14 +2,11 @@ namespace Illuminate\Tests\Database; +use Closure; use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Builder; -use Illuminate\Database\Schema\Grammars\MariaDbGrammar; use Illuminate\Database\Schema\Grammars\MySqlGrammar; -use Illuminate\Database\Schema\Grammars\PostgresGrammar; -use Illuminate\Database\Schema\Grammars\SQLiteGrammar; -use Illuminate\Database\Schema\Grammars\SqlServerGrammar; use Illuminate\Tests\Database\Fixtures\Models\User; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -24,29 +21,28 @@ protected function tearDown(): void public function testToSqlRunsCommandsFromBlueprint() { - $conn = m::mock(Connection::class); + $conn = $this->getConnection(); $conn->shouldReceive('statement')->once()->with('foo'); $conn->shouldReceive('statement')->once()->with('bar'); - $grammar = m::mock(MySqlGrammar::class); - $blueprint = $this->getMockBuilder(Blueprint::class)->onlyMethods(['toSql'])->setConstructorArgs(['users'])->getMock(); - $blueprint->expects($this->once())->method('toSql')->with($this->equalTo($conn), $this->equalTo($grammar))->willReturn(['foo', 'bar']); + $blueprint = $this->getMockBuilder(Blueprint::class)->onlyMethods(['toSql'])->setConstructorArgs([$conn, 'users'])->getMock(); + $blueprint->expects($this->once())->method('toSql')->willReturn(['foo', 'bar']); - $blueprint->build($conn, $grammar); + $blueprint->build(); } public function testIndexDefaultNames() { - $blueprint = new Blueprint('users'); + $blueprint = $this->getBlueprint(table: 'users'); $blueprint->unique(['foo', 'bar']); $commands = $blueprint->getCommands(); $this->assertSame('users_foo_bar_unique', $commands[0]->index); - $blueprint = new Blueprint('users'); + $blueprint = $this->getBlueprint(table: 'users'); $blueprint->index('foo'); $commands = $blueprint->getCommands(); $this->assertSame('users_foo_index', $commands[0]->index); - $blueprint = new Blueprint('geo'); + $blueprint = $this->getBlueprint(table: 'geo'); $blueprint->spatialIndex('coordinates'); $commands = $blueprint->getCommands(); $this->assertSame('geo_coordinates_spatialindex', $commands[0]->index); @@ -54,17 +50,17 @@ public function testIndexDefaultNames() public function testIndexDefaultNamesWhenPrefixSupplied() { - $blueprint = new Blueprint('users', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'users', prefix: 'prefix_'); $blueprint->unique(['foo', 'bar']); $commands = $blueprint->getCommands(); $this->assertSame('prefix_users_foo_bar_unique', $commands[0]->index); - $blueprint = new Blueprint('users', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'users', prefix: 'prefix_'); $blueprint->index('foo'); $commands = $blueprint->getCommands(); $this->assertSame('prefix_users_foo_index', $commands[0]->index); - $blueprint = new Blueprint('geo', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'geo', prefix: 'prefix_'); $blueprint->spatialIndex('coordinates'); $commands = $blueprint->getCommands(); $this->assertSame('prefix_geo_coordinates_spatialindex', $commands[0]->index); @@ -72,17 +68,17 @@ public function testIndexDefaultNamesWhenPrefixSupplied() public function testDropIndexDefaultNames() { - $blueprint = new Blueprint('users'); + $blueprint = $this->getBlueprint(table: 'users'); $blueprint->dropUnique(['foo', 'bar']); $commands = $blueprint->getCommands(); $this->assertSame('users_foo_bar_unique', $commands[0]->index); - $blueprint = new Blueprint('users'); + $blueprint = $this->getBlueprint(table: 'users'); $blueprint->dropIndex(['foo']); $commands = $blueprint->getCommands(); $this->assertSame('users_foo_index', $commands[0]->index); - $blueprint = new Blueprint('geo'); + $blueprint = $this->getBlueprint(table: 'geo'); $blueprint->dropSpatialIndex(['coordinates']); $commands = $blueprint->getCommands(); $this->assertSame('geo_coordinates_spatialindex', $commands[0]->index); @@ -90,17 +86,17 @@ public function testDropIndexDefaultNames() public function testDropIndexDefaultNamesWhenPrefixSupplied() { - $blueprint = new Blueprint('users', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'users', prefix: 'prefix_'); $blueprint->dropUnique(['foo', 'bar']); $commands = $blueprint->getCommands(); $this->assertSame('prefix_users_foo_bar_unique', $commands[0]->index); - $blueprint = new Blueprint('users', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'users', prefix: 'prefix_'); $blueprint->dropIndex(['foo']); $commands = $blueprint->getCommands(); $this->assertSame('prefix_users_foo_index', $commands[0]->index); - $blueprint = new Blueprint('geo', null, 'prefix_'); + $blueprint = $this->getBlueprint(table: 'geo', prefix: 'prefix_'); $blueprint->dropSpatialIndex(['coordinates']); $commands = $blueprint->getCommands(); $this->assertSame('prefix_geo_coordinates_spatialindex', $commands[0]->index); @@ -108,157 +104,148 @@ public function testDropIndexDefaultNamesWhenPrefixSupplied() public function testDefaultCurrentDateTime() { - $base = new Blueprint('users', function ($table) { - $table->dateTime('created')->useCurrent(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; - $this->assertEquals(['alter table `users` add `created` datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new PostgresGrammar)); - - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); - $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->dateTime('created')->useCurrent(); + })->toSql(); + }; - $blueprint = clone $base; - $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table `users` add `created` datetime not null default CURRENT_TIMESTAMP'], $getSql('MySql')); + $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $getSql('SQLite')); + $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $getSql('SqlServer')); } public function testDefaultCurrentTimestamp() { - $base = new Blueprint('users', function ($table) { - $table->timestamp('created')->useCurrent(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; - $this->assertEquals(['alter table `users` add `created` timestamp not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new PostgresGrammar)); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->timestamp('created')->useCurrent(); + })->toSql(); + }; - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); - $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table `users` add `created` timestamp not null default CURRENT_TIMESTAMP'], $getSql('MySql')); + $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $getSql('SQLite')); + $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $getSql('SqlServer')); } public function testRemoveColumn() { - $base = new Blueprint('users', function ($table) { - $table->string('foo'); - $table->string('remove_this'); - $table->removeColumn('remove_this'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->string('foo'); + $table->string('remove_this'); + $table->removeColumn('remove_this'); + })->toSql(); + }; - $this->assertEquals(['alter table `users` add `foo` varchar(255) not null'], $blueprint->toSql($connection, new MySqlGrammar)); + $this->assertEquals(['alter table `users` add `foo` varchar(255) not null'], $getSql('MySql')); } public function testRenameColumn() { - $base = new Blueprint('users', function ($table) { - $table->renameColumn('foo', 'bar'); - }); - - $connection = m::mock(Connection::class); - $connection->shouldReceive('getServerVersion')->andReturn('8.0.4'); - $connection->shouldReceive('isMaria')->andReturn(false); + $getSql = function ($grammar) { + $connection = $this->getConnection($grammar); + $connection->shouldReceive('getServerVersion')->andReturn('8.0.4'); + $connection->shouldReceive('isMaria')->andReturn(false); - $blueprint = clone $base; - $this->assertEquals(['alter table `users` rename column `foo` to `bar`'], $blueprint->toSql($connection, new MySqlGrammar)); + return (new Blueprint($connection, 'users', function ($table) { + $table->renameColumn('foo', 'bar'); + }))->toSql(); + }; - $blueprint = clone $base; - $this->assertEquals(['alter table "users" rename column "foo" to "bar"'], $blueprint->toSql($connection, new PostgresGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" rename column "foo" to "bar"'], $blueprint->toSql($connection, new SQLiteGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['sp_rename N\'"users"."foo"\', "bar", N\'COLUMN\''], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table `users` rename column `foo` to `bar`'], $getSql('MySql')); + $this->assertEquals(['alter table "users" rename column "foo" to "bar"'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" rename column "foo" to "bar"'], $getSql('SQLite')); + $this->assertEquals(['sp_rename N\'"users"."foo"\', "bar", N\'COLUMN\''], $getSql('SqlServer')); } public function testNativeRenameColumnOnMysql57() { - $blueprint = new Blueprint('users', function ($table) { - $table->renameColumn('name', 'title'); - $table->renameColumn('id', 'key'); - $table->renameColumn('generated', 'new_generated'); - }); - - $connection = m::mock(Connection::class); + $connection = $this->getConnection('MySql'); $connection->shouldReceive('isMaria')->andReturn(false); $connection->shouldReceive('getServerVersion')->andReturn('5.7'); - $connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([ + $connection->getSchemaBuilder()->shouldReceive('getColumns')->andReturn([ ['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false, 'generation' => null], ['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true, 'generation' => null], ['name' => 'generated', 'type' => 'int', 'type_name' => 'int', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => null, 'auto_increment' => false, 'generation' => ['type' => 'stored', 'expression' => 'expression']], ]); + $blueprint = new Blueprint($connection, 'users', function ($table) { + $table->renameColumn('name', 'title'); + $table->renameColumn('id', 'key'); + $table->renameColumn('generated', 'new_generated'); + }); + $this->assertEquals([ "alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'", "alter table `users` change `id` `key` bigint unsigned not null auto_increment comment 'lorem ipsum'", 'alter table `users` change `generated` `new_generated` int as (expression) stored not null', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $blueprint->toSql()); } public function testNativeRenameColumnOnLegacyMariaDB() { - $blueprint = new Blueprint('users', function ($table) { - $table->renameColumn('name', 'title'); - $table->renameColumn('id', 'key'); - $table->renameColumn('generated', 'new_generated'); - $table->renameColumn('foo', 'bar'); - }); - - $connection = m::mock(Connection::class); + $connection = $this->getConnection('MariaDb'); $connection->shouldReceive('isMaria')->andReturn(true); $connection->shouldReceive('getServerVersion')->andReturn('10.1.35'); - $connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([ + $connection->getSchemaBuilder()->shouldReceive('getColumns')->andReturn([ ['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false, 'generation' => null], ['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true, 'generation' => null], ['name' => 'generated', 'type' => 'int', 'type_name' => 'int', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => null, 'auto_increment' => false, 'generation' => ['type' => 'stored', 'expression' => 'expression']], ['name' => 'foo', 'type' => 'int', 'type_name' => 'int', 'nullable' => true, 'collation' => null, 'default' => 'NULL', 'comment' => null, 'auto_increment' => false, 'generation' => null], ]); + $blueprint = new Blueprint($connection, 'users', function ($table) { + $table->renameColumn('name', 'title'); + $table->renameColumn('id', 'key'); + $table->renameColumn('generated', 'new_generated'); + $table->renameColumn('foo', 'bar'); + }); + $this->assertEquals([ "alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'", "alter table `users` change `id` `key` bigint unsigned not null auto_increment comment 'lorem ipsum'", 'alter table `users` change `generated` `new_generated` int as (expression) stored not null', 'alter table `users` change `foo` `bar` int null default NULL', - ], $blueprint->toSql($connection, new MariaDbGrammar)); + ], $blueprint->toSql()); } public function testDropColumn() { - $base = new Blueprint('users', function ($table) { - $table->dropColumn('foo'); - }); - - $connection = m::mock(Connection::class); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->dropColumn('foo'); + })->toSql(); + }; - $blueprint = clone $base; - $this->assertEquals(['alter table `users` drop `foo`'], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" drop column "foo"'], $blueprint->toSql($connection, new PostgresGrammar)); + $this->assertEquals(['alter table `users` drop `foo`'], $getSql('MySql')); + $this->assertEquals(['alter table "users" drop column "foo"'], $getSql('Postgres')); + $this->assertEquals(['alter table "users" drop column "foo"'], $getSql('SQLite')); + $this->assertStringContainsString('alter table "users" drop column "foo"', $getSql('SqlServer')[0]); + } - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); - $this->assertEquals(['alter table "users" drop column "foo"'], $blueprint->toSql($connection, new SQLiteGrammar)); + public function testNativeColumnModifyingOnMySql() + { + $blueprint = $this->getBlueprint('MySql', 'users', function ($table) { + $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->geometry('positions', 'multipolygon', 1234)->storedAs('expression')->change(); + $table->string('old_name', 50)->renameTo('new_name')->change(); + $table->bigIncrements('id')->first()->from(10)->comment('my comment')->change(); + }); - $blueprint = clone $base; - $this->assertStringContainsString('alter table "users" drop column "foo"', $blueprint->toSql($connection, new SqlServerGrammar)[0]); + $this->assertEquals([ + 'alter table `users` modify `amount` double null invisible after `name`', + 'alter table `users` modify `added_at` timestamp(4) not null default CURRENT_TIMESTAMP(4) on update CURRENT_TIMESTAMP(4)', + "alter table `users` modify `difficulty` enum('easy', 'hard') character set utf8mb4 collate 'unicode' not null default 'easy'", + 'alter table `users` modify `positions` multipolygon srid 1234 as (expression) stored', + 'alter table `users` change `old_name` `new_name` varchar(50) not null', + "alter table `users` modify `id` bigint unsigned not null auto_increment comment 'my comment' first", + 'alter table `users` auto_increment = 10', + ], $blueprint->toSql()); } public function testMacroable() @@ -271,391 +258,347 @@ public function testMacroable() return 'bar'; }); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('MySql', 'users', function ($table) { $table->foo(); }); - $connection = m::mock(Connection::class); - - $this->assertEquals(['bar'], $blueprint->toSql($connection, new MySqlGrammar)); + $this->assertEquals(['bar'], $blueprint->toSql()); } public function testDefaultUsingIdMorph() { - $base = new Blueprint('comments', function ($table) { - $table->morphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->morphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) not null', 'alter table `comments` add `commentable_id` bigint unsigned not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDefaultUsingNullableIdMorph() { - $base = new Blueprint('comments', function ($table) { - $table->nullableMorphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->nullableMorphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) null', 'alter table `comments` add `commentable_id` bigint unsigned null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDefaultUsingUuidMorph() { Builder::defaultMorphKeyType('uuid'); - $base = new Blueprint('comments', function ($table) { - $table->morphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->morphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) not null', 'alter table `comments` add `commentable_id` char(36) not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDefaultUsingNullableUuidMorph() { Builder::defaultMorphKeyType('uuid'); - $base = new Blueprint('comments', function ($table) { - $table->nullableMorphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->nullableMorphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) null', 'alter table `comments` add `commentable_id` char(36) null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDefaultUsingUlidMorph() { Builder::defaultMorphKeyType('ulid'); - $base = new Blueprint('comments', function ($table) { - $table->morphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->morphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) not null', 'alter table `comments` add `commentable_id` char(26) not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDefaultUsingNullableUlidMorph() { Builder::defaultMorphKeyType('ulid'); - $base = new Blueprint('comments', function ($table) { - $table->nullableMorphs('commentable'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'comments', function ($table) { + $table->nullableMorphs('commentable'); + })->toSql(); + }; $this->assertEquals([ 'alter table `comments` add `commentable_type` varchar(255) null', 'alter table `comments` add `commentable_id` char(26) null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testGenerateRelationshipColumnWithIncrementalModel() { - $base = new Blueprint('posts', function ($table) { - $table->foreignIdFor('Illuminate\Foundation\Auth\User'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor('Illuminate\Foundation\Auth\User'); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` add `user_id` bigint unsigned not null', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testGenerateRelationshipColumnWithNonIncrementalModel() { - $base = new Blueprint('posts', function ($table) { - $table->foreignIdFor(Fixtures\Models\EloquentModelUsingNonIncrementedInt::class); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor(Fixtures\Models\EloquentModelUsingNonIncrementedInt::class); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` add `model_using_non_incremented_int_id` bigint unsigned not null', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testGenerateRelationshipColumnWithUuidModel() { - $base = new Blueprint('posts', function ($table) { - $table->foreignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` add `model_using_uuid_id` char(36) not null', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testGenerateRelationshipColumnWithUlidModel() { - $base = new Blueprint('posts', function (Blueprint $table) { - $table->foreignIdFor(Fixtures\Models\EloquentModelUsingUlid::class); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor(Fixtures\Models\EloquentModelUsingUlid::class); + })->toSql(); + }; $this->assertEquals([ 'alter table "posts" add column "model_using_ulid_id" char(26) not null', - ], $blueprint->toSql($connection, new PostgresGrammar)); - - $blueprint = clone $base; + ], $getSql('Postgres')); $this->assertEquals([ 'alter table `posts` add `model_using_ulid_id` char(26) not null', - ], $blueprint->toSql($connection, new MySqlGrammar())); + ], $getSql('MySql')); } public function testGenerateRelationshipConstrainedColumn() { - $base = new Blueprint('posts', function ($table) { - $table->foreignIdFor('Illuminate\Foundation\Auth\User')->constrained(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor('Illuminate\Foundation\Auth\User')->constrained(); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` add `user_id` bigint unsigned not null', 'alter table `posts` add constraint `posts_user_id_foreign` foreign key (`user_id`) references `users` (`id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testGenerateRelationshipForModelWithNonStandardPrimaryKeyName() { - $base = new Blueprint('posts', function ($table) { - $table->foreignIdFor(User::class)->constrained(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->foreignIdFor(User::class)->constrained(); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` add `user_internal_id` bigint unsigned not null', 'alter table `posts` add constraint `posts_user_internal_id_foreign` foreign key (`user_internal_id`) references `users` (`internal_id`)', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDropRelationshipColumnWithIncrementalModel() { - $base = new Blueprint('posts', function ($table) { - $table->dropForeignIdFor('Illuminate\Foundation\Auth\User'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->dropForeignIdFor('Illuminate\Foundation\Auth\User'); + })->toSql(); + }; $this->assertEquals([ - 'alter table `posts` drop foreign key `posts_user_id_foreign`', - ], $blueprint->toSql($connection, new MySqlGrammar)); + 'alter table `posts` drop `user_id`', + ], $getSql('MySql')); } public function testDropRelationshipColumnWithUuidModel() { - $base = new Blueprint('posts', function ($table) { - $table->dropForeignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->dropForeignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); + })->toSql(); + }; $this->assertEquals([ - 'alter table `posts` drop foreign key `posts_model_using_uuid_id_foreign`', - ], $blueprint->toSql($connection, new MySqlGrammar)); + 'alter table `posts` drop `model_using_uuid_id`', + ], $getSql('MySql')); } public function testDropConstrainedRelationshipColumnWithIncrementalModel() { - $base = new Blueprint('posts', function ($table) { - $table->dropConstrainedForeignIdFor('Illuminate\Foundation\Auth\User'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->dropConstrainedForeignIdFor('Illuminate\Foundation\Auth\User'); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` drop foreign key `posts_user_id_foreign`', 'alter table `posts` drop `user_id`', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testDropConstrainedRelationshipColumnWithUuidModel() { - $base = new Blueprint('posts', function ($table) { - $table->dropConstrainedForeignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->dropConstrainedForeignIdFor(Fixtures\Models\EloquentModelUsingUuid::class); + })->toSql(); + }; $this->assertEquals([ 'alter table `posts` drop foreign key `posts_model_using_uuid_id_foreign`', 'alter table `posts` drop `model_using_uuid_id`', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); } public function testTinyTextColumn() { - $base = new Blueprint('posts', function ($table) { - $table->tinyText('note'); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; - $this->assertEquals([ - 'alter table `posts` add `note` tinytext not null', - ], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); - $this->assertEquals([ - 'alter table "posts" add column "note" text not null', - ], $blueprint->toSql($connection, new SQLiteGrammar)); - - $blueprint = clone $base; - $this->assertEquals([ - 'alter table "posts" add column "note" varchar(255) not null', - ], $blueprint->toSql($connection, new PostgresGrammar)); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->tinyText('note'); + })->toSql(); + }; - $blueprint = clone $base; - $this->assertEquals([ - 'alter table "posts" add "note" nvarchar(255) not null', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table `posts` add `note` tinytext not null'], $getSql('MySql')); + $this->assertEquals(['alter table "posts" add column "note" text not null'], $getSql('SQLite')); + $this->assertEquals(['alter table "posts" add column "note" varchar(255) not null'], $getSql('Postgres')); + $this->assertEquals(['alter table "posts" add "note" nvarchar(255) not null'], $getSql('SqlServer')); } public function testTinyTextNullableColumn() { - $base = new Blueprint('posts', function ($table) { - $table->tinyText('note')->nullable(); - }); - - $connection = m::mock(Connection::class); - - $blueprint = clone $base; - $this->assertEquals([ - 'alter table `posts` add `note` tinytext null', - ], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); - $this->assertEquals([ - 'alter table "posts" add column "note" text', - ], $blueprint->toSql($connection, new SQLiteGrammar)); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->tinyText('note')->nullable(); + })->toSql(); + }; - $blueprint = clone $base; - $this->assertEquals([ - 'alter table "posts" add column "note" varchar(255) null', - ], $blueprint->toSql($connection, new PostgresGrammar)); - - $blueprint = clone $base; - $this->assertEquals([ - 'alter table "posts" add "note" nvarchar(255) null', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table `posts` add `note` tinytext null'], $getSql('MySql')); + $this->assertEquals(['alter table "posts" add column "note" text'], $getSql('SQLite')); + $this->assertEquals(['alter table "posts" add column "note" varchar(255) null'], $getSql('Postgres')); + $this->assertEquals(['alter table "posts" add "note" nvarchar(255) null'], $getSql('SqlServer')); } public function testRawColumn() { - $base = new Blueprint('posts', function ($table) { - $table->rawColumn('legacy_boolean', 'INT(1)')->nullable(); - }); - - $connection = m::mock(Connection::class); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->rawColumn('legacy_boolean', 'INT(1)')->nullable(); + })->toSql(); + }; - $blueprint = clone $base; $this->assertEquals([ 'alter table `posts` add `legacy_boolean` INT(1) null', - ], $blueprint->toSql($connection, new MySqlGrammar)); + ], $getSql('MySql')); - $blueprint = clone $base; - $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals([ 'alter table "posts" add column "legacy_boolean" INT(1)', - ], $blueprint->toSql($connection, new SQLiteGrammar)); + ], $getSql('SQLite')); - $blueprint = clone $base; $this->assertEquals([ 'alter table "posts" add column "legacy_boolean" INT(1) null', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $getSql('Postgres')); - $blueprint = clone $base; $this->assertEquals([ 'alter table "posts" add "legacy_boolean" INT(1) null', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + ], $getSql('SqlServer')); } public function testTableComment() { - $base = new Blueprint('posts', function (Blueprint $table) { - $table->comment('Look at my comment, it is amazing'); - }); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'posts', function ($table) { + $table->comment('Look at my comment, it is amazing'); + })->toSql(); + }; - $connection = m::mock(Connection::class); + $this->assertEquals(['alter table `posts` comment = \'Look at my comment, it is amazing\''], $getSql('MySql')); + $this->assertEquals(['comment on table "posts" is \'Look at my comment, it is amazing\''], $getSql('Postgres')); + } - $blueprint = clone $base; - $this->assertEquals([ - 'alter table `posts` comment = \'Look at my comment, it is amazing\'', - ], $blueprint->toSql($connection, new MySqlGrammar)); + protected function getConnection(?string $grammar = null, string $prefix = '') + { + $connection = m::mock(Connection::class) + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->with('prefix_indexes')->andReturn(true) + ->getMock(); - $blueprint = clone $base; - $this->assertEquals([ - 'comment on table "posts" is \'Look at my comment, it is amazing\'', - ], $blueprint->toSql($connection, new PostgresGrammar)); + $grammar ??= 'MySql'; + $grammarClass = 'Illuminate\Database\Schema\Grammars\\'.$grammar.'Grammar'; + $builderClass = 'Illuminate\Database\Schema\\'.$grammar.'Builder'; + + $connection->shouldReceive('getSchemaGrammar')->andReturn(new $grammarClass($connection)); + $connection->shouldReceive('getSchemaBuilder')->andReturn(m::mock($builderClass)); + + if ($grammar === 'SQLite') { + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); + } + + if ($grammar === 'MySql') { + $connection->shouldReceive('isMaria')->andReturn(false); + } + + return $connection; + } + + protected function getBlueprint( + ?string $grammar = null, + string $table = '', + ?Closure $callback = null, + string $prefix = '' + ): Blueprint { + $connection = $this->getConnection($grammar, $prefix); + + return new Blueprint($connection, $table, $callback); } } diff --git a/tests/Database/DatabaseSchemaBuilderTest.php b/tests/Database/DatabaseSchemaBuilderTest.php index e5ffd52d3d4e..c069c5f5e773 100644 --- a/tests/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Database/DatabaseSchemaBuilderTest.php @@ -6,7 +6,6 @@ use Illuminate\Database\Query\Processors\Processor; use Illuminate\Database\Schema\Builder; use Illuminate\Database\Schema\Grammars\Grammar; -use LogicException; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; @@ -22,36 +21,35 @@ public function testCreateDatabase() { $connection = m::mock(Connection::class); $grammar = m::mock(stdClass::class); + $grammar->shouldReceive('compileCreateDatabase')->andReturn('sql'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('statement')->with('sql')->andReturnTrue(); $builder = new Builder($connection); - $this->expectException(LogicException::class); - $this->expectExceptionMessage('This database driver does not support creating databases.'); - - $builder->createDatabase('foo'); + $this->assertTrue($builder->createDatabase('foo')); } public function testDropDatabaseIfExists() { $connection = m::mock(Connection::class); $grammar = m::mock(stdClass::class); + $grammar->shouldReceive('compileDropDatabaseIfExists')->andReturn('sql'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('statement')->with('sql')->andReturnTrue(); $builder = new Builder($connection); - $this->expectException(LogicException::class); - $this->expectExceptionMessage('This database driver does not support dropping databases.'); - - $builder->dropDatabaseIfExists('foo'); + $this->assertTrue($builder->dropDatabaseIfExists('foo')); } public function testHasTableCorrectlyCallsGrammar() { $connection = m::mock(Connection::class); - $grammar = m::mock(stdClass::class); + $grammar = m::mock(Grammar::class); $processor = m::mock(Processor::class); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); $builder = new Builder($connection); + $grammar->shouldReceive('compileTableExists'); $grammar->shouldReceive('compileTables')->once()->andReturn('sql'); $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); @@ -82,7 +80,7 @@ public function testGetColumnTypeAddsPrefix() $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'id', 'type_name' => 'integer']]); $builder = new Builder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); - $grammar->shouldReceive('compileColumns')->once()->with('prefix_users')->andReturn('sql'); + $grammar->shouldReceive('compileColumns')->once()->with(null, 'prefix_users')->andReturn('sql'); $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'id', 'type_name' => 'integer']]); $this->assertSame('integer', $builder->getColumnType('users', 'id')); diff --git a/tests/Database/DatabaseSqlServerQueryGrammarTest.php b/tests/Database/DatabaseSqlServerQueryGrammarTest.php index 2960719672e8..f3052b1250b1 100755 --- a/tests/Database/DatabaseSqlServerQueryGrammarTest.php +++ b/tests/Database/DatabaseSqlServerQueryGrammarTest.php @@ -18,8 +18,7 @@ public function testToRawSql() { $connection = m::mock(Connection::class); $connection->shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); - $grammar = new SqlServerGrammar; - $grammar->setConnection($connection); + $grammar = new SqlServerGrammar($connection); $query = $grammar->substituteBindingsIntoRawSql( "select * from [users] where 'Hello''World?' IS NOT NULL AND [email] = ?", diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index 81c15f7aa79d..d06003473498 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -7,6 +7,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\ForeignIdColumnDefinition; use Illuminate\Database\Schema\Grammars\SqlServerGrammar; +use Illuminate\Database\Schema\SqlServerBuilder; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -19,19 +20,19 @@ protected function tearDown(): void public function testBasicCreateTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "users" ("id" int not null identity primary key, "email" nvarchar(255) not null)', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ @@ -39,11 +40,12 @@ public function testBasicCreateTable() 'alter table "users" add "email" nvarchar(255) not null', ], $statements); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(prefix: 'prefix_'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()->setTablePrefix('prefix_')); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "prefix_users" ("id" int not null identity primary key, "email" nvarchar(255) not null)', $statements[0]); @@ -51,29 +53,46 @@ public function testBasicCreateTable() public function testCreateTemporaryTable() { - $blueprint = new Blueprint('users'); + $connection = $this->getConnection(); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $blueprint = new Blueprint($connection, 'users'); $blueprint->create(); $blueprint->temporary(); $blueprint->increments('id'); $blueprint->string('email'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create table "#users" ("id" int not null identity primary key, "email" nvarchar(255) not null)', $statements[0]); } + public function testCreateTemporaryTableWithPrefix() + { + $connection = $this->getConnection(prefix: 'prefix_'); + $blueprint = new Blueprint($connection, 'users'); + $blueprint->create(); + $blueprint->temporary(); + $blueprint->increments('id'); + $blueprint->string('email'); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create table "#prefix_users" ("id" int not null identity primary key, "email" nvarchar(255) not null)', $statements[0]); + } + public function testDropTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table "users"', $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(prefix: 'prefix_'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->drop(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()->setTablePrefix('prefix_')); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop table "prefix_users"', $statements[0]); @@ -81,16 +100,17 @@ public function testDropTable() public function testDropTableIfExists() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('if object_id(N\'"users"\', \'U\') is not null drop table "users"', $statements[0]); - $blueprint = new Blueprint('users'); + $conn = $this->getConnection(prefix: 'prefix_'); + $blueprint = new Blueprint($conn, 'users'); $blueprint->dropIfExists(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()->setTablePrefix('prefix_')); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('if object_id(N\'"prefix_users"\', \'U\') is not null drop table "prefix_users"', $statements[0]); @@ -98,23 +118,23 @@ public function testDropTableIfExists() public function testDropColumn() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertStringContainsString('alter table "users" drop column "foo"', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn(['foo', 'bar']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertStringContainsString('alter table "users" drop column "foo", "bar"', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropColumn('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertStringContainsString('alter table "users" drop column "foo", "bar"', $statements[0]); @@ -122,9 +142,9 @@ public function testDropColumn() public function testDropColumnDropsCreatesSqlToDropDefaultConstraints() { - $blueprint = new Blueprint('foo'); + $blueprint = new Blueprint($this->getConnection(), 'foo'); $blueprint->dropColumn('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame("DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"foo\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"foo\"') AND [name] in ('bar') AND [default_object_id] <> 0;EXEC(@sql);alter table \"foo\" drop column \"bar\"", $statements[0]); @@ -132,9 +152,9 @@ public function testDropColumnDropsCreatesSqlToDropDefaultConstraints() public function testDropPrimary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropPrimary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop constraint "foo"', $statements[0]); @@ -142,9 +162,9 @@ public function testDropPrimary() public function testDropUnique() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropUnique('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "foo" on "users"', $statements[0]); @@ -152,9 +172,9 @@ public function testDropUnique() public function testDropIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropIndex('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "foo" on "users"', $statements[0]); @@ -162,9 +182,9 @@ public function testDropIndex() public function testDropSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->dropSpatialIndex(['coordinates']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('drop index "geo_coordinates_spatialindex" on "geo"', $statements[0]); @@ -172,9 +192,9 @@ public function testDropSpatialIndex() public function testDropForeign() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropForeign('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" drop constraint "foo"', $statements[0]); @@ -182,9 +202,9 @@ public function testDropForeign() public function testDropConstrainedForeignId() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropConstrainedForeignId('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('alter table "users" drop constraint "users_foo_foreign"', $statements[0]); @@ -193,9 +213,9 @@ public function testDropConstrainedForeignId() public function testDropTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertStringContainsString('alter table "users" drop column "created_at", "updated_at"', $statements[0]); @@ -203,9 +223,9 @@ public function testDropTimestamps() public function testDropTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dropTimestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertStringContainsString('alter table "users" drop column "created_at", "updated_at"', $statements[0]); @@ -213,9 +233,9 @@ public function testDropTimestampsTz() public function testDropMorphs() { - $blueprint = new Blueprint('photos'); + $blueprint = new Blueprint($this->getConnection(), 'photos'); $blueprint->dropMorphs('imageable'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('drop index "photos_imageable_type_imageable_id_index" on "photos"', $statements[0]); @@ -224,9 +244,9 @@ public function testDropMorphs() public function testRenameTable() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rename('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('sp_rename N\'"users"\', "foo"', $statements[0]); @@ -234,9 +254,9 @@ public function testRenameTable() public function testRenameIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->renameIndex('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('sp_rename N\'"users"."foo"\', "bar", N\'INDEX\'', $statements[0]); @@ -244,9 +264,9 @@ public function testRenameIndex() public function testAddingPrimaryKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->primary('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add constraint "bar" primary key ("foo")', $statements[0]); @@ -254,9 +274,9 @@ public function testAddingPrimaryKey() public function testAddingUniqueKey() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->unique('foo', 'bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create unique index "bar" on "users" ("foo")', $statements[0]); @@ -264,9 +284,9 @@ public function testAddingUniqueKey() public function testAddingIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->index(['foo', 'bar'], 'baz'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "baz" on "users" ("foo", "bar")', $statements[0]); @@ -274,9 +294,9 @@ public function testAddingIndex() public function testAddingSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->spatialIndex('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create spatial index "geo_coordinates_spatialindex" on "geo" ("coordinates")', $statements[0]); @@ -284,9 +304,9 @@ public function testAddingSpatialIndex() public function testAddingFluentSpatialIndex() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates', 'point')->spatialIndex(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame('create spatial index "geo_coordinates_spatialindex" on "geo" ("coordinates")', $statements[1]); @@ -294,9 +314,9 @@ public function testAddingFluentSpatialIndex() public function testAddingRawIndex() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rawIndex('(function(column))', 'raw_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('create index "raw_index" on "users" ((function(column)))', $statements[0]); @@ -304,9 +324,9 @@ public function testAddingRawIndex() public function testAddingIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->increments('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "id" int not null identity primary key', $statements[0]); @@ -314,9 +334,9 @@ public function testAddingIncrementingID() public function testAddingSmallIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "id" smallint not null identity primary key', $statements[0]); @@ -324,9 +344,9 @@ public function testAddingSmallIncrementingID() public function testAddingMediumIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "id" int not null identity primary key', $statements[0]); @@ -334,16 +354,16 @@ public function testAddingMediumIncrementingID() public function testAddingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "id" bigint not null identity primary key', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->id('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" bigint not null identity primary key', $statements[0]); @@ -351,14 +371,14 @@ public function testAddingID() public function testAddingForeignID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignId = $blueprint->foreignId('foo'); $blueprint->foreignId('company_id')->constrained(); $blueprint->foreignId('laravel_idea_id')->constrained(); $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -376,9 +396,9 @@ public function testAddingForeignID() public function testAddingForeignIdSpecifyingIndexNameInConstraint() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertSame([ 'alter table "users" add "company_id" bigint not null', 'alter table "users" add constraint "my_index" foreign key ("company_id") references "companies" ("id")', @@ -387,9 +407,9 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() public function testAddingBigIncrementingID() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigIncrements('id'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "id" bigint not null identity primary key', $statements[0]); @@ -397,23 +417,23 @@ public function testAddingBigIncrementingID() public function testAddingString() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(255) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(100) not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->string('foo', 100)->nullable()->default('bar'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(100) null default \'bar\'', $statements[0]); @@ -421,9 +441,9 @@ public function testAddingString() public function testAddingText() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->text('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(max) not null', $statements[0]); @@ -431,16 +451,16 @@ public function testAddingText() public function testAddingBigInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" bigint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->bigInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" bigint not null identity primary key', $statements[0]); @@ -448,16 +468,16 @@ public function testAddingBigInteger() public function testAddingInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" int not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->integer('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" int not null identity primary key', $statements[0]); @@ -465,16 +485,16 @@ public function testAddingInteger() public function testAddingMediumInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" int not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->mediumInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" int not null identity primary key', $statements[0]); @@ -482,16 +502,16 @@ public function testAddingMediumInteger() public function testAddingTinyInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" tinyint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->tinyInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" tinyint not null identity primary key', $statements[0]); @@ -499,16 +519,16 @@ public function testAddingTinyInteger() public function testAddingSmallInteger() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" smallint not null', $statements[0]); - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->smallInteger('foo', true); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" smallint not null identity primary key', $statements[0]); @@ -516,9 +536,9 @@ public function testAddingSmallInteger() public function testAddingFloat() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->float('foo', 5); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" float(5) not null', $statements[0]); @@ -526,9 +546,9 @@ public function testAddingFloat() public function testAddingDouble() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->double('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" double precision not null', $statements[0]); @@ -536,9 +556,9 @@ public function testAddingDouble() public function testAddingDecimal() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->decimal('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" decimal(5, 2) not null', $statements[0]); @@ -546,9 +566,9 @@ public function testAddingDecimal() public function testAddingBoolean() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->boolean('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" bit not null', $statements[0]); @@ -556,9 +576,9 @@ public function testAddingBoolean() public function testAddingEnum() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->enum('role', ['member', 'admin']); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "role" nvarchar(255) check ("role" in (N\'member\', N\'admin\')) not null', $statements[0]); @@ -566,9 +586,9 @@ public function testAddingEnum() public function testAddingJson() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->json('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(max) not null', $statements[0]); @@ -576,9 +596,9 @@ public function testAddingJson() public function testAddingJsonb() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->jsonb('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(max) not null', $statements[0]); @@ -586,9 +606,9 @@ public function testAddingJsonb() public function testAddingDate() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->date('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" date not null', $statements[0]); @@ -596,126 +616,126 @@ public function testAddingDate() public function testAddingYear() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->year('birth_year'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "birth_year" int not null', $statements[0]); } public function testAddingDateTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetime not null', $statements[0]); } public function testAddingDateTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTime('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetime2(1) not null', $statements[0]); } public function testAddingDateTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" datetimeoffset not null', $statements[0]); } public function testAddingDateTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->dateTimeTz('foo', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" datetimeoffset(1) not null', $statements[0]); } public function testAddingTime() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" time not null', $statements[0]); } public function testAddingTimeWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->time('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" time(1) not null', $statements[0]); } public function testAddingTimeTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" time not null', $statements[0]); } public function testAddingTimeTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timeTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" time(1) not null', $statements[0]); } public function testAddingTimestamp() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetime not null', $statements[0]); } public function testAddingTimestampWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamp('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetime2(1) not null', $statements[0]); } public function testAddingTimestampTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetimeoffset not null', $statements[0]); } public function testAddingTimestampTzWithPrecision() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampTz('created_at', 1); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "created_at" datetimeoffset(1) not null', $statements[0]); } public function testAddingTimestamps() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestamps(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add "created_at" datetime null', @@ -725,9 +745,9 @@ public function testAddingTimestamps() public function testAddingTimestampsTz() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->timestampsTz(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(2, $statements); $this->assertSame([ 'alter table "users" add "created_at" datetimeoffset null', @@ -737,9 +757,9 @@ public function testAddingTimestampsTz() public function testAddingRememberToken() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->rememberToken(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "remember_token" nvarchar(100) null', $statements[0]); @@ -747,9 +767,9 @@ public function testAddingRememberToken() public function testAddingBinary() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->binary('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" varbinary(max) not null', $statements[0]); @@ -757,9 +777,9 @@ public function testAddingBinary() public function testAddingUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" uniqueidentifier not null', $statements[0]); @@ -767,9 +787,9 @@ public function testAddingUuid() public function testAddingUuidDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->uuid(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "uuid" uniqueidentifier not null', $statements[0]); @@ -777,14 +797,14 @@ public function testAddingUuidDefaultsColumnName() public function testAddingForeignUuid() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $foreignId = $blueprint->foreignUuid('foo'); $blueprint->foreignUuid('company_id')->constrained(); $blueprint->foreignUuid('laravel_idea_id')->constrained(); $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ @@ -802,9 +822,9 @@ public function testAddingForeignUuid() public function testAddingIpAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(45) not null', $statements[0]); @@ -812,9 +832,9 @@ public function testAddingIpAddress() public function testAddingIpAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->ipAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "ip_address" nvarchar(45) not null', $statements[0]); @@ -822,9 +842,9 @@ public function testAddingIpAddressDefaultsColumnName() public function testAddingMacAddress() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress('foo'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "foo" nvarchar(17) not null', $statements[0]); @@ -832,9 +852,9 @@ public function testAddingMacAddress() public function testAddingMacAddressDefaultsColumnName() { - $blueprint = new Blueprint('users'); + $blueprint = new Blueprint($this->getConnection(), 'users'); $blueprint->macAddress(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "users" add "mac_address" nvarchar(17) not null', $statements[0]); @@ -842,9 +862,9 @@ public function testAddingMacAddressDefaultsColumnName() public function testAddingGeometry() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geometry('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add "coordinates" geometry not null', $statements[0]); @@ -852,9 +872,9 @@ public function testAddingGeometry() public function testAddingGeography() { - $blueprint = new Blueprint('geo'); + $blueprint = new Blueprint($this->getConnection(), 'geo'); $blueprint->geography('coordinates'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(1, $statements); $this->assertSame('alter table "geo" add "coordinates" geography not null', $statements[0]); @@ -862,11 +882,11 @@ public function testAddingGeography() public function testAddingGeneratedColumn() { - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->computed('discounted_virtual', 'price - 5'); $blueprint->computed('discounted_stored', 'price - 5')->persisted(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ 'alter table "products" add "price" int not null', @@ -874,11 +894,11 @@ public function testAddingGeneratedColumn() 'alter table "products" add "discounted_stored" as (price - 5) persisted', ], $statements); - $blueprint = new Blueprint('products'); + $blueprint = new Blueprint($this->getConnection(), 'products'); $blueprint->integer('price'); $blueprint->computed('discounted_virtual', new Expression('price - 5')); $blueprint->computed('discounted_stored', new Expression('price - 5'))->persisted(); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $statements = $blueprint->toSql(); $this->assertCount(3, $statements); $this->assertSame([ 'alter table "products" add "price" int not null', @@ -911,16 +931,14 @@ public function testQuoteStringOnArray() public function testCreateDatabase() { - $connection = $this->getConnection(); - - $statement = $this->getGrammar()->compileCreateDatabase('my_database_a', $connection); + $statement = $this->getGrammar()->compileCreateDatabase('my_database_a'); $this->assertSame( 'create database "my_database_a"', $statement ); - $statement = $this->getGrammar()->compileCreateDatabase('my_database_b', $connection); + $statement = $this->getGrammar()->compileCreateDatabase('my_database_b'); $this->assertSame( 'create database "my_database_b"', @@ -945,13 +963,32 @@ public function testDropDatabaseIfExists() ); } - protected function getConnection() + protected function getConnection( + ?SqlServerGrammar $grammar = null, + ?SqlServerBuilder $builder = null, + string $prefix = '' + ) { + $connection = m::mock(Connection::class) + ->shouldReceive('getTablePrefix')->andReturn($prefix) + ->shouldReceive('getConfig')->with('prefix_indexes')->andReturn(null) + ->getMock(); + + $grammar ??= $this->getGrammar($connection); + $builder ??= $this->getBuilder(); + + return $connection + ->shouldReceive('getSchemaGrammar')->andReturn($grammar) + ->shouldReceive('getSchemaBuilder')->andReturn($builder) + ->getMock(); + } + + public function getGrammar(?Connection $connection = null) { - return m::mock(Connection::class); + return new SqlServerGrammar($connection ?? $this->getConnection()); } - public function getGrammar() + public function getBuilder() { - return new SqlServerGrammar; + return mock(SqlServerBuilder::class); } } diff --git a/tests/Database/DatabaseTransactionsManagerTest.php b/tests/Database/DatabaseTransactionsManagerTest.php index 61f7276d92f6..0cf4f8bdc34a 100755 --- a/tests/Database/DatabaseTransactionsManagerTest.php +++ b/tests/Database/DatabaseTransactionsManagerTest.php @@ -177,6 +177,96 @@ public function testCallbackIsExecutedIfNoTransactions() $this->assertEquals(['default', 1], $callbacks[0]); } + public function testCallbacksForRollbackAreAddedToTheCurrentTransaction() + { + $callbacks = []; + + $manager = (new DatabaseTransactionsManager); + + $manager->begin('default', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + }); + + $manager->begin('default', 2); + + $manager->begin('admin', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + }); + + $this->assertCount(1, $manager->getPendingTransactions()[0]->getCallbacksForRollback()); + $this->assertCount(0, $manager->getPendingTransactions()[1]->getCallbacksForRollback()); + $this->assertCount(1, $manager->getPendingTransactions()[2]->getCallbacksForRollback()); + } + + public function testRollbackTransactionsExecutesCallbacks() + { + $callbacks = []; + + $manager = (new DatabaseTransactionsManager); + + $manager->begin('default', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 1]; + }); + + $manager->begin('default', 2); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 2]; + }); + + $manager->begin('admin', 1); + + $manager->rollback('default', 1); + $manager->rollback('default', 0); + + $this->assertCount(2, $callbacks); + $this->assertEquals(['default', 2], $callbacks[0]); + $this->assertEquals(['default', 1], $callbacks[1]); + } + + public function testRollbackExecutesOnlyCallbacksOfTheConnection() + { + $callbacks = []; + + $manager = (new DatabaseTransactionsManager); + + $manager->begin('default', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 1]; + }); + + $manager->begin('default', 2); + $manager->begin('admin', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['admin', 1]; + }); + + $manager->rollback('default', 1); + $manager->rollback('default', 0); + + $this->assertCount(1, $callbacks); + $this->assertEquals(['default', 1], $callbacks[0]); + } + + public function testCallbackForRollbackIsNotExecutedIfNoTransactions() + { + $callbacks = []; + + $manager = (new DatabaseTransactionsManager); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 1]; + }); + + $this->assertCount(0, $callbacks); + } + public function testStageTransactions() { $manager = (new DatabaseTransactionsManager); diff --git a/tests/Database/Fixtures/Models/EloquentResourceCollectionTestModel.php b/tests/Database/Fixtures/Models/EloquentResourceCollectionTestModel.php new file mode 100644 index 000000000000..0596158dc5de --- /dev/null +++ b/tests/Database/Fixtures/Models/EloquentResourceCollectionTestModel.php @@ -0,0 +1,10 @@ +shouldHaveReceived('call')->with([$command, 'handle']); } + public function testProhibitable() + { + $input = new ArrayInput([]); + $output = new NullOutput; + $outputStyle = new OutputStyle($input, $output); + + $resolver = m::mock(ConnectionResolverInterface::class); + + $container = m::mock(Container::class); + $container->shouldReceive('call'); + $container->shouldReceive('runningUnitTests')->andReturn('true'); + $container->shouldReceive('make')->with(OutputStyle::class, m::any())->andReturn( + $outputStyle + ); + $container->shouldReceive('make')->with(Factory::class, m::any())->andReturn( + new Factory($outputStyle) + ); + + $command = new SeedCommand($resolver); + $command->setLaravel($container); + + // call run to set up IO, then fire manually. + $command->run($input, $output); + + SeedCommand::prohibit(); + + Assert::assertSame(Command::FAILURE, $command->handle()); + } + protected function tearDown(): void { + SeedCommand::prohibit(false); + Model::unsetEventDispatcher(); m::close(); diff --git a/tests/Database/SqlServerBuilderTest.php b/tests/Database/SqlServerBuilderTest.php index 039cadb8394a..5ae3689adfd0 100644 --- a/tests/Database/SqlServerBuilderTest.php +++ b/tests/Database/SqlServerBuilderTest.php @@ -17,9 +17,9 @@ protected function tearDown(): void public function testCreateDatabase() { - $grammar = new SqlServerGrammar; - $connection = m::mock(Connection::class); + $grammar = new SqlServerGrammar($connection); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( 'create database "my_temporary_database_a"' @@ -31,9 +31,9 @@ public function testCreateDatabase() public function testDropDatabaseIfExists() { - $grammar = new SqlServerGrammar; - $connection = m::mock(Connection::class); + $grammar = new SqlServerGrammar($connection); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); $connection->shouldReceive('statement')->once()->with( 'drop database if exists "my_temporary_database_b"' diff --git a/tests/Database/migrations/should_run/2016_01_01_200000_create_flights_table.php b/tests/Database/migrations/should_run/2016_01_01_200000_create_flights_table.php new file mode 100644 index 000000000000..edd6f747bf9a --- /dev/null +++ b/tests/Database/migrations/should_run/2016_01_01_200000_create_flights_table.php @@ -0,0 +1,36 @@ +increments('id'); + $table->string('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('flights'); + } +} diff --git a/tests/Encryption/EncrypterTest.php b/tests/Encryption/EncrypterTest.php index fa55d068a663..78c45723105d 100755 --- a/tests/Encryption/EncrypterTest.php +++ b/tests/Encryption/EncrypterTest.php @@ -248,9 +248,11 @@ public static function provideTamperedData() return [ [['iv' => ['value_in_array'], 'value' => '', 'mac' => '']], - [['iv' => new class() {}, 'value' => '', 'mac' => '']], + [['iv' => new class() { + }, 'value' => '', 'mac' => '']], [['iv' => $validIv, 'value' => ['value_in_array'], 'mac' => '']], - [['iv' => $validIv, 'value' => new class() {}, 'mac' => '']], + [['iv' => $validIv, 'value' => new class() { + }, 'mac' => '']], [['iv' => $validIv, 'value' => '', 'mac' => ['value_in_array']]], [['iv' => $validIv, 'value' => '', 'mac' => null]], [['iv' => $validIv, 'value' => '', 'mac' => '', 'tag' => ['value_in_array']]], diff --git a/tests/Events/BroadcastedEventsTest.php b/tests/Events/BroadcastedEventsTest.php index c241938364ba..9b2a84a31ae7 100644 --- a/tests/Events/BroadcastedEventsTest.php +++ b/tests/Events/BroadcastedEventsTest.php @@ -62,6 +62,92 @@ public function testShouldBroadcastFail() $this->assertFalse($d->shouldBroadcast([$event])); } + + public function testBroadcastWithMultipleChannels() + { + $d = new Dispatcher($container = m::mock(Container::class)); + $broadcast = m::mock(BroadcastFactory::class); + $broadcast->shouldReceive('queue')->once(); + $container->shouldReceive('make')->once()->with(BroadcastFactory::class)->andReturn($broadcast); + + $event = new class implements ShouldBroadcast + { + public function broadcastOn() + { + return ['channel-1', 'channel-2']; + } + }; + + $d->dispatch($event); + } + + public function testBroadcastWithCustomConnectionName() + { + $d = new Dispatcher($container = m::mock(Container::class)); + $broadcast = m::mock(BroadcastFactory::class); + $broadcast->shouldReceive('queue')->once(); + $container->shouldReceive('make')->once()->with(BroadcastFactory::class)->andReturn($broadcast); + + $event = new class implements ShouldBroadcast + { + public $connection = 'custom-connection'; + + public function broadcastOn() + { + return ['test-channel']; + } + }; + + $d->dispatch($event); + } + + public function testBroadcastWithCustomEventName() + { + $d = new Dispatcher($container = m::mock(Container::class)); + $broadcast = m::mock(BroadcastFactory::class); + $broadcast->shouldReceive('queue')->once(); + $container->shouldReceive('make')->once()->with(BroadcastFactory::class)->andReturn($broadcast); + + $event = new class implements ShouldBroadcast + { + public function broadcastOn() + { + return ['test-channel']; + } + + public function broadcastAs() + { + return 'custom-event-name'; + } + }; + + $d->dispatch($event); + } + + public function testBroadcastWithCustomPayload() + { + $d = new Dispatcher($container = m::mock(Container::class)); + $broadcast = m::mock(BroadcastFactory::class); + $broadcast->shouldReceive('queue')->once(); + $container->shouldReceive('make')->once()->with(BroadcastFactory::class)->andReturn($broadcast); + + $event = new class implements ShouldBroadcast + { + public $customData = 'test-data'; + + public function broadcastOn() + { + return ['test-channel']; + } + + public function broadcastWith() + { + return ['custom' => $this->customData]; + } + }; + + $d->dispatch($event); + } } class BroadcastEvent implements ShouldBroadcast diff --git a/tests/Filesystem/FilesystemManagerTest.php b/tests/Filesystem/FilesystemManagerTest.php index 8850c5557a06..4bc8e0545a73 100644 --- a/tests/Filesystem/FilesystemManagerTest.php +++ b/tests/Filesystem/FilesystemManagerTest.php @@ -99,6 +99,38 @@ public function testCanBuildScopedDisks() } } + public function testCanBuildScopedDiskFromScopedDisk() + { + try { + $filesystem = new FilesystemManager(tap(new Application, function ($app) { + $app['config'] = [ + 'filesystems.disks.local' => [ + 'driver' => 'local', + 'root' => 'root-to-be-scoped', + ], + 'filesystems.disks.scoped-from-root' => [ + 'driver' => 'scoped', + 'disk' => 'local', + 'prefix' => 'scoped-from-root-prefix', + ], + ]; + })); + + $root = $filesystem->disk('local'); + $nestedScoped = $filesystem->build([ + 'driver' => 'scoped', + 'disk' => 'scoped-from-root', + 'prefix' => 'nested-scoped-prefix', + ]); + + $nestedScoped->put('dirname/filename.txt', 'file content'); + $this->assertEquals('file content', $root->get('scoped-from-root-prefix/nested-scoped-prefix/dirname/filename.txt')); + $root->deleteDirectory('scoped-from-root-prefix'); + } finally { + rmdir(__DIR__.'/../../root-to-be-scoped'); + } + } + #[RequiresOperatingSystem('Linux|Darwin')] public function testCanBuildScopedDisksWithVisibility() { diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php index fe26087c6274..1930ff15f963 100755 --- a/tests/Filesystem/FilesystemTest.php +++ b/tests/Filesystem/FilesystemTest.php @@ -648,6 +648,19 @@ public function testHash() $this->assertSame('76d3bc41c9f588f7fcd0d5bf4718f8f84b1c41b20882703100b9eb9413807c01', $filesystem->hash(self::$tempDir.'/foo.txt', 'sha3-256')); } + public function testLastModifiedReturnsTimestamp() + { + $path = self::$tempDir.'/timestamp.txt'; + file_put_contents($path, 'test content'); + + $filesystem = new Filesystem; + $timestamp = $filesystem->lastModified($path); + + $this->assertIsInt($timestamp); + $this->assertGreaterThan(0, $timestamp); + $this->assertEquals(filemtime($path), $timestamp); + } + /** * @param string $file * @return int @@ -659,4 +672,42 @@ private function getFilePermissions($file) return (int) base_convert($filePerms, 8, 10); } + + public function testFileCreationAndContentVerification() + { + $files = new Filesystem; + + $testContent = 'This is a test file content'; + $filePath = self::$tempDir.'/test.txt'; + + $files->put($filePath, $testContent); + + $this->assertTrue($files->exists($filePath)); + $this->assertSame($testContent, $files->get($filePath)); + $this->assertEquals(strlen($testContent), $files->size($filePath)); + } + + public function testDirectoryOperationsWithSubdirectories() + { + $files = new Filesystem; + + $dirPath = self::$tempDir.'/test_dir'; + $subDirPath = $dirPath.'/sub_dir'; + + $this->assertTrue($files->makeDirectory($dirPath)); + $this->assertTrue($files->isDirectory($dirPath)); + + $this->assertTrue($files->makeDirectory($subDirPath)); + $this->assertTrue($files->isDirectory($subDirPath)); + + $filePath = $subDirPath.'/test.txt'; + $files->put($filePath, 'test content'); + + $this->assertTrue($files->exists($filePath)); + + $allFiles = $files->allFiles($dirPath); + + $this->assertCount(1, $allFiles); + $this->assertEquals('test.txt', $allFiles[0]->getFilename()); + } } diff --git a/tests/Foundation/Configuration/ExceptionsTest.php b/tests/Foundation/Configuration/ExceptionsTest.php index 704ca02b8a97..fc868e6c5f4c 100644 --- a/tests/Foundation/Configuration/ExceptionsTest.php +++ b/tests/Foundation/Configuration/ExceptionsTest.php @@ -13,11 +13,6 @@ class ExceptionsTest extends TestCase { - public function tearDown(): void - { - parent::tearDown(); - } - public function testStopIgnoring() { $container = new Container; diff --git a/tests/Foundation/Configuration/MiddlewareTest.php b/tests/Foundation/Configuration/MiddlewareTest.php index 3971b895cac1..d54169a6c9dc 100644 --- a/tests/Foundation/Configuration/MiddlewareTest.php +++ b/tests/Foundation/Configuration/MiddlewareTest.php @@ -21,7 +21,7 @@ class MiddlewareTest extends TestCase { - public function tearDown(): void + protected function tearDown(): void { parent::tearDown(); diff --git a/tests/Foundation/FoundationExceptionsHandlerTest.php b/tests/Foundation/FoundationExceptionsHandlerTest.php index c4f1044f1e55..98af2559a578 100644 --- a/tests/Foundation/FoundationExceptionsHandlerTest.php +++ b/tests/Foundation/FoundationExceptionsHandlerTest.php @@ -571,6 +571,36 @@ public function testAssertExceptionIsThrown() } } + public function testAssertNoExceptionIsThrown() + { + try { + $this->assertDoesntThrow(function () { + throw new Exception; + }); + + $testFailed = true; + } catch (AssertionFailedError) { + $testFailed = false; + } + + if ($testFailed) { + Assert::fail('assertDoesntThrow failed: thrown exception was not detected.'); + } + + try { + $this->assertDoesntThrow(function () { + }); + + $testFailed = false; + } catch (AssertionFailedError) { + $testFailed = true; + } + + if ($testFailed) { + Assert::fail('assertDoesntThrow failed: exception was detected while no exception was thrown.'); + } + } + public function testItReportsDuplicateExceptions() { $reported = []; diff --git a/tests/Foundation/FoundationInteractsWithTimeTest.php b/tests/Foundation/FoundationInteractsWithTimeTest.php new file mode 100644 index 000000000000..ce2435bea527 --- /dev/null +++ b/tests/Foundation/FoundationInteractsWithTimeTest.php @@ -0,0 +1,78 @@ +freezeTime(); + + $this->assertTrue(Carbon::hasTestNow()); + $this->assertInstanceOf(\DateTimeInterface::class, $actual); + $this->assertTrue(Carbon::getTestNow()->eq($actual)); + } + + public function testFreezeTimeReturnsCallbackResult() + { + $actual = $this->freezeTime(function () { + return 12345; + }); + + $this->assertSame(12345, $actual); + $this->assertFalse(Carbon::hasTestNow()); + } + + public function testFreezeTimeReturnsCallbackResultEvenWhenNull() + { + $actual = $this->freezeTime(function () { + return null; + }); + + $this->assertNull($actual); + $this->assertFalse(Carbon::hasTestNow()); + } + + public function testFreezeSecondReturnsFrozenTime() + { + $actual = $this->freezeSecond(); + + $this->assertTrue(Carbon::hasTestNow()); + $this->assertInstanceOf(\DateTimeInterface::class, $actual); + $this->assertTrue(Carbon::getTestNow()->eq($actual)); + $this->assertSame(0, $actual->milliseconds); + } + + public function testFreezeSecondReturnsCallbackResult() + { + $actual = $this->freezeSecond(function () { + return 12345; + }); + + $this->assertSame(12345, $actual); + $this->assertFalse(Carbon::hasTestNow()); + } + + public function testFreezeSecondReturnsCallbackResultEvenWhenNull() + { + $actual = $this->freezeSecond(function () { + return null; + }); + + $this->assertNull($actual); + $this->assertFalse(Carbon::hasTestNow()); + } +} diff --git a/tests/Foundation/FoundationViteTest.php b/tests/Foundation/FoundationViteTest.php index 4a113f107cc9..b54f018d9c83 100644 --- a/tests/Foundation/FoundationViteTest.php +++ b/tests/Foundation/FoundationViteTest.php @@ -1693,6 +1693,18 @@ public function testItCanConfigureThePrefetchTriggerEvent() $this->cleanViteManifest($buildDir); } + public function testItCanFlushState() + { + $this->makeViteManifest(); + + app(Vite::class)('resources/js/app.js'); + app()->forgetScopedInstances(); + $this->assertCount(1, app(Vite::class)->preloadedAssets()); + + app(Vite::class)->flush(); + $this->assertCount(0, app(Vite::class)->preloadedAssets()); + } + protected function cleanViteManifest($path = 'build') { if (file_exists(public_path("{$path}/manifest.json"))) { diff --git a/tests/Foundation/Http/Middleware/ValidatePathEncodingTest.php b/tests/Foundation/Http/Middleware/ValidatePathEncodingTest.php new file mode 100644 index 000000000000..67a0b4ea22b7 --- /dev/null +++ b/tests/Foundation/Http/Middleware/ValidatePathEncodingTest.php @@ -0,0 +1,53 @@ +server->set('REQUEST_METHOD', 'GET'); + $symfonyRequest->server->set('REQUEST_URI', $path); + $request = Request::createFromBase($symfonyRequest); + + $response = $middleware->handle($request, fn () => new Response('OK')); + + $this->assertSame(200, $response->status()); + $this->assertSame('OK', $response->content()); + } + + #[TestWith(['%C0'])] + #[TestWith(['%c0'])] + public function testInvalidPathsAreFailing(string $path): void + { + $middleware = new ValidatePathEncoding; + $symfonyRequest = new SymfonyRequest; + $symfonyRequest->server->set('REQUEST_METHOD', 'GET'); + $symfonyRequest->server->set('REQUEST_URI', $path); + $request = Request::createFromBase($symfonyRequest); + + try { + $middleware->handle($request, fn () => new Response('OK')); + + $this->fail('MalformedUrlExceptions should have been thrown.'); + } catch(MalformedUrlException $e) { + $this->assertSame(400, $e->getStatusCode()); + $this->assertSame('Malformed URL.', $e->getMessage()); + } + } +} diff --git a/tests/Foundation/Testing/DatabaseTruncationTest.php b/tests/Foundation/Testing/DatabaseTruncationTest.php index 8b28d69cab76..1f972145cdbd 100644 --- a/tests/Foundation/Testing/DatabaseTruncationTest.php +++ b/tests/Foundation/Testing/DatabaseTruncationTest.php @@ -47,8 +47,8 @@ protected function tearDown(): void public function testTruncateTables() { $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => null, 'name' => 'foo'], - ['schema' => null, 'name' => 'bar'], + ['schema' => null, 'name' => 'foo', 'schema_qualified_name' => 'foo'], + ['schema' => null, 'name' => 'bar', 'schema_qualified_name' => 'bar'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -61,10 +61,10 @@ public function testTruncateTablesWithTablesToTruncateProperty() $this->tablesToTruncate = ['foo', 'bar', 'qux']; $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => null, 'name' => 'migrations'], - ['schema' => null, 'name' => 'foo'], - ['schema' => null, 'name' => 'bar'], - ['schema' => null, 'name' => 'baz'], + ['schema' => null, 'name' => 'migrations', 'schema_qualified_name' => 'migrations'], + ['schema' => null, 'name' => 'foo', 'schema_qualified_name' => 'foo'], + ['schema' => null, 'name' => 'bar', 'schema_qualified_name' => 'bar'], + ['schema' => null, 'name' => 'baz', 'schema_qualified_name' => 'baz'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -77,10 +77,10 @@ public function testTruncateTablesWithExceptTablesProperty() $this->exceptTables = ['baz', 'qux']; $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => null, 'name' => 'migrations'], - ['schema' => null, 'name' => 'foo'], - ['schema' => null, 'name' => 'bar'], - ['schema' => null, 'name' => 'baz'], + ['schema' => null, 'name' => 'migrations', 'schema_qualified_name' => 'migrations'], + ['schema' => null, 'name' => 'foo', 'schema_qualified_name' => 'foo'], + ['schema' => null, 'name' => 'bar', 'schema_qualified_name' => 'bar'], + ['schema' => null, 'name' => 'baz', 'schema_qualified_name' => 'baz'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -91,12 +91,12 @@ public function testTruncateTablesWithExceptTablesProperty() public function testTruncateTablesWithSchema() { $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => 'public', 'name' => 'migrations'], - ['schema' => 'public', 'name' => 'foo'], - ['schema' => 'public', 'name' => 'bar'], - ['schema' => 'private', 'name' => 'migrations'], - ['schema' => 'private', 'name' => 'foo'], - ['schema' => 'private', 'name' => 'baz'], + ['schema' => 'public', 'name' => 'migrations', 'schema_qualified_name' => 'public.migrations'], + ['schema' => 'public', 'name' => 'foo', 'schema_qualified_name' => 'public.foo'], + ['schema' => 'public', 'name' => 'bar', 'schema_qualified_name' => 'public.bar'], + ['schema' => 'private', 'name' => 'migrations', 'schema_qualified_name' => 'private.migrations'], + ['schema' => 'private', 'name' => 'foo', 'schema_qualified_name' => 'private.foo'], + ['schema' => 'private', 'name' => 'baz', 'schema_qualified_name' => 'private.baz'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -109,13 +109,13 @@ public function testTruncateTablesWithSchemaTablesToTruncateProperty() $this->tablesToTruncate = ['foo', 'public.bar']; $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => 'public', 'name' => 'migrations'], - ['schema' => 'public', 'name' => 'foo'], - ['schema' => 'public', 'name' => 'bar'], - ['schema' => 'public', 'name' => 'baz'], - ['schema' => 'private', 'name' => 'migrations'], - ['schema' => 'private', 'name' => 'foo'], - ['schema' => 'private', 'name' => 'bar'], + ['schema' => 'public', 'name' => 'migrations', 'schema_qualified_name' => 'public.migrations'], + ['schema' => 'public', 'name' => 'foo', 'schema_qualified_name' => 'public.foo'], + ['schema' => 'public', 'name' => 'bar', 'schema_qualified_name' => 'public.bar'], + ['schema' => 'public', 'name' => 'baz', 'schema_qualified_name' => 'public.baz'], + ['schema' => 'private', 'name' => 'migrations', 'schema_qualified_name' => 'private.migrations'], + ['schema' => 'private', 'name' => 'foo', 'schema_qualified_name' => 'private.foo'], + ['schema' => 'private', 'name' => 'bar', 'schema_qualified_name' => 'private.bar'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -128,13 +128,13 @@ public function testTruncateTablesWithSchemaAndExceptTablesProperty() $this->exceptTables = ['foo', 'public.bar']; $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => 'public', 'name' => 'migrations'], - ['schema' => 'public', 'name' => 'foo'], - ['schema' => 'public', 'name' => 'bar'], - ['schema' => 'public', 'name' => 'baz'], - ['schema' => 'private', 'name' => 'migrations'], - ['schema' => 'private', 'name' => 'foo'], - ['schema' => 'private', 'name' => 'bar'], + ['schema' => 'public', 'name' => 'migrations', 'schema_qualified_name' => 'public.migrations'], + ['schema' => 'public', 'name' => 'foo', 'schema_qualified_name' => 'public.foo'], + ['schema' => 'public', 'name' => 'bar', 'schema_qualified_name' => 'public.bar'], + ['schema' => 'public', 'name' => 'baz', 'schema_qualified_name' => 'public.baz'], + ['schema' => 'private', 'name' => 'migrations', 'schema_qualified_name' => 'private.migrations'], + ['schema' => 'private', 'name' => 'foo', 'schema_qualified_name' => 'private.foo'], + ['schema' => 'private', 'name' => 'bar', 'schema_qualified_name' => 'private.bar'], ]); $this->truncateTablesForConnection($connection, 'test'); @@ -145,11 +145,11 @@ public function testTruncateTablesWithSchemaAndExceptTablesProperty() public function testTruncateTablesWithConnectionPrefix() { $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => 'public', 'name' => 'my_migrations'], - ['schema' => 'public', 'name' => 'my_foo'], - ['schema' => 'public', 'name' => 'my_baz'], - ['schema' => 'private', 'name' => 'my_migrations'], - ['schema' => 'private', 'name' => 'my_foo'], + ['schema' => 'public', 'name' => 'my_migrations', 'schema_qualified_name' => 'public.my_migrations'], + ['schema' => 'public', 'name' => 'my_foo', 'schema_qualified_name' => 'public.my_foo'], + ['schema' => 'public', 'name' => 'my_baz', 'schema_qualified_name' => 'public.my_baz'], + ['schema' => 'private', 'name' => 'my_migrations', 'schema_qualified_name' => 'private.my_migrations'], + ['schema' => 'private', 'name' => 'my_foo', 'schema_qualified_name' => 'private.my_foo'], ], 'my_'); $this->truncateTablesForConnection($connection, 'test'); @@ -160,14 +160,14 @@ public function testTruncateTablesWithConnectionPrefix() public function testTruncateTablesOnPgsqlWithSearchPath() { $connection = $this->arrangeConnection($truncatedTables, [ - ['schema' => 'public', 'name' => 'migrations'], - ['schema' => 'public', 'name' => 'foo'], - ['schema' => 'public', 'name' => 'bar'], - ['schema' => 'my_schema', 'name' => 'foo'], - ['schema' => 'my_schema', 'name' => 'baz'], - ['schema' => 'private', 'name' => 'migrations'], - ['schema' => 'private', 'name' => 'foo'], - ['schema' => 'private', 'name' => 'baz'], + ['schema' => 'public', 'name' => 'migrations', 'schema_qualified_name' => 'public.migrations'], + ['schema' => 'public', 'name' => 'foo', 'schema_qualified_name' => 'public.foo'], + ['schema' => 'public', 'name' => 'bar', 'schema_qualified_name' => 'public.bar'], + ['schema' => 'my_schema', 'name' => 'foo', 'schema_qualified_name' => 'my_schema.foo'], + ['schema' => 'my_schema', 'name' => 'baz', 'schema_qualified_name' => 'my_schema.baz'], + ['schema' => 'private', 'name' => 'migrations', 'schema_qualified_name' => 'private.migrations'], + ['schema' => 'private', 'name' => 'foo', 'schema_qualified_name' => 'private.foo'], + ['schema' => 'private', 'name' => 'baz', 'schema_qualified_name' => 'private.baz'], ], '', PostgresBuilder::class, ['my_schema', 'public']); $this->truncateTablesForConnection($connection, 'test'); @@ -181,11 +181,12 @@ private function arrangeConnection( $actual = []; $schema = m::mock($builder ?? Builder::class); - $schema->shouldReceive('getTables')->once()->andReturn($allTables); - - if ($builder === PostgresBuilder::class && $schemas) { - $schema->shouldReceive('getSchemas')->once()->andReturn($schemas); - } + $schema->shouldReceive('getTables')->with($schemas)->once()->andReturn( + empty($schemas) + ? $allTables + : array_filter($allTables, fn ($table) => in_array($table['schema'], $schemas)) + ); + $schema->shouldReceive('getCurrentSchemaListing')->once()->andReturn($schemas); $connection = m::mock(Connection::class); $connection->shouldReceive('getTablePrefix')->andReturn($prefix); diff --git a/tests/Hashing/HasherTest.php b/tests/Hashing/HasherTest.php index 137f8345b288..2547a9fea588 100755 --- a/tests/Hashing/HasherTest.php +++ b/tests/Hashing/HasherTest.php @@ -59,6 +59,13 @@ public function testBasicBcryptHashing() $this->assertTrue($this->hashManager->isHashed($value)); } + public function testBcryptValueTooLong() + { + $this->expectException(\InvalidArgumentException::class); + $hasher = new BcryptHasher(['limit' => 72]); + $hasher->make(str_repeat('a', 73)); + } + public function testBasicArgon2iHashing() { $hasher = new ArgonHasher; diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 77570b385254..56ee25932b0f 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -33,6 +33,7 @@ use Mockery as m; use OutOfBoundsException; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -530,11 +531,12 @@ public function testCanSendFormData() }); } - public function testCanSendArrayableFormData() + #[DataProvider('methodsReceivingArrayableDataProvider')] + public function testCanSendArrayableFormData(string $method) { $this->factory->fake(); - $this->factory->asForm()->post('http://foo.com/form', new Fluent([ + $this->factory->asForm()->{$method}('http://foo.com/form', new Fluent([ 'name' => 'Taylor', 'title' => 'Laravel Developer', ])); @@ -546,11 +548,12 @@ public function testCanSendArrayableFormData() }); } - public function testCanSendJsonSerializableData() + #[DataProvider('methodsReceivingArrayableDataProvider')] + public function testCanSendJsonSerializableData(string $method) { $this->factory->fake(); - $this->factory->asJson()->post('http://foo.com/form', new class implements JsonSerializable + $this->factory->asJson()->{$method}('http://foo.com/form', new class implements JsonSerializable { public function jsonSerialize(): mixed { @@ -568,11 +571,12 @@ public function jsonSerialize(): mixed }); } - public function testPrefersJsonSerializableOverArrayableData() + #[DataProvider('methodsReceivingArrayableDataProvider')] + public function testPrefersJsonSerializableOverArrayableData(string $method) { $this->factory->fake(); - $this->factory->asJson()->post('http://foo.com/form', new class implements JsonSerializable, Arrayable + $this->factory->asJson()->{$method}('http://foo.com/form', new class implements JsonSerializable, Arrayable { public function jsonSerialize(): mixed { @@ -2330,6 +2334,22 @@ public function testExceptionThrownInRetryCallbackIsReturnedWithoutRetryingInPoo $this->factory->assertSentCount(1); } + public function testExceptionThrowInMiddlewareAllowsRetry() + { + $middleware = Middleware::mapRequest(function (RequestInterface $request) { + throw new RuntimeException; + }); + + $this->expectException(RuntimeException::class); + + $this->factory->fake(function (Request $request) { + return $this->factory->response('Fake'); + })->withMiddleware($middleware) + ->retry(3, 1, function (Exception $exception, PendingRequest $request) { + return true; + })->post('https://example.com'); + } + public function testRequestsWillBeWaitingSleepMillisecondsReceivedInBackoffArray() { Sleep::fake(); @@ -2358,6 +2378,16 @@ public function testRequestsWillBeWaitingSleepMillisecondsReceivedInBackoffArray ]); } + public function testFailedRequest() + { + $requestException = $this->factory->failedRequest(['code' => 'not_found'], 404, ['X-RateLimit-Remaining' => 199]); + + $this->assertInstanceOf(RequestException::class, $requestException); + $this->assertEqualsCanonicalizing(['code' => 'not_found'], $requestException->response->json()); + $this->assertEquals(404, $requestException->response->status()); + $this->assertEquals(199, $requestException->response->header('X-RateLimit-Remaining')); + } + public function testFakeConnectionException() { $this->factory->fake($this->factory->failedConnection('Fake')); @@ -3472,6 +3502,16 @@ public function testItCanCreatePendingRequest() $this->assertInstanceOf(PendingRequest::class, $factory->createPendingRequest()); } + + public static function methodsReceivingArrayableDataProvider() + { + return [ + 'patch' => ['patch'], + 'put' => ['put'], + 'post' => ['post'], + 'delete' => ['delete'], + ]; + } } class CustomFactory extends Factory diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index e8ccd145fd50..249b3043691e 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -1067,6 +1067,16 @@ public function testMergeIfMissingMethod() $request->mergeIfMissing($merge); $this->assertSame('Taylor', $request->input('name')); $this->assertSame(1, $request->input('boolean_setting')); + + $request = Request::create('/', 'GET', ['user' => ['first_name' => 'Taylor', 'email' => 'taylor@laravel.com']]); + $merge = ['user.last_name' => 'Otwell']; + $request->mergeIfMissing($merge); + $this->assertSame('Otwell', $request->input('user.last_name')); + + $request = Request::create('/', 'GET', ['user' => ['first_name' => 'Taylor', 'email' => 'taylor@laravel.com']]); + $merge = ['user.first_name' => 'John']; + $request->mergeIfMissing($merge); + $this->assertSame('Taylor', $request->input('user.first_name')); } public function testReplaceMethod() diff --git a/tests/Http/JsonResourceTest.php b/tests/Http/JsonResourceTest.php index a39b3e402b52..5e1e0d029ae7 100644 --- a/tests/Http/JsonResourceTest.php +++ b/tests/Http/JsonResourceTest.php @@ -12,7 +12,8 @@ class JsonResourceTest extends TestCase { public function testJsonResourceNullAttributes() { - $model = new class extends Model {}; + $model = new class extends Model { + }; $model->setAttribute('relation_sum_column', null); $model->setAttribute('relation_count', null); @@ -31,7 +32,8 @@ public function testJsonResourceNullAttributes() public function testJsonResourceToJsonSucceedsWithPriorErrors(): void { - $model = new class extends Model {}; + $model = new class extends Model { + }; $resource = m::mock(JsonResource::class, ['resource' => $model]) ->makePartial() diff --git a/tests/Http/Middleware/CacheTest.php b/tests/Http/Middleware/CacheTest.php index 95958446b89b..242991854a2b 100644 --- a/tests/Http/Middleware/CacheTest.php +++ b/tests/Http/Middleware/CacheTest.php @@ -108,7 +108,7 @@ public function testGenerateEtag() return new Response('some content'); }, 'etag;max_age=100;s_maxage=200'); - $this->assertSame('"9893532233caff98cd083a116b013c0b"', $response->getEtag()); + $this->assertSame('"4f1b32bff4356281946800d355007128"', $response->getEtag()); $this->assertSame('max-age=100, public, s-maxage=200', $response->headers->get('Cache-Control')); } @@ -124,7 +124,7 @@ public function testDoesNotOverrideEtag() public function testIsNotModified() { $request = new Request; - $request->headers->set('If-None-Match', '"9893532233caff98cd083a116b013c0b"'); + $request->headers->set('If-None-Match', '"4f1b32bff4356281946800d355007128"'); $response = (new Cache)->handle($request, function () { return new Response('some content'); diff --git a/tests/Http/Middleware/VitePreloadingTest.php b/tests/Http/Middleware/VitePreloadingTest.php index 4fb11f8b0b37..765ee029590a 100644 --- a/tests/Http/Middleware/VitePreloadingTest.php +++ b/tests/Http/Middleware/VitePreloadingTest.php @@ -106,4 +106,47 @@ public function testItDoesNotOverwriteOtherLinkHeaders() $response->headers->all('Link'), ); } + + public function testItCanLimitNumberOfAssetsPreloaded() + { + $app = new Container; + $app->instance(Vite::class, new class extends Vite + { + protected $preloadedAssets = [ + 'https://laravel.com/first.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/second.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/third.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + 'https://laravel.com/fourth.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + ]; + }); + Facade::setFacadeApplication($app); + + $response = (new AddLinkHeadersForPreloadedAssets)->handle(new Request, fn () => new Response('ok'), 2); + + $this->assertSame( + [ + '; rel="modulepreload"; foo="bar", ; rel="modulepreload"; foo="bar"', + ], + $response->headers->all('Link'), + ); + } + + public function test_it_can_configure_the_middleware() + { + $definition = AddLinkHeadersForPreloadedAssets::using(limit: 5); + + $this->assertSame('Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets:5', $definition); + } } diff --git a/tests/Integration/Auth/Fixtures/Models/Nested/SubTestUser.php b/tests/Integration/Auth/Fixtures/Models/Nested/SubTestUser.php new file mode 100644 index 000000000000..8a02e5bbdee3 --- /dev/null +++ b/tests/Integration/Auth/Fixtures/Models/Nested/SubTestUser.php @@ -0,0 +1,27 @@ +assertInstanceOf( + TopTestUserPolicy::class, + Gate::getPolicyFor(Fixtures\Models\Nested\TopTestUser::class) + ); + + $this->assertInstanceOf( + SubTestUserPolicy::class, + Gate::getPolicyFor(Fixtures\Models\Nested\SubTestUser::class) + ); + } + public function testPolicyCanBeGuessedUsingCallback() { Gate::guessPolicyNamesUsing(function () { diff --git a/tests/Integration/Cache/MemoizedStoreTest.php b/tests/Integration/Cache/MemoizedStoreTest.php new file mode 100644 index 000000000000..028688b262e2 --- /dev/null +++ b/tests/Integration/Cache/MemoizedStoreTest.php @@ -0,0 +1,409 @@ +setUpRedis(); + + Config::set('cache.default', 'redis'); + Redis::flushAll(); + } + + protected function tearDown(): void + { + parent::tearDown(); + + $this->tearDownRedis(); + } + + public function test_it_can_memoize_when_retrieving_single_value() + { + Cache::put('name', 'Tim', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Tim', $live); + $this->assertSame('Tim', $memoized); + + Cache::put('name', 'Taylor', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Taylor', $live); + $this->assertSame('Tim', $memoized); + } + + public function test_null_values_are_memoized_when_retrieving_single_value() + { + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertNull($live); + $this->assertNull($memoized); + + Cache::put('name', 'Taylor', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Taylor', $live); + $this->assertNull($memoized); + } + + public function test_it_can_memoize_when_retrieving_mulitple_values() + { + Cache::put('name.0', 'Tim', 60); + Cache::put('name.1', 'Taylor', 60); + + $live = Cache::getMultiple(['name.0', 'name.1']); + $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $memoized); + + Cache::put('name.0', 'MacDonald', 60); + Cache::put('name.1', 'Otwell', 60); + + $live = Cache::getMultiple(['name.0', 'name.1']); + $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Otwell'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $memoized); + } + + public function test_it_uses_correct_keys_for_getMultiple() + { + $data = [ + 'a' => 'string-value', + '1.1' => 'float-value', + '1' => 'integer-value-as-string', + 2 => 'integer-value', + ]; + Cache::putMany($data); + + $memoValue = Cache::memo()->many(['a', '1.1', '1', 2]); + $cacheValue = Cache::many(['a', '1.1', '1', 2]); + + $this->assertSame([ + 'a' => 'string-value', + '1.1' => 'float-value', + '1' => 'integer-value-as-string', + 2 => 'integer-value', + ], $cacheValue); + $this->assertSame($cacheValue, $memoValue); + + // ensure correct on the second memoized retrieval + $memoValue = Cache::memo()->many(['a', '1.1', '1', 2]); + + $this->assertSame($cacheValue, $memoValue); + } + + public function test_null_values_are_memoized_when_retrieving_mulitple_values() + { + $live = Cache::getMultiple(['name.0', 'name.1']); + $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); + $this->assertSame($live, ['name.0' => null, 'name.1' => null]); + $this->assertSame($memoized, ['name.0' => null, 'name.1' => null]); + + Cache::put('name.0', 'MacDonald', 60); + Cache::put('name.1', 'Otwell', 60); + + $live = Cache::getMultiple(['name.0', 'name.1']); + $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); + $this->assertSame($live, ['name.0' => 'MacDonald', 'name.1' => 'Otwell']); + $this->assertSame($memoized, ['name.0' => null, 'name.1' => null]); + } + + public function test_it_can_retrieve_already_memoized_and_not_yet_memoized_values_when_retrieving_mulitple_values() + { + Cache::put('name.0', 'Tim', 60); + Cache::put('name.1', 'Taylor', 60); + + $live = Cache::get('name.0'); + $memoized = Cache::memo()->get('name.0'); + $this->assertSame('Tim', $live); + $this->assertSame('Tim', $memoized); + + Cache::put('name.0', 'MacDonald', 60); + Cache::put('name.1', 'Otwell', 60); + + $live = Cache::getMultiple(['name.0', 'name.1']); + $memoized = Cache::memo()->getMultiple(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Otwell'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Otwell'], $memoized); + } + + public function test_put_forgets_memoized_value() + { + Cache::put(['name.0' => 'Tim', 'name.1' => 'Taylor'], 60); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $memoized); + + Cache::memo()->put('name.0', 'MacDonald'); + Cache::memo()->put('name.1', 'Otwell'); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Otwell'], $live); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Otwell'], $memoized); + } + + public function test_put_many_forgets_memoized_value() + { + Cache::memo()->put(['name.0' => 'Tim', 'name.1' => 'Taylor'], 60); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $memoized); + + Cache::memo()->put(['name.0' => 'MacDonald'], 60); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Taylor'], $live); + $this->assertSame(['name.0' => 'MacDonald', 'name.1' => 'Taylor'], $memoized); + } + + public function test_increment_forgets_memoized_value() + { + Cache::put('count', 1, 60); + + $live = Cache::get('count'); + $memoized = Cache::memo()->get('count'); + $this->assertSame('1', $live); + $this->assertSame('1', $memoized); + + Cache::memo()->increment('count'); + + $live = Cache::get('count'); + $memoized = Cache::memo()->get('count'); + $this->assertSame('2', $live); + $this->assertSame('2', $memoized); + } + + public function test_decrement_forgets_memoized_value() + { + Cache::put('count', 1, 60); + + $live = Cache::get('count'); + $memoized = Cache::memo()->get('count'); + $this->assertSame('1', $live); + $this->assertSame('1', $memoized); + + Cache::memo()->decrement('count'); + + $live = Cache::get('count'); + $memoized = Cache::memo()->get('count'); + $this->assertSame('0', $live); + $this->assertSame('0', $memoized); + } + + public function test_forever_forgets_memoized_value() + { + Cache::put('name', 'Tim', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Tim', $live); + $this->assertSame('Tim', $memoized); + + Cache::memo()->forever('name', 'Taylor'); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Taylor', $live); + $this->assertSame('Taylor', $memoized); + } + + public function test_forget_forgets_memoized_value() + { + Cache::put('name', 'Tim', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Tim', $live); + $this->assertSame('Tim', $memoized); + + Cache::memo()->forget('name'); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertNull($live); + $this->assertNull($memoized); + } + + public function test_flush_forgets_memoized_value() + { + Cache::put(['name.0' => 'Tim', 'name.1' => 'Taylor'], 60); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $live); + $this->assertSame(['name.0' => 'Tim', 'name.1' => 'Taylor'], $memoized); + + Cache::memo()->flush(); + + $live = Cache::get(['name.0', 'name.1']); + $memoized = Cache::memo()->get(['name.0', 'name.1']); + $this->assertSame(['name.0' => null, 'name.1' => null], $live); + $this->assertSame(['name.0' => null, 'name.1' => null], $memoized); + } + + public function test_memoized_driver_uses_underlying_drivers_prefix() + { + $this->assertSame('laravel_cache_', Cache::memo()->getPrefix()); + + Cache::driver('redis')->setPrefix('foo'); + + $this->assertSame('foo', Cache::memo()->getPrefix()); + } + + public function test_memoized_keys_are_prefixed() + { + $redis = Cache::store('redis'); + + $redis->setPrefix('aaaa'); + $redis->put('name', 'Tim', 60); + $redis->setPrefix('zzzz'); + $redis->put('name', 'Taylor', 60); + + $redis->setPrefix('aaaa'); + $value = Cache::memo('redis')->get('name'); + $this->assertSame('Tim', $value); + + $redis->setPrefix('zzzz'); + $value = Cache::memo('redis')->get('name'); + $this->assertSame('Taylor', $value); + } + + public function test_it_dispatches_decorated_driver_events_only() + { + $redis = Cache::driver('redis'); + $events = []; + Event::listen('*', function ($type, $event) use (&$events) { + if ($event[0] instanceof CacheEvent) { + $events[] = $event[0]; + } + }); + + Cache::memo('redis')->get('name'); + $this->assertCount(2, $events); + $this->assertInstanceOf(RetrievingKey::class, $events[0]); + $this->assertSame('redis', $events[0]->storeName); + $this->assertSame('name', $events[0]->key); + $this->assertInstanceOf(CacheMissed::class, $events[1]); + $this->assertSame('redis', $events[1]->storeName); + $this->assertSame('name', $events[1]->key); + Cache::memo('redis')->get('name'); + $this->assertCount(2, $events); + + Cache::memo('redis')->many(['name']); + $this->assertCount(2, $events); + + Cache::memo('redis')->many(['name.0', 'name.1']); + $this->assertCount(5, $events); + $this->assertInstanceOf(RetrievingManyKeys::class, $events[2]); + $this->assertSame('redis', $events[2]->storeName); + $this->assertSame(['name.0', 'name.1'], $events[2]->keys); + $this->assertInstanceOf(CacheMissed::class, $events[3]); + $this->assertSame('redis', $events[3]->storeName); + $this->assertSame('name.0', $events[3]->key); + $this->assertInstanceOf(CacheMissed::class, $events[4]); + $this->assertSame('redis', $events[4]->storeName); + $this->assertSame('name.1', $events[4]->key); + + Cache::memo('redis')->many(['name.0', 'name.1']); + $this->assertCount(5, $events); + + Cache::memo('redis')->put('name', 'Tim', 1); + $this->assertCount(7, $events); + $this->assertInstanceOf(WritingKey::class, $events[5]); + $this->assertSame('redis', $events[5]->storeName); + $this->assertSame('name', $events[5]->key); + $this->assertInstanceOf(KeyWritten::class, $events[6]); + $this->assertSame('redis', $events[6]->storeName); + $this->assertSame('name', $events[6]->key); + + Cache::memo('redis')->putMany(['name.0' => 'Tim', 'name.1' => 'Taylor']); + $this->assertCount(11, $events); + $this->assertInstanceOf(WritingKey::class, $events[7]); + $this->assertSame('redis', $events[7]->storeName); + $this->assertSame('name.0', $events[7]->key); + $this->assertInstanceOf(KeyWritten::class, $events[8]); + $this->assertSame('redis', $events[8]->storeName); + $this->assertSame('name.0', $events[8]->key); + $this->assertInstanceOf(WritingKey::class, $events[9]); + $this->assertSame('redis', $events[9]->storeName); + $this->assertSame('name.1', $events[9]->key); + $this->assertInstanceOf(KeyWritten::class, $events[10]); + $this->assertSame('redis', $events[10]->storeName); + $this->assertSame('name.1', $events[10]->key); + + Cache::memo('redis')->increment('count'); + $this->assertCount(11, $events); + + Cache::memo('redis')->decrement('count'); + $this->assertCount(11, $events); + + Cache::memo('redis')->forever('name', 'Taylor'); + $this->assertCount(13, $events); + $this->assertInstanceOf(WritingKey::class, $events[11]); + $this->assertSame('redis', $events[11]->storeName); + $this->assertSame('name', $events[11]->key); + $this->assertInstanceOf(KeyWritten::class, $events[12]); + $this->assertSame('redis', $events[12]->storeName); + $this->assertSame('name', $events[12]->key); + + Cache::memo('redis')->forget('name'); + $this->assertCount(15, $events); + $this->assertInstanceOf(ForgettingKey::class, $events[13]); + $this->assertSame('redis', $events[13]->storeName); + $this->assertSame('name', $events[13]->key); + $this->assertInstanceOf(KeyForgotten::class, $events[14]); + $this->assertSame('redis', $events[14]->storeName); + $this->assertSame('name', $events[14]->key); + + Cache::memo('redis')->flush(); + $this->assertCount(15, $events); + } + + public function test_it_resets_cache_store_with_scoped_instances() + { + Cache::put('name', 'Tim', 60); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Tim', $live); + $this->assertSame('Tim', $memoized); + + Cache::put('name', 'Taylor', 60); + $this->app->forgetScopedInstances(); + + $live = Cache::get('name'); + $memoized = Cache::memo()->get('name'); + $this->assertSame('Taylor', $live); + $this->assertSame('Taylor', $memoized); + } +} diff --git a/tests/Integration/Cache/Psr6RedisTest.php b/tests/Integration/Cache/Psr6RedisTest.php index 062d5ec4dbfb..ff8816f9d9e1 100644 --- a/tests/Integration/Cache/Psr6RedisTest.php +++ b/tests/Integration/Cache/Psr6RedisTest.php @@ -14,16 +14,15 @@ class Psr6RedisTest extends TestCase protected function setUp(): void { - parent::setUp(); + $this->afterApplicationCreated(function () { + $this->setUpRedis(); + }); - $this->setUpRedis(); - } + $this->beforeApplicationDestroyed(function () { + $this->tearDownRedis(); + }); - protected function tearDown(): void - { - parent::tearDown(); - - $this->tearDownRedis(); + parent::setUp(); } #[DataProvider('redisClientDataProvider')] diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index af731995b7de..f4a217c9e9f2 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -16,19 +16,19 @@ class RedisStoreTest extends TestCase { use InteractsWithRedis; + /** {@inheritdoc} */ + #[\Override] protected function setUp(): void { - parent::setUp(); + $this->afterApplicationCreated(function () { + $this->setUpRedis(); + }); - $this->setUpRedis(); - } + $this->beforeApplicationDestroyed(function () { + $this->tearDownRedis(); + }); - protected function tearDown(): void - { - parent::tearDown(); - - $this->tearDownRedis(); - m::close(); + parent::setUp(); } public function testCacheTtl(): void diff --git a/tests/Integration/Cache/RepositoryTest.php b/tests/Integration/Cache/RepositoryTest.php index b0d48e2024fe..8b13595cd5c5 100644 --- a/tests/Integration/Cache/RepositoryTest.php +++ b/tests/Integration/Cache/RepositoryTest.php @@ -103,7 +103,7 @@ public function testStaleWhileRevalidate(): void $this->assertSame(3, $cache->get('foo')); $this->assertSame(946684832, $cache->get('illuminate:cache:flexible:created:foo')); - // Now we will execute the deferred callback but we will first aquire + // Now we will execute the deferred callback but we will first acquire // our own lock. This means that the value should not be refreshed by // deferred callback. /** @var Lock */ @@ -118,7 +118,7 @@ public function testStaleWhileRevalidate(): void $this->assertTrue($lock->release()); // Now we have cleared the lock we will, one last time, confirm that - // the deferred callack does refresh the value when the lock is not active. + // the deferred callback does refresh the value when the lock is not active. defer()->invoke(); $this->assertCount(0, defer()); $this->assertSame(4, $cache->get('foo')); diff --git a/tests/Integration/Concurrency/ConcurrencyTest.php b/tests/Integration/Concurrency/ConcurrencyTest.php index a745a8d27bc7..6de1ac4bbb6f 100644 --- a/tests/Integration/Concurrency/ConcurrencyTest.php +++ b/tests/Integration/Concurrency/ConcurrencyTest.php @@ -8,6 +8,7 @@ use Illuminate\Process\Factory as ProcessFactory; use Illuminate\Support\Facades\Concurrency; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresOperatingSystem; #[RequiresOperatingSystem('Linux|DAR')] @@ -52,6 +53,39 @@ public function testRunHandlerProcessErrorCode() ]); } + public function testOutputIsMappedToArrayInput() + { + $input = [ + 'first' => fn () => 1 + 1, + 'second' => fn () => 2 + 2, + ]; + + $processOutput = Concurrency::driver('process')->run($input); + + $this->assertIsArray($processOutput); + $this->assertArrayHasKey('first', $processOutput); + $this->assertArrayHasKey('second', $processOutput); + + $syncOutput = Concurrency::driver('sync')->run($input); + + $this->assertIsArray($syncOutput); + $this->assertArrayHasKey('first', $syncOutput); + $this->assertArrayHasKey('second', $syncOutput); + + /** As of now, the spatie/fork package is not included by default. + * $forkOutput = Concurrency::driver('fork')->run([ + * 'first' => fn() => 1 + 1, + * 'second' => fn() => 2 + 2, + * ]);. + * + * $this->assertIsArray($forkOutput); + * $this->assertArrayHasKey('first', $forkOutput); + * $this->assertArrayHasKey('second', $forkOutput); + * $this->assertEquals(2, $forkOutput['first']); + * $this->assertEquals(4, $forkOutput['second']); + */ + } + public function testRunHandlerProcessErrorWithDefaultExceptionWithoutParam() { $this->expectException(Exception::class); @@ -86,6 +120,42 @@ public function testRunHandlerProcessErrorWithCustomExceptionWithParam() ), ]); } + + public static function getConcurrencyDrivers(): array + { + return [ + ['sync'], + ['process'], + // spatie/fork package is not included by default + // ['fork'], + ]; + } + + #[DataProvider('getConcurrencyDrivers')] + public function testRunPreservesCallbackOrder(string $driver) + { + [$first, $second, $third] = Concurrency::driver($driver)->run([ + function () { + usleep(1000000); + + return 'first'; + }, + function () { + usleep(500000); + + return 'second'; + }, + function () { + usleep(200000); + + return 'third'; + }, + ]); + + $this->assertEquals('first', $first); + $this->assertEquals('second', $second); + $this->assertEquals('third', $third); + } } class ExceptionWithoutParam extends Exception diff --git a/tests/Integration/Concurrency/Console/InvokeSerializedClosureCommandTest.php b/tests/Integration/Concurrency/Console/InvokeSerializedClosureCommandTest.php new file mode 100644 index 000000000000..b20b124112f3 --- /dev/null +++ b/tests/Integration/Concurrency/Console/InvokeSerializedClosureCommandTest.php @@ -0,0 +1,141 @@ +app[Kernel::class]->registerCommand(new InvokeSerializedClosureCommand); + } + + public function testItCanInvokeSerializedClosureFromArgument() + { + // Create a simple closure and serialize it + $closure = fn () => 'Hello, World!'; + $serialized = serialize(new SerializableClosure($closure)); + + // Create a new output buffer + $output = new BufferedOutput; + + // Call the command with the serialized closure + Artisan::call('invoke-serialized-closure', [ + 'code' => $serialized, + ], $output); + + // Get the output and decode it + $result = json_decode($output->fetch(), true); + + // Verify the result + $this->assertTrue($result['successful']); + $this->assertEquals('Hello, World!', unserialize($result['result'])); + } + + public function testItCanInvokeSerializedClosureFromEnvironment() + { + // Create a simple closure and serialize it + $closure = fn () => 'From Environment'; + $serialized = serialize(new SerializableClosure($closure)); + + // Set the environment variable + $_SERVER['LARAVEL_INVOKABLE_CLOSURE'] = $serialized; + + // Create a new output buffer + $output = new BufferedOutput; + + // Call the command without arguments + Artisan::call('invoke-serialized-closure', [], $output); + + // Get the output and decode it + $result = json_decode($output->fetch(), true); + + // Verify the result + $this->assertTrue($result['successful']); + $this->assertEquals('From Environment', unserialize($result['result'])); + + // Clean up + unset($_SERVER['LARAVEL_INVOKABLE_CLOSURE']); + } + + public function testItReturnsNullWhenNoClosureIsProvided() + { + // Create a new output buffer + $output = new BufferedOutput; + + // Call the command without arguments + Artisan::call('invoke-serialized-closure', [], $output); + + // Get the output and decode it + $result = json_decode($output->fetch(), true); + + // Verify the result + $this->assertTrue($result['successful']); + $this->assertNull(unserialize($result['result'])); + } + + public function testItHandlesExceptionsGracefully() + { + // Create a closure that throws an exception + $closure = fn () => throw new RuntimeException('Test exception'); + $serialized = serialize(new SerializableClosure($closure)); + + // Create a new output buffer + $output = new BufferedOutput; + + // Call the command with the serialized closure + Artisan::call('invoke-serialized-closure', [ + 'code' => $serialized, + ], $output); + + // Get the output and decode it + $result = json_decode($output->fetch(), true); + + // Verify the exception was caught + $this->assertFalse($result['successful']); + $this->assertEquals('RuntimeException', $result['exception']); + $this->assertEquals('Test exception', $result['message']); + } + + public function testItHandlesCustomExceptionWithParameters() + { + // Create a closure that throws an exception with parameters + $closure = fn () => throw new CustomParameterException('Test param'); + $serialized = serialize(new SerializableClosure($closure)); + + // Create a new output buffer + $output = new BufferedOutput; + + // Call the command with the serialized closure + Artisan::call('invoke-serialized-closure', [ + 'code' => $serialized, + ], $output); + + // Get the output and decode it + $result = json_decode($output->fetch(), true); + + // Verify the exception was caught and parameters were captured + $this->assertFalse($result['successful']); + $this->assertArrayHasKey('parameters', $result); + $this->assertEquals('Test param', $result['parameters']['customParam'] ?? null); + } +} diff --git a/tests/Integration/Console/CallbackSchedulingTest.php b/tests/Integration/Console/CallbackSchedulingTest.php index 13349238ca70..7fef2668b29d 100644 --- a/tests/Integration/Console/CallbackSchedulingTest.php +++ b/tests/Integration/Console/CallbackSchedulingTest.php @@ -45,13 +45,6 @@ public function store($name = null) $container->instance(SchedulingMutex::class, new CacheSchedulingMutex($cache)); } - protected function tearDown(): void - { - Container::setInstance(null); - - parent::tearDown(); - } - public function testExecutionOrder() { $event = $this->app->make(Schedule::class) diff --git a/tests/Integration/Console/Events/EventListCommandTest.php b/tests/Integration/Console/Events/EventListCommandTest.php index 200f8df3c80b..9409d77e3075 100644 --- a/tests/Integration/Console/Events/EventListCommandTest.php +++ b/tests/Integration/Console/Events/EventListCommandTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Events\Dispatcher; use Illuminate\Foundation\Console\EventListCommand; +use Illuminate\Support\Facades\Artisan; use Orchestra\Testbench\TestCase; class EventListCommandTest extends TestCase @@ -58,6 +59,54 @@ public function testDisplayFilteredEvent() ->expectsOutputToContain('ExampleEvent'); } + public function testDisplayEmptyListAsJson() + { + $this->withoutMockingConsoleOutput()->artisan(EventListCommand::class, ['--json' => true]); + $output = Artisan::output(); + + $this->assertJson($output); + $this->assertJsonStringEqualsJsonString('[]', $output); + } + + public function testDisplayEventsAsJson() + { + $this->dispatcher->subscribe(ExampleSubscriber::class); + $this->dispatcher->listen(ExampleEvent::class, ExampleListener::class); + $this->dispatcher->listen(ExampleEvent::class, ExampleQueueListener::class); + $this->dispatcher->listen(ExampleBroadcastEvent::class, ExampleBroadcastListener::class); + $this->dispatcher->listen(ExampleEvent::class, fn () => ''); + $closureLineNumber = __LINE__ - 1; + $unixFilePath = str_replace('\\', '/', __FILE__); + + $this->withoutMockingConsoleOutput()->artisan(EventListCommand::class, ['--json' => true]); + $output = Artisan::output(); + + $this->assertJson($output); + $this->assertStringContainsString('ExampleSubscriberEventName', $output); + $this->assertStringContainsString(json_encode('Illuminate\Tests\Integration\Console\Events\ExampleSubscriber@a'), $output); + $this->assertStringContainsString(json_encode('Illuminate\Tests\Integration\Console\Events\ExampleBroadcastEvent (ShouldBroadcast)'), $output); + $this->assertStringContainsString(json_encode('Illuminate\Tests\Integration\Console\Events\ExampleBroadcastListener'), $output); + $this->assertStringContainsString(json_encode('Illuminate\Tests\Integration\Console\Events\ExampleEvent'), $output); + $this->assertStringContainsString(json_encode('Closure at: '.$unixFilePath.':'.$closureLineNumber), $output); + } + + public function testDisplayFilteredEventAsJson() + { + $this->dispatcher->subscribe(ExampleSubscriber::class); + $this->dispatcher->listen(ExampleEvent::class, ExampleListener::class); + + $this->withoutMockingConsoleOutput()->artisan(EventListCommand::class, [ + '--event' => 'ExampleEvent', + '--json' => true, + ]); + $output = Artisan::output(); + + $this->assertJson($output); + $this->assertStringContainsString('ExampleEvent', $output); + $this->assertStringContainsString('ExampleListener', $output); + $this->assertStringNotContainsString('ExampleSubscriberEventName', $output); + } + protected function tearDown(): void { parent::tearDown(); diff --git a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php index 806b0a7e929e..b65ab28d218e 100644 --- a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php +++ b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php @@ -156,11 +156,9 @@ public function testClosureCommandsMayBeScheduled() protected function tearDown(): void { - parent::tearDown(); - putenv('SHELL_VERBOSITY'); - ScheduleListCommand::resolveTerminalWidthUsing(null); + parent::tearDown(); } } diff --git a/tests/Integration/Database/DatabaseCustomCastsTest.php b/tests/Integration/Database/DatabaseCustomCastsTest.php index 0337edf26dcc..ec14a2bf3c49 100644 --- a/tests/Integration/Database/DatabaseCustomCastsTest.php +++ b/tests/Integration/Database/DatabaseCustomCastsTest.php @@ -7,8 +7,10 @@ use Illuminate\Database\Eloquent\Casts\AsStringable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Fluent; use Illuminate\Support\Stringable; class DatabaseCustomCastsTest extends DatabaseTestCase @@ -151,6 +153,68 @@ public function test_custom_casting_nullable_values() $model->array_object_json->toArray() ); } + + public function test_as_collection_with_map_into() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::of(Fluent::class), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(Fluent::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } + + public function test_as_custom_collection_with_map_into() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::using(CustomCollection::class, Fluent::class), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(CustomCollection::class, $model->collection); + $this->assertInstanceOf(Fluent::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } + + public function test_as_collection_with_map_callback(): void + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::of([FluentWithCallback::class, 'make']), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(FluentWithCallback::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } + + public function test_as_custom_collection_with_map_callback(): void + { + $model = new TestEloquentModelWithCustomCasts(); + $model->mergeCasts([ + 'collection' => AsCollection::using(CustomCollection::class, [FluentWithCallback::class, 'make']), + ]); + + $model->setRawAttributes([ + 'collection' => json_encode([['foo' => 'bar']]), + ]); + + $this->assertInstanceOf(CustomCollection::class, $model->collection); + $this->assertInstanceOf(FluentWithCallback::class, $model->collection->first()); + $this->assertSame('bar', $model->collection->first()->foo); + } } class TestEloquentModelWithCustomCasts extends Model @@ -197,3 +261,15 @@ class TestEloquentModelWithCustomCastsNullable extends Model 'stringable' => AsStringable::class, ]; } + +class FluentWithCallback extends Fluent +{ + public static function make($attributes = []) + { + return new static($attributes); + } +} + +class CustomCollection extends Collection +{ +} diff --git a/tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php index 060031684a0b..48c79acad305 100644 --- a/tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php +++ b/tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php @@ -184,7 +184,7 @@ public function testSettingRawAttributesClearsTheCastCache() $this->assertSame('117 Spencer St.', $model->address->lineOne); } - public function testCastsThatOnlyHaveGetterDoNotPeristAnythingToModelOnSave() + public function testCastsThatOnlyHaveGetterDoNotPersistAnythingToModelOnSave() { $model = new TestEloquentModelWithAttributeCast; diff --git a/tests/Integration/Database/EloquentBelongsToManyTest.php b/tests/Integration/Database/EloquentBelongsToManyTest.php index 8cb8acba0187..c104e0e05026 100644 --- a/tests/Integration/Database/EloquentBelongsToManyTest.php +++ b/tests/Integration/Database/EloquentBelongsToManyTest.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Database\RecordsNotFoundException; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; @@ -15,13 +16,6 @@ class EloquentBelongsToManyTest extends DatabaseTestCase { - protected function tearDown(): void - { - parent::tearDown(); - - Carbon::setTestNow(null); - } - protected function afterRefreshingDatabase() { Schema::create('users', function (Blueprint $table) { @@ -342,6 +336,49 @@ public function testDetachMethod() $this->assertCount(0, $post->tags); } + public function testDetachMethodWithCustomPivot() + { + $post = Post::create(['title' => Str::random()]); + + $tag = Tag::create(['name' => Str::random()]); + $tag2 = Tag::create(['name' => Str::random()]); + $tag3 = Tag::create(['name' => Str::random()]); + $tag4 = Tag::create(['name' => Str::random()]); + $tag5 = Tag::create(['name' => Str::random()]); + Tag::create(['name' => Str::random()]); + Tag::create(['name' => Str::random()]); + + $post->tagsWithCustomPivot()->attach(Tag::all()); + + $this->assertEquals(Tag::pluck('name'), $post->tags->pluck('name')); + + $post->tagsWithCustomPivot()->detach($tag->id); + $post->load('tagsWithCustomPivot'); + $this->assertEquals( + Tag::whereNotIn('id', [$tag->id])->pluck('name'), + $post->tagsWithCustomPivot->pluck('name') + ); + + $post->tagsWithCustomPivot()->detach([$tag2->id, $tag3->id]); + $post->load('tagsWithCustomPivot'); + $this->assertEquals( + Tag::whereNotIn('id', [$tag->id, $tag2->id, $tag3->id])->pluck('name'), + $post->tagsWithCustomPivot->pluck('name') + ); + + $post->tagsWithCustomPivot()->detach(new Collection([$tag4, $tag5])); + $post->load('tagsWithCustomPivot'); + $this->assertEquals( + Tag::whereNotIn('id', [$tag->id, $tag2->id, $tag3->id, $tag4->id, $tag5->id])->pluck('name'), + $post->tagsWithCustomPivot->pluck('name') + ); + + $this->assertCount(2, $post->tagsWithCustomPivot); + $post->tagsWithCustomPivot()->detach(); + $post->load('tagsWithCustomPivot'); + $this->assertCount(0, $post->tagsWithCustomPivot); + } + public function testFirstMethod() { $post = Post::create(['title' => Str::random()]); @@ -420,6 +457,29 @@ public function testFindMethodStringyKey() $this->assertCount(2, $post->tags()->findMany(new Collection([$tag->id, $tag2->id]))); } + public function testFindSoleMethod() + { + $post = Post::create(['title' => Str::random()]); + + $tag = Tag::create(['name' => Str::random()]); + + $post->tags()->attach($tag); + + $this->assertEquals($tag->id, $post->tags()->findSole($tag->id)->id); + + $this->assertEquals($tag->id, $post->tags()->findSole($tag)->id); + + // Test with no records + $post->tags()->detach($tag); + + try { + $post->tags()->findSole($tag); + $this->fail('Expected RecordsNotFoundException was not thrown.'); + } catch (RecordsNotFoundException $e) { + $this->assertTrue(true); + } + } + public function testFindOrFailMethod() { $this->expectException(ModelNotFoundException::class); diff --git a/tests/Integration/Database/EloquentMassPrunableTest.php b/tests/Integration/Database/EloquentMassPrunableTest.php index e3e1c0c36f34..62401e880db3 100644 --- a/tests/Integration/Database/EloquentMassPrunableTest.php +++ b/tests/Integration/Database/EloquentMassPrunableTest.php @@ -2,7 +2,6 @@ namespace Illuminate\Tests\Integration\Database; -use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Model; @@ -19,13 +18,11 @@ protected function setUp(): void { parent::setUp(); - Container::setInstance($container = new Container); - - $container->singleton(Dispatcher::class, function () { + $this->app->singleton(Dispatcher::class, function () { return m::mock(Dispatcher::class); }); - $container->alias(Dispatcher::class, 'events'); + $this->app->alias(Dispatcher::class, 'events'); } protected function afterRefreshingDatabase() @@ -93,15 +90,6 @@ public function testPrunesSoftDeletedRecords() $this->assertEquals(0, MassPrunableSoftDeleteTestModel::count()); $this->assertEquals(2000, MassPrunableSoftDeleteTestModel::withTrashed()->count()); } - - protected function tearDown(): void - { - parent::tearDown(); - - Container::setInstance(null); - - m::close(); - } } class MassPrunableTestModel extends Model diff --git a/tests/Integration/Database/EloquentModelCustomEventsTest.php b/tests/Integration/Database/EloquentModelCustomEventsTest.php index 6a85e3379ca4..e52227c3d47f 100644 --- a/tests/Integration/Database/EloquentModelCustomEventsTest.php +++ b/tests/Integration/Database/EloquentModelCustomEventsTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Database\EloquentModelCustomEventsTest; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Event; @@ -24,6 +25,11 @@ protected function afterRefreshingDatabase() Schema::create('test_model1', function (Blueprint $table) { $table->increments('id'); }); + + Schema::create('eloquent_model_stub_with_custom_event_from_traits', function (Blueprint $table) { + $table->boolean('custom_attribute'); + $table->boolean('observer_attribute'); + }); } public function testFlushListenersClearsCustomEvents() @@ -45,6 +51,19 @@ public function testCustomEventListenersAreFired() $this->assertTrue($_SERVER['fired_event']); } + + public function testAddObservableEventFromTrait() + { + $model = new EloquentModelStubWithCustomEventFromTrait(); + + $this->assertNull($model->custom_attribute); + $this->assertNull($model->observer_attribute); + + $model->completeCustomAction(); + + $this->assertTrue($model->custom_attribute); + $this->assertTrue($model->observer_attribute); + } } class TestModel1 extends Model @@ -59,3 +78,36 @@ class CustomEvent { // } + +trait CustomEventTrait +{ + public function completeCustomAction() + { + $this->custom_attribute = true; + + $this->fireModelEvent('customEvent'); + } + + public function initializeCustomEventTrait() + { + $this->addObservableEvents([ + 'customEvent', + ]); + } +} + +class CustomObserver +{ + public function customEvent(EloquentModelStubWithCustomEventFromTrait $model) + { + $model->observer_attribute = true; + } +} + +#[ObservedBy(CustomObserver::class)] +class EloquentModelStubWithCustomEventFromTrait extends Model +{ + use CustomEventTrait; + + public $timestamps = false; +} diff --git a/tests/Integration/Database/EloquentModelEncryptedCastingTest.php b/tests/Integration/Database/EloquentModelEncryptedCastingTest.php index f4a00d3c98ca..0c87c7440022 100644 --- a/tests/Integration/Database/EloquentModelEncryptedCastingTest.php +++ b/tests/Integration/Database/EloquentModelEncryptedCastingTest.php @@ -11,6 +11,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Fluent; use stdClass; class EloquentModelEncryptedCastingTest extends DatabaseTestCase @@ -231,6 +232,58 @@ public function testAsEncryptedCollection() $this->assertNull($subject->fresh()->secret_collection); } + public function testAsEncryptedCollectionMap() + { + $this->encrypter->expects('encryptString') + ->twice() + ->with('[{"key1":"value1"}]') + ->andReturn('encrypted-secret-collection-string-1'); + $this->encrypter->expects('encryptString') + ->times(12) + ->with('[{"key1":"value1"},{"key2":"value2"}]') + ->andReturn('encrypted-secret-collection-string-2'); + $this->encrypter->expects('decryptString') + ->once() + ->with('encrypted-secret-collection-string-2') + ->andReturn('[{"key1":"value1"},{"key2":"value2"}]'); + + $subject = new EncryptedCast; + + $subject->mergeCasts(['secret_collection' => AsEncryptedCollection::of(Fluent::class)]); + + $subject->secret_collection = new Collection([new Fluent(['key1' => 'value1'])]); + $subject->secret_collection->push(new Fluent(['key2' => 'value2'])); + + $subject->save(); + + $this->assertInstanceOf(Collection::class, $subject->secret_collection); + $this->assertInstanceOf(Fluent::class, $subject->secret_collection->first()); + $this->assertSame('value1', $subject->secret_collection->get(0)->key1); + $this->assertSame('value2', $subject->secret_collection->get(1)->key2); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_collection' => 'encrypted-secret-collection-string-2', + ]); + + $subject = $subject->fresh(); + + $this->assertInstanceOf(Collection::class, $subject->secret_collection); + $this->assertInstanceOf(Fluent::class, $subject->secret_collection->first()); + $this->assertSame('value1', $subject->secret_collection->get(0)->key1); + $this->assertSame('value2', $subject->secret_collection->get(1)->key2); + + $subject->secret_collection = null; + $subject->save(); + + $this->assertNull($subject->secret_collection); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_collection' => null, + ]); + + $this->assertNull($subject->fresh()->secret_collection); + } + public function testAsEncryptedArrayObject() { $this->encrypter->expects('encryptString') diff --git a/tests/Integration/Database/EloquentModelRelationAutoloadTest.php b/tests/Integration/Database/EloquentModelRelationAutoloadTest.php new file mode 100644 index 000000000000..03f1d7ca611f --- /dev/null +++ b/tests/Integration/Database/EloquentModelRelationAutoloadTest.php @@ -0,0 +1,286 @@ +increments('id'); + }); + + Schema::create('videos', function (Blueprint $table) { + $table->increments('id'); + }); + + Schema::create('comments', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('parent_id')->nullable(); + $table->morphs('commentable'); + }); + + Schema::create('likes', function (Blueprint $table) { + $table->increments('id'); + $table->morphs('likeable'); + }); + } + + public function testRelationAutoloadForCollection() + { + $post1 = Post::create(); + $comment1 = $post1->comments()->create(['parent_id' => null]); + $comment2 = $post1->comments()->create(['parent_id' => $comment1->id]); + $comment2->likes()->create(); + $comment2->likes()->create(); + + $post2 = Post::create(); + $comment3 = $post2->comments()->create(['parent_id' => null]); + $comment3->likes()->create(); + + $posts = Post::get(); + + DB::enableQueryLog(); + + $likes = []; + + $posts->withRelationshipAutoloading(); + + foreach ($posts as $post) { + foreach ($post->comments as $comment) { + $likes = array_merge($likes, $comment->likes->all()); + } + } + + $this->assertCount(2, DB::getQueryLog()); + $this->assertCount(3, $likes); + $this->assertTrue($posts[0]->comments[0]->relationLoaded('likes')); + + DB::disableQueryLog(); + } + + public function testRelationAutoloadForSingleModel() + { + $post = Post::create(); + $comment1 = $post->comments()->create(['parent_id' => null]); + $comment2 = $post->comments()->create(['parent_id' => $comment1->id]); + $comment2->likes()->create(); + $comment2->likes()->create(); + + DB::enableQueryLog(); + + $likes = []; + + $post->withRelationshipAutoloading(); + + foreach ($post->comments as $comment) { + $likes = array_merge($likes, $comment->likes->all()); + } + + $this->assertCount(2, DB::getQueryLog()); + $this->assertCount(2, $likes); + $this->assertTrue($post->comments[0]->relationLoaded('likes')); + + DB::disableQueryLog(); + } + + public function testRelationAutoloadWithSerialization() + { + Model::automaticallyEagerLoadRelationships(); + + $post = Post::create(); + $comment1 = $post->comments()->create(['parent_id' => null]); + $comment2 = $post->comments()->create(['parent_id' => $comment1->id]); + $comment2->likes()->create(); + + DB::enableQueryLog(); + + $likes = []; + + $post = serialize($post); + $post = unserialize($post); + + foreach ($post->comments as $comment) { + $likes = array_merge($likes, $comment->likes->all()); + } + + $this->assertCount(2, DB::getQueryLog()); + + Model::automaticallyEagerLoadRelationships(false); + + DB::disableQueryLog(); + } + + public function testRelationAutoloadWithCircularRelations() + { + $post = Post::create(); + $comment1 = $post->comments()->create(['parent_id' => null]); + $comment2 = $post->comments()->create(['parent_id' => $comment1->id]); + $post->likes()->create(); + + DB::enableQueryLog(); + + $post->withRelationshipAutoloading(); + $comment = $post->comments->first(); + $comment->setRelation('post', $post); + + $this->assertCount(1, $post->likes); + + $this->assertCount(2, DB::getQueryLog()); + + DB::disableQueryLog(); + } + + public function testRelationAutoloadWithChaperoneRelations() + { + Model::automaticallyEagerLoadRelationships(); + + $post = Post::create(); + $comment1 = $post->comments()->create(['parent_id' => null]); + $comment2 = $post->comments()->create(['parent_id' => $comment1->id]); + $post->likes()->create(); + + DB::enableQueryLog(); + + $post->load('commentsWithChaperone'); + + $this->assertCount(1, $post->likes); + + $this->assertCount(2, DB::getQueryLog()); + + Model::automaticallyEagerLoadRelationships(false); + + DB::disableQueryLog(); + } + + public function testRelationAutoloadVariousNestedMorphRelations() + { + tap(Post::create(), function ($post) { + $post->likes()->create(); + $post->comments()->create(); + tap($post->comments()->create(), function ($comment) { + $comment->likes()->create(); + $comment->likes()->create(); + }); + }); + + tap(Post::create(), function ($post) { + $post->likes()->create(); + tap($post->comments()->create(), function ($comment) { + $comment->likes()->create(); + }); + }); + + tap(Video::create(), function ($video) { + tap($video->comments()->create(), function ($comment) { + $comment->likes()->create(); + }); + }); + + tap(Video::create(), function ($video) { + tap($video->comments()->create(), function ($comment) { + $comment->likes()->create(); + }); + }); + + $likes = Like::get(); + + DB::enableQueryLog(); + + $videos = []; + $videoLike = null; + + $likes->withRelationshipAutoloading(); + + foreach ($likes as $like) { + $likeable = $like->likeable; + + if (($likeable instanceof Comment) && ($likeable->commentable instanceof Video)) { + $videos[] = $likeable->commentable; + $videoLike = $like; + } + } + + $this->assertCount(4, DB::getQueryLog()); + $this->assertCount(2, $videos); + $this->assertTrue($videoLike->relationLoaded('likeable')); + $this->assertTrue($videoLike->likeable->relationLoaded('commentable')); + + DB::disableQueryLog(); + } +} + +class Comment extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function parent() + { + return $this->belongsTo(self::class); + } + + public function likes() + { + return $this->morphMany(Like::class, 'likeable'); + } + + public function commentable() + { + return $this->morphTo(); + } +} + +class Post extends Model +{ + public $timestamps = false; + + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function commentsWithChaperone() + { + return $this->morphMany(Comment::class, 'commentable')->chaperone(); + } + + public function likes() + { + return $this->morphMany(Like::class, 'likeable'); + } +} + +class Video extends Model +{ + public $timestamps = false; + + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function likes() + { + return $this->morphMany(Like::class, 'likeable'); + } +} + +class Like extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function likeable() + { + return $this->morphTo(); + } +} diff --git a/tests/Integration/Database/EloquentModelScopeTest.php b/tests/Integration/Database/EloquentModelScopeTest.php index 408026738c2e..7ea822b42810 100644 --- a/tests/Integration/Database/EloquentModelScopeTest.php +++ b/tests/Integration/Database/EloquentModelScopeTest.php @@ -2,6 +2,8 @@ namespace Illuminate\Tests\Integration\Database; +use Illuminate\Contracts\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Attributes\Scope as NamedScope; use Illuminate\Database\Eloquent\Model; class EloquentModelScopeTest extends DatabaseTestCase @@ -19,12 +21,25 @@ public function testModelDoesNotHaveScope() $this->assertFalse($model->hasNamedScope('doesNotExist')); } + + public function testModelHasAttributedScope() + { + $model = new TestScopeModel1; + + $this->assertTrue($model->hasNamedScope('existsAsWell')); + } } class TestScopeModel1 extends Model { - public function scopeExists() + public function scopeExists(Builder $builder) + { + return $builder; + } + + #[NamedScope] + protected function existsAsWell(Builder $builder) { - return true; + return $builder; } } diff --git a/tests/Integration/Database/EloquentNamedScopeAttributeTest.php b/tests/Integration/Database/EloquentNamedScopeAttributeTest.php new file mode 100644 index 000000000000..5d1441a43e3a --- /dev/null +++ b/tests/Integration/Database/EloquentNamedScopeAttributeTest.php @@ -0,0 +1,47 @@ +markTestSkippedUnless( + $this->usesSqliteInMemoryDatabaseConnection(), + 'Requires in-memory database connection', + ); + } + + #[DataProvider('namedScopeDataProvider')] + public function test_it_can_query_named_scoped_from_the_query_builder(string $methodName) + { + $query = Fixtures\NamedScopeUser::query()->{$methodName}(true); + + $this->assertSame($this->query, $query->toRawSql()); + } + + #[DataProvider('namedScopeDataProvider')] + public function test_it_can_query_named_scoped_from_static_query(string $methodName) + { + $query = Fixtures\NamedScopeUser::{$methodName}(true); + + $this->assertSame($this->query, $query->toRawSql()); + } + + public static function namedScopeDataProvider(): array + { + return [ + 'scope with return' => ['verified'], + 'scope without return' => ['verifiedWithoutReturn'], + ]; + } +} diff --git a/tests/Integration/Database/EloquentPivotEventsTest.php b/tests/Integration/Database/EloquentPivotEventsTest.php index 7a9962982595..e94fe5cce005 100644 --- a/tests/Integration/Database/EloquentPivotEventsTest.php +++ b/tests/Integration/Database/EloquentPivotEventsTest.php @@ -74,6 +74,34 @@ public function testPivotWillTriggerEventsToBeFired() $this->assertEquals(['deleting', 'deleted'], PivotEventsTestCollaborator::$eventsCalled); } + public function testPivotWithPivotValueWillTriggerEventsToBeFired() + { + $user = PivotEventsTestUser::forceCreate(['email' => 'taylor@laravel.com']); + $user2 = PivotEventsTestUser::forceCreate(['email' => 'ralph@ralphschindler.com']); + $project = PivotEventsTestProject::forceCreate(['name' => 'Test Project']); + + $project->managers()->attach($user); + $this->assertEquals(['saving', 'creating', 'created', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + $project->managers()->attach($user2); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->updateExistingPivot($user->id, ['permissions' => ['foo', 'bar']]); + $this->assertEquals(['saving', 'updating', 'updated', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + $project->managers()->detach($user2); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->sync([$user2->id]); + $this->assertEquals(['deleting', 'deleted', 'saving', 'creating', 'created', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->sync([$user->id => ['permissions' => ['foo']], $user2->id => ['permissions' => ['bar']]]); + $this->assertEquals(['saving', 'creating', 'created', 'saved', 'saving', 'updating', 'updated', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->detach($user); + $this->assertEquals(['deleting', 'deleted'], PivotEventsTestCollaborator::$eventsCalled); + } + public function testPivotWithPivotCriteriaTriggerEventsToBeFiredOnCreateUpdateNoneOnDetach() { $user = PivotEventsTestUser::forceCreate(['email' => 'taylor@laravel.com']); @@ -192,6 +220,13 @@ public function contributors() ->wherePivot('role', 'contributor'); } + public function managers() + { + return $this->belongsToMany(PivotEventsTestUser::class, 'project_users', 'project_id', 'user_id') + ->using(PivotEventsTestCollaborator::class) + ->withPivotValue('role', 'manager'); + } + public function equipments() { return $this->morphToMany(PivotEventsTestEquipment::class, 'equipmentable')->using(PivotEventsTestModelEquipment::class); diff --git a/tests/Integration/Database/EloquentPrunableTest.php b/tests/Integration/Database/EloquentPrunableTest.php index 22f48e9464fc..495b3f53dfdb 100644 --- a/tests/Integration/Database/EloquentPrunableTest.php +++ b/tests/Integration/Database/EloquentPrunableTest.php @@ -2,12 +2,14 @@ namespace Illuminate\Tests\Integration\Database; +use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Events\ModelsPruned; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Exceptions; use Illuminate\Support\Facades\Schema; use LogicException; @@ -20,6 +22,7 @@ protected function afterRefreshingDatabase() 'prunable_soft_delete_test_models', 'prunable_test_model_missing_prunable_methods', 'prunable_with_custom_prune_method_test_models', + 'prunable_with_exceptions', ])->each(function ($table) { Schema::create($table, function (Blueprint $table) { $table->increments('id'); @@ -97,6 +100,27 @@ public function testPruneWithCustomPruneMethod() Event::assertDispatched(ModelsPruned::class, 1); } + + public function testPruneWithExceptionAtOneOfModels() + { + Event::fake(); + Exceptions::fake(); + + collect(range(1, 5000))->map(function ($id) { + return ['name' => 'foo']; + })->chunk(200)->each(function ($chunk) { + PrunableWithException::insert($chunk->all()); + }); + + $count = (new PrunableWithException)->pruneAll(); + + $this->assertEquals(999, $count); + + Event::assertDispatched(ModelsPruned::class, 1); + Event::assertDispatched(fn (ModelsPruned $event) => $event->count === 999); + Exceptions::assertReportedCount(1); + Exceptions::assertReported(fn (Exception $exception) => $exception->getMessage() === 'foo bar'); + } } class PrunableTestModel extends Model @@ -136,6 +160,23 @@ public function prune() } } +class PrunableWithException extends Model +{ + use Prunable; + + public function prunable() + { + return $this->where('id', '<=', 1000); + } + + public function prune() + { + if ($this->id === 500) { + throw new Exception('foo bar'); + } + } +} + class PrunableTestModelMissingPrunableMethod extends Model { use Prunable; diff --git a/tests/Integration/Database/Fixtures/NamedScopeUser.php b/tests/Integration/Database/Fixtures/NamedScopeUser.php new file mode 100644 index 000000000000..b33b8823dc2e --- /dev/null +++ b/tests/Integration/Database/Fixtures/NamedScopeUser.php @@ -0,0 +1,44 @@ + 'datetime', + 'password' => 'hashed', + ]; + } + + #[NamedScope] + protected function verified(Builder $builder, bool $email = true) + { + return $builder->when( + $email === true, + fn ($query) => $query->whereNotNull('email_verified_at'), + fn ($query) => $query->whereNull('email_verified_at'), + ); + } + + #[NamedScope] + protected function verifiedWithoutReturn(Builder $builder, bool $email = true) + { + $this->verified($builder, $email); + } + + public function scopeVerifiedUser(Builder $builder, bool $email = true) + { + return $builder->when( + $email === true, + fn ($query) => $query->whereNotNull('email_verified_at'), + fn ($query) => $query->whereNull('email_verified_at'), + ); + } +} diff --git a/tests/Integration/Database/MigrationServiceProviderTest.php b/tests/Integration/Database/MigrationServiceProviderTest.php new file mode 100644 index 000000000000..6da602074f39 --- /dev/null +++ b/tests/Integration/Database/MigrationServiceProviderTest.php @@ -0,0 +1,16 @@ +app->make('migrator'); + $fromClass = $this->app->make(Migrator::class); + + $this->assertSame($fromString, $fromClass); + } +} diff --git a/tests/Integration/Database/SchemaBuilderSchemaNameTest.php b/tests/Integration/Database/SchemaBuilderSchemaNameTest.php index b88b1964aadd..93bd151f4b1b 100644 --- a/tests/Integration/Database/SchemaBuilderSchemaNameTest.php +++ b/tests/Integration/Database/SchemaBuilderSchemaNameTest.php @@ -8,17 +8,42 @@ use Orchestra\Testbench\Attributes\RequiresDatabase; use PHPUnit\Framework\Attributes\DataProvider; -#[RequiresDatabase(['pgsql', 'sqlsrv'])] class SchemaBuilderSchemaNameTest extends DatabaseTestCase { + protected function setUp(): void + { + parent::setUp(); + + if ($this->usesSqliteInMemoryDatabaseConnection()) { + $this->markTestSkipped('Test cannot be run using :memory: database connection, SQLite test file is here: \Illuminate\Tests\Integration\Database\Sqlite\SchemaBuilderSchemaNameTest'); + } + } + protected function defineDatabaseMigrations() { - if ($this->driver === 'pgsql') { - DB::connection('without-prefix')->statement('create schema if not exists my_schema'); - DB::connection('with-prefix')->statement('create schema if not exists my_schema'); + if (in_array($this->driver, ['mariadb', 'mysql'])) { + Schema::createDatabase('my_schema'); + } elseif ($this->driver === 'sqlite') { + DB::connection('without-prefix')->statement("attach database ':memory:' as my_schema"); + DB::connection('with-prefix')->statement("attach database ':memory:' as my_schema"); + } elseif ($this->driver === 'pgsql') { + DB::statement('create schema if not exists my_schema'); } elseif ($this->driver === 'sqlsrv') { - DB::connection('without-prefix')->statement("if schema_id('my_schema') is null begin exec('create schema my_schema') end"); - DB::connection('with-prefix')->statement("if schema_id('my_schema') is null begin exec('create schema my_schema') end"); + DB::statement("if schema_id('my_schema') is null begin exec('create schema my_schema') end"); + } + } + + protected function destroyDatabaseMigrations() + { + if (in_array($this->driver, ['mariadb', 'mysql'])) { + Schema::dropDatabaseIfExists('my_schema'); + } elseif ($this->driver === 'sqlite') { + DB::connection('without-prefix')->statement('detach database my_schema'); + DB::connection('with-prefix')->statement('detach database my_schema'); + } elseif ($this->driver === 'pgsql') { + DB::statement('drop schema if exists my_schema cascade'); + } elseif ($this->driver === 'sqlsrv') { + // DB::statement("if schema_id('my_schema') is not null begin exec('drop schema my_schema') end"); } } @@ -26,12 +51,34 @@ protected function defineEnvironment($app) { parent::defineEnvironment($app); + $connection = $app['config']->get('database.default'); + + $app['config']->set("database.connections.$connection.prefix_indexes", true); $app['config']->set('database.connections.pgsql.search_path', 'public,my_schema'); - $app['config']->set('database.connections.without-prefix', $app['config']->get('database.connections.'.$this->driver)); + $app['config']->set('database.connections.without-prefix', $app['config']->get('database.connections.'.$connection)); $app['config']->set('database.connections.with-prefix', $app['config']->get('database.connections.without-prefix')); $app['config']->set('database.connections.with-prefix.prefix', 'example_'); } + #[DataProvider('connectionProvider')] + public function testSchemas($connection) + { + $schema = Schema::connection($connection); + + $schemas = $schema->getSchemas(); + + $this->assertSame($schema->getCurrentSchemaName(), collect($schemas)->firstWhere('default')['name']); + $this->assertEqualsCanonicalizing( + match ($this->driver) { + 'mysql', 'mariadb' => ['laravel', 'my_schema'], + 'pgsql' => ['public', 'my_schema'], + 'sqlite' => ['main', 'my_schema'], + 'sqlsrv' => ['dbo', 'guest', 'my_schema'], + }, + array_column($schemas, 'name'), + ); + } + #[DataProvider('connectionProvider')] public function testCreate($connection) { @@ -43,6 +90,14 @@ public function testCreate($connection) $this->assertTrue($schema->hasTable('my_schema.table')); $this->assertFalse($schema->hasTable('table')); + + $currentSchema = $schema->getCurrentSchemaName(); + $tableName = $connection === 'with-prefix' ? 'example_table' : 'table'; + + $this->assertEqualsCanonicalizing( + [$currentSchema.'.migrations', 'my_schema.'.$tableName], + $schema->getTableListing([$currentSchema, 'my_schema']) + ); } #[DataProvider('connectionProvider')] @@ -62,7 +117,11 @@ public function testRename($connection) $this->assertTrue($schema->hasTable('table')); $this->assertFalse($schema->hasTable('my_table')); - $schema->rename('my_schema.table', 'new_table'); + if (in_array($this->driver, ['mariadb', 'mysql'])) { + $schema->rename('my_schema.table', 'my_schema.new_table'); + } else { + $schema->rename('my_schema.table', 'new_table'); + } $schema->rename('table', 'my_table'); $this->assertTrue($schema->hasTable('my_schema.new_table')); @@ -86,10 +145,23 @@ public function testDrop($connection) $this->assertTrue($schema->hasTable('my_schema.table')); $this->assertTrue($schema->hasTable('table')); + $currentSchema = $schema->getCurrentSchemaName(); + $tableName = $connection === 'with-prefix' ? 'example_table' : 'table'; + + $this->assertEqualsCanonicalizing( + [$currentSchema.'.migrations', $currentSchema.'.'.$tableName, 'my_schema.'.$tableName], + $schema->getTableListing([$currentSchema, 'my_schema']) + ); + $schema->drop('my_schema.table'); $this->assertFalse($schema->hasTable('my_schema.table')); $this->assertTrue($schema->hasTable('table')); + + $this->assertEqualsCanonicalizing( + [$currentSchema.'.migrations', $currentSchema.'.'.$tableName], + $schema->getTableListing([$currentSchema, 'my_schema']) + ); } #[DataProvider('connectionProvider')] @@ -210,8 +282,16 @@ public function testModifyColumns($connection) $this->assertStringContainsString('default title', collect($schema->getColumns('my_table'))->firstWhere('name', 'title')['default']); $this->assertEquals($this->driver === 'sqlsrv' ? 'nvarchar' : 'varchar', $schema->getColumnType('my_schema.table', 'name')); $this->assertEquals($this->driver === 'sqlsrv' ? 'nvarchar' : 'varchar', $schema->getColumnType('my_table', 'title')); - $this->assertEquals($this->driver === 'pgsql' ? 'int8' : 'bigint', $schema->getColumnType('my_schema.table', 'count')); - $this->assertEquals($this->driver === 'pgsql' ? 'int8' : 'bigint', $schema->getColumnType('my_table', 'count')); + $this->assertEquals(match ($this->driver) { + 'pgsql' => 'int8', + 'sqlite' => 'integer', + default => 'bigint', + }, $schema->getColumnType('my_schema.table', 'count')); + $this->assertEquals(match ($this->driver) { + 'pgsql' => 'int8', + 'sqlite' => 'integer', + default => 'bigint', + }, $schema->getColumnType('my_table', 'count')); } #[DataProvider('connectionProvider')] @@ -306,6 +386,7 @@ public function testIndexes($connection) } #[DataProvider('connectionProvider')] + #[RequiresDatabase(['mariadb', 'mysql', 'pgsql', 'sqlsrv'])] public function testForeignKeys($connection) { $schema = Schema::connection($connection); @@ -315,7 +396,8 @@ public function testForeignKeys($connection) }); $schema->create('my_schema.table', function (Blueprint $table) { $table->id(); - $table->foreignId('my_table_id')->constrained(); + $table->foreignId('my_table_id') + ->constrained(table: in_array($this->driver, ['mariadb', 'mysql']) ? 'laravel.my_tables' : null); }); $schema->create('table', function (Blueprint $table) { $table->unsignedBigInteger('table_id'); @@ -324,10 +406,15 @@ public function testForeignKeys($connection) $schemaTableName = $connection === 'with-prefix' ? 'example_table' : 'table'; $tableName = $connection === 'with-prefix' ? 'example_my_tables' : 'my_tables'; + $defaultSchemaName = match ($this->driver) { + 'pgsql' => 'public', + 'sqlsrv' => 'dbo', + default => 'laravel', + }; $this->assertTrue(collect($schema->getForeignKeys('my_schema.table'))->contains( fn ($foreign) => $foreign['columns'] === ['my_table_id'] - && $foreign['foreign_table'] === $tableName && in_array($foreign['foreign_schema'], ['public', 'dbo']) + && $foreign['foreign_table'] === $tableName && $foreign['foreign_schema'] === $defaultSchemaName && $foreign['foreign_columns'] === ['id'] )); @@ -348,29 +435,80 @@ public function testForeignKeys($connection) $this->assertEmpty($schema->getForeignKeys('table')); } + #[DataProvider('connectionProvider')] + #[RequiresDatabase('sqlite')] + public function testForeignKeysOnSameSchema($connection) + { + $schema = Schema::connection($connection); + + $schema->create('my_schema.my_tables', function (Blueprint $table) { + $table->id(); + }); + $schema->create('my_schema.table', function (Blueprint $table) { + $table->id(); + $table->foreignId('my_table_id')->constrained(); + }); + $schema->create('my_schema.second_table', function (Blueprint $table) { + $table->unsignedBigInteger('table_id'); + $table->foreign('table_id')->references('id')->on('table'); + }); + + $myTableName = $connection === 'with-prefix' ? 'example_my_tables' : 'my_tables'; + $tableName = $connection === 'with-prefix' ? 'example_table' : 'table'; + + $this->assertTrue(collect($schema->getForeignKeys('my_schema.table'))->contains( + fn ($foreign) => $foreign['columns'] === ['my_table_id'] + && $foreign['foreign_table'] === $myTableName && $foreign['foreign_schema'] === 'my_schema' + && $foreign['foreign_columns'] === ['id'] + )); + + $this->assertTrue(collect($schema->getForeignKeys('my_schema.second_table'))->contains( + fn ($foreign) => $foreign['columns'] === ['table_id'] + && $foreign['foreign_table'] === $tableName && $foreign['foreign_schema'] === 'my_schema' + && $foreign['foreign_columns'] === ['id'] + )); + + $schema->table('my_schema.table', function (Blueprint $table) { + $table->dropForeign(['my_table_id']); + }); + + $this->assertEmpty($schema->getForeignKeys('my_schema.table')); + } + #[DataProvider('connectionProvider')] public function testHasView($connection) { - $connection = DB::connection($connection); - $schema = $connection->getSchemaBuilder(); + $db = DB::connection($connection); + $schema = $db->getSchemaBuilder(); - $connection->statement('create view '.$connection->getSchemaGrammar()->wrapTable('my_schema.view').' (name) as select 1'); - $connection->statement('create view '.$connection->getSchemaGrammar()->wrapTable('my_view').' (name) as select 1'); + $db->statement('create view '.$db->getSchemaGrammar()->wrapTable('my_schema.view').' (name) as select 1'); + $db->statement('create view '.$db->getSchemaGrammar()->wrapTable('my_view').' (name) as select 1'); $this->assertTrue($schema->hasView('my_schema.view')); $this->assertTrue($schema->hasView('my_view')); $this->assertTrue($schema->hasColumn('my_schema.view', 'name')); $this->assertTrue($schema->hasColumn('my_view', 'name')); - $connection->statement('drop view '.$connection->getSchemaGrammar()->wrapTable('my_schema.view')); - $connection->statement('drop view '.$connection->getSchemaGrammar()->wrapTable('my_view')); + $currentSchema = $schema->getCurrentSchemaName(); + $viewName = $connection === 'with-prefix' ? 'example_view' : 'view'; + $myViewName = $connection === 'with-prefix' ? 'example_my_view' : 'my_view'; + + $this->assertEqualsCanonicalizing( + [$currentSchema.'.'.$myViewName, 'my_schema.'.$viewName], + array_column($schema->getViews([$currentSchema, 'my_schema']), 'schema_qualified_name') + ); + + $db->statement('drop view '.$db->getSchemaGrammar()->wrapTable('my_schema.view')); + $db->statement('drop view '.$db->getSchemaGrammar()->wrapTable('my_view')); $this->assertFalse($schema->hasView('my_schema.view')); $this->assertFalse($schema->hasView('my_view')); + + $this->assertEmpty($schema->getViews([$currentSchema, 'my_schema'])); } #[DataProvider('connectionProvider')] - #[RequiresDatabase('pgsql')] + #[RequiresDatabase(['mariadb', 'mysql', 'pgsql'])] public function testComment($connection) { $schema = Schema::connection($connection); @@ -386,12 +524,13 @@ public function testComment($connection) $tables = collect($schema->getTables()); $tableName = $connection === 'with-prefix' ? 'example_table' : 'table'; + $defaultSchema = $this->driver === 'pgsql' ? 'public' : 'laravel'; $this->assertEquals('comment on schema table', $tables->first(fn ($table) => $table['name'] === $tableName && $table['schema'] === 'my_schema')['comment'] ); $this->assertEquals('comment on table', - $tables->first(fn ($table) => $table['name'] === $tableName && $table['schema'] === 'public')['comment'] + $tables->first(fn ($table) => $table['name'] === $tableName && $table['schema'] === $defaultSchema)['comment'] ); $this->assertEquals('comment on schema column', collect($schema->getColumns('my_schema.table'))->firstWhere('name', 'name')['comment'] @@ -402,7 +541,7 @@ public function testComment($connection) } #[DataProvider('connectionProvider')] - #[RequiresDatabase('pgsql')] + #[RequiresDatabase(['mariadb', 'mysql', 'pgsql'])] public function testAutoIncrementStartingValue($connection) { $this->expectNotToPerformAssertions(); @@ -440,7 +579,7 @@ public function testHasTable($connection) 'database.connections.'.$connection.'.password' => 'Passw0rd', ]); - $this->assertEquals('my_schema', $db->scalar('select schema_name()')); + $this->assertEquals('my_schema', $schema->getCurrentSchemaName()); $schema->create('table', function (Blueprint $table) { $table->id(); diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 612ac3dc16e6..6fca5a006116 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -4,7 +4,6 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Grammars\SQLiteGrammar; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\Attributes\RequiresDatabase; @@ -51,11 +50,11 @@ public function testChangeToTinyInteger() $table->string('test_column'); }); - $blueprint = new Blueprint('test', function (Blueprint $table) { + $blueprint = new Blueprint($this->getConnection(), 'test', function (Blueprint $table) { $table->tinyInteger('test_column')->change(); }); - $blueprint->build($this->getConnection(), new SQLiteGrammar); + $blueprint->build(); $this->assertSame('integer', Schema::getColumnType('test', 'test_column')); } @@ -68,17 +67,15 @@ public function testChangeToTextColumn() }); foreach (['tinyText', 'text', 'mediumText', 'longText'] as $type) { - $blueprint = new Blueprint('test', function ($table) use ($type) { + $blueprint = new Blueprint($this->getConnection(), 'test', function ($table) use ($type) { $table->$type('test_column')->change(); }); - $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $uppercase = strtolower($type); $expected = ["alter table `test` modify `test_column` $uppercase not null"]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $blueprint->toSql()); } } @@ -90,17 +87,15 @@ public function testChangeTextColumnToTextColumn() }); foreach (['tinyText', 'mediumText', 'longText'] as $type) { - $blueprint = new Blueprint('test', function ($table) use ($type) { + $blueprint = new Blueprint($this->getConnection(), 'test', function ($table) use ($type) { $table->$type('test_column')->change(); }); - $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $lowercase = strtolower($type); $expected = ["alter table `test` modify `test_column` $lowercase not null"]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $blueprint->toSql()); } } @@ -114,15 +109,13 @@ public function testModifyNullableColumn() $table->string('nullable_column_to_not_null')->nullable(); }); - $blueprint = new Blueprint('test', function ($table) { + $blueprint = new Blueprint($this->getConnection(), 'test', function ($table) { $table->text('not_null_column_to_not_null')->change(); $table->text('not_null_column_to_nullable')->nullable()->change(); $table->text('nullable_column_to_nullable')->nullable()->change(); $table->text('nullable_column_to_not_null')->change(); }); - $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $expected = [ 'alter table `test` modify `not_null_column_to_not_null` text not null', 'alter table `test` modify `not_null_column_to_nullable` text null', @@ -130,7 +123,7 @@ public function testModifyNullableColumn() 'alter table `test` modify `nullable_column_to_not_null` text not null', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $blueprint->toSql()); } public function testChangeNullableColumn() @@ -799,18 +792,6 @@ public function testAddAndDropPrimaryOnSqlite() $this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique')); } - #[RequiresDatabase('sqlite')] - public function testSetJournalModeOnSqlite() - { - file_put_contents(DB::connection('sqlite')->getConfig('database'), ''); - - $this->assertSame('delete', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode); - - Schema::connection('sqlite')->setJournalMode('WAL'); - - $this->assertSame('wal', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode); - } - public function testAddingMacros() { Schema::macro('foo', fn () => 'foo'); diff --git a/tests/Integration/Database/Sqlite/ConnectorTest.php b/tests/Integration/Database/Sqlite/ConnectorTest.php new file mode 100644 index 000000000000..04ead4ba1ff9 --- /dev/null +++ b/tests/Integration/Database/Sqlite/ConnectorTest.php @@ -0,0 +1,61 @@ +databasePath = database_path('secondary.sqlite')); + } + + protected function destroyDatabaseMigrations() + { + Schema::dropDatabaseIfExists($this->databasePath); + } + + public function testConnectionConfigurations() + { + $schema = DB::build([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ])->getSchemaBuilder(); + + $this->assertSame(0, $schema->pragma('foreign_keys')); + $this->assertSame(60000, $schema->pragma('busy_timeout')); + $this->assertSame('memory', $schema->pragma('journal_mode')); + $this->assertSame(2, $schema->pragma('synchronous')); + + $schema = DB::build([ + 'driver' => 'sqlite', + 'database' => $this->databasePath, + 'foreign_key_constraints' => true, + 'busy_timeout' => 12345, + 'journal_mode' => 'wal', + 'synchronous' => 'normal', + ])->getSchemaBuilder(); + + $this->assertSame(1, $schema->pragma('foreign_keys')); + $this->assertSame(12345, $schema->pragma('busy_timeout')); + $this->assertSame('wal', $schema->pragma('journal_mode')); + $this->assertSame(1, $schema->pragma('synchronous')); + + $schema->pragma('foreign_keys', 0); + $schema->pragma('busy_timeout', 54321); + $schema->pragma('journal_mode', 'delete'); + $schema->pragma('synchronous', 0); + + $this->assertSame(0, $schema->pragma('foreign_keys')); + $this->assertSame(54321, $schema->pragma('busy_timeout')); + $this->assertSame('delete', $schema->pragma('journal_mode')); + $this->assertSame(0, $schema->pragma('synchronous')); + } +} diff --git a/tests/Integration/Database/Sqlite/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/Sqlite/DatabaseSchemaBlueprintTest.php index 27d074cb5509..cf1e71712318 100644 --- a/tests/Integration/Database/Sqlite/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/Sqlite/DatabaseSchemaBlueprintTest.php @@ -2,11 +2,8 @@ namespace Illuminate\Tests\Integration\Database\Sqlite; +use Closure; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Grammars\MySqlGrammar; -use Illuminate\Database\Schema\Grammars\PostgresGrammar; -use Illuminate\Database\Schema\Grammars\SQLiteGrammar; -use Illuminate\Database\Schema\Grammars\SqlServerGrammar; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\Attributes\RequiresDatabase; @@ -35,12 +32,12 @@ public function testRenamingAndChangingColumnsWork() $table->string('age'); }); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('SQLite', 'users', function ($table) { $table->renameColumn('name', 'first_name'); $table->integer('age')->change(); }); - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(); $expected = [ 'alter table "users" rename column "name" to "first_name"', @@ -72,37 +69,9 @@ public function testRenamingColumnsWorks() $this->assertTrue($schema->hasColumns('test', ['bar', 'qux'])); } - public function testNativeColumnModifyingOnMySql() - { - $connection = DB::connection(); - $schema = $connection->getSchemaBuilder(); - - $blueprint = new Blueprint('users', function ($table) { - $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->geometry('positions', 'multipolygon', 1234)->storedAs('expression')->change(); - $table->string('old_name', 50)->renameTo('new_name')->change(); - $table->bigIncrements('id')->first()->from(10)->comment('my comment')->change(); - }); - - $this->assertEquals([ - 'alter table `users` modify `amount` double null invisible after `name`', - 'alter table `users` modify `added_at` timestamp(4) not null default CURRENT_TIMESTAMP(4) on update CURRENT_TIMESTAMP(4)', - "alter table `users` modify `difficulty` enum('easy', 'hard') character set utf8mb4 collate 'unicode' not null default 'easy'", - 'alter table `users` modify `positions` multipolygon srid 1234 as (expression) stored', - 'alter table `users` change `old_name` `new_name` varchar(50) not null', - "alter table `users` modify `id` bigint unsigned not null auto_increment comment 'my comment' first", - 'alter table `users` auto_increment = 10', - ], $blueprint->toSql($connection, new MySqlGrammar)); - } - public function testNativeColumnModifyingOnPostgreSql() { - $connection = DB::connection(); - $schema = $connection->getSchemaBuilder(); - - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('Postgres', 'users', function ($table) { $table->integer('code')->autoIncrement()->from(10)->comment('my comment')->change(); }); @@ -112,9 +81,9 @@ public function testNativeColumnModifyingOnPostgreSql() .'alter column "code" set not null', 'alter sequence users_code_seq restart with 10', 'comment on column "users"."code" is \'my comment\'', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('Postgres', 'users', function ($table) { $table->char('name', 40)->nullable()->default('easy')->collation('unicode')->change(); }); @@ -125,9 +94,9 @@ public function testNativeColumnModifyingOnPostgreSql() .'alter column "name" set default \'easy\', ' .'alter column "name" drop identity if exists', 'comment on column "users"."name" is NULL', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('Postgres', 'users', function ($table) { $table->integer('foo')->generatedAs('expression')->always()->change(); }); @@ -139,9 +108,9 @@ public function testNativeColumnModifyingOnPostgreSql() .'alter column "foo" drop identity if exists, ' .'alter column "foo" add generated always as identity (expression)', 'comment on column "users"."foo" is NULL', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('Postgres', 'users', function ($table) { $table->geometry('foo', 'point', 1234)->change(); }); @@ -152,9 +121,9 @@ public function testNativeColumnModifyingOnPostgreSql() .'alter column "foo" drop default, ' .'alter column "foo" drop identity if exists', 'comment on column "users"."foo" is NULL', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('Postgres', 'users', function ($table) { $table->timestamp('added_at', 2)->useCurrent()->storedAs(null)->change(); }); @@ -166,15 +135,12 @@ public function testNativeColumnModifyingOnPostgreSql() .'alter column "added_at" drop expression if exists, ' .'alter column "added_at" drop identity if exists', 'comment on column "users"."added_at" is NULL', - ], $blueprint->toSql($connection, new PostgresGrammar)); + ], $blueprint->toSql()); } public function testNativeColumnModifyingOnSqlServer() { - $connection = DB::connection(); - $schema = $connection->getSchemaBuilder(); - - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('SqlServer', 'users', function ($table) { $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->change(); }); @@ -182,9 +148,9 @@ public function testNativeColumnModifyingOnSqlServer() "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"users\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"users\"') AND [name] in ('added_at') AND [default_object_id] <> 0;EXEC(@sql)", 'alter table "users" alter column "added_at" datetime2(4) not null', 'alter table "users" add default CURRENT_TIMESTAMP for "added_at"', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('SqlServer', 'users', function ($table) { $table->char('name', 40)->nullable()->default('easy')->collation('unicode')->change(); }); @@ -192,16 +158,16 @@ public function testNativeColumnModifyingOnSqlServer() "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"users\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"users\"') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql)", 'alter table "users" alter column "name" nchar(40) collate unicode null', 'alter table "users" add default \'easy\' for "name"', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + ], $blueprint->toSql()); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('SqlServer', 'users', function ($table) { $table->integer('foo')->change(); }); $this->assertEquals([ "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"users\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"users\"') AND [name] in ('foo') AND [default_object_id] <> 0;EXEC(@sql)", 'alter table "users" alter column "foo" int not null', - ], $blueprint->toSql($connection, new SqlServerGrammar)); + ], $blueprint->toSql()); } public function testChangingColumnWithCollationWorks() @@ -210,15 +176,15 @@ public function testChangingColumnWithCollationWorks() $table->string('age'); }); - $blueprint = new Blueprint('users', function ($table) { + $blueprint = $this->getBlueprint('SQLite', 'users', function ($table) { $table->integer('age')->collation('RTRIM')->change(); }); - $blueprint2 = new Blueprint('users', function ($table) { + $blueprint2 = $this->getBlueprint('SQLite', 'users', function ($table) { $table->integer('age')->collation('NOCASE')->change(); }); - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $queries = $blueprint->toSql(); $expected = [ 'create table "__temp__users" ("age" integer not null collate \'RTRIM\')', @@ -229,7 +195,7 @@ public function testChangingColumnWithCollationWorks() $this->assertEquals($expected, $queries); - $queries = $blueprint2->toSql(DB::connection(), new SQLiteGrammar); + $queries = $blueprint2->toSql(); $expected = [ 'create table "__temp__users" ("age" integer not null collate \'NOCASE\')', @@ -247,11 +213,11 @@ public function testChangingCharColumnsWork() $table->string('name'); }); - $blueprint = new Blueprint('users', function ($table) { - $table->char('name', 50)->change(); - }); - - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->text('changed_col')->change(); + })->toSql(); + }; $expected = [ 'create table "__temp__users" ("name" varchar not null)', @@ -260,7 +226,7 @@ public function testChangingCharColumnsWork() 'alter table "__temp__users" rename to "users"', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SQLite')); } public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumnsWork() @@ -269,11 +235,11 @@ public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumns $table->increments('id'); }); - $blueprint = new Blueprint('users', function ($table) { - $table->binary('id')->change(); - }); - - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->binary('id')->change(); + })->toSql(); + }; $expected = [ 'create table "__temp__users" ("id" blob not null, primary key ("id"))', @@ -282,7 +248,7 @@ public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumns 'alter table "__temp__users" rename to "users"', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SQLite')); } public function testChangingDoubleColumnsWork() @@ -291,11 +257,11 @@ public function testChangingDoubleColumnsWork() $table->integer('price'); }); - $blueprint = new Blueprint('products', function ($table) { - $table->double('price')->change(); - }); - - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'products', function ($table) { + $table->double('price')->change(); + })->toSql(); + }; $expected = [ 'create table "__temp__products" ("price" double not null)', @@ -304,23 +270,23 @@ public function testChangingDoubleColumnsWork() 'alter table "__temp__products" rename to "products"', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SQLite')); } public function testChangingColumnsWithDefaultWorks() { - DB::connection()->getSchemaBuilder()->create('products', function (Blueprint $table) { + DB::connection()->getSchemaBuilder()->create('products', function ($table) { $table->integer('changed_col'); $table->timestamp('timestamp_col')->useCurrent(); $table->integer('integer_col')->default(123); $table->string('string_col')->default('value'); }); - $blueprint = new Blueprint('products', function ($table) { - $table->text('changed_col')->change(); - }); - - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'products', function ($table) { + $table->text('changed_col')->change(); + })->toSql(); + }; $expected = [ 'create table "__temp__products" ("changed_col" text not null, "timestamp_col" datetime not null default (CURRENT_TIMESTAMP), "integer_col" integer not null default (\'123\'), "string_col" varchar not null default (\'value\'))', @@ -329,7 +295,7 @@ public function testChangingColumnsWithDefaultWorks() 'alter table "__temp__products" rename to "products"', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SQLite')); } public function testRenameIndexWorks() @@ -338,47 +304,40 @@ public function testRenameIndexWorks() $table->string('name'); $table->string('age'); }); - DB::connection()->getSchemaBuilder()->table('users', function ($table) { $table->index(['name'], 'index1'); }); - $blueprint = new Blueprint('users', function ($table) { - $table->renameIndex('index1', 'index2'); - }); - - $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->renameIndex('index1', 'index2'); + })->toSql(); + }; $expected = [ 'drop index "index1"', 'create index "index2" on "users" ("name")', ]; - $this->assertEquals($expected, $queries); - - $queries = $blueprint->toSql(DB::connection(), new SqlServerGrammar); + $this->assertEquals($expected, $getSql('SQLite')); $expected = [ 'sp_rename N\'"users"."index1"\', "index2", N\'INDEX\'', ]; - $this->assertEquals($expected, $queries); - - $queries = $blueprint->toSql(DB::connection(), new MySqlGrammar); + $this->assertEquals($expected, $getSql('SqlServer')); $expected = [ 'alter table `users` rename index `index1` to `index2`', ]; - $this->assertEquals($expected, $queries); - - $queries = $blueprint->toSql(DB::connection(), new PostgresGrammar); + $this->assertEquals($expected, $getSql('MySql')); $expected = [ 'alter index "index1" rename to "index2"', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('Postgres')); } public function testAddUniqueIndexWithoutNameWorks() @@ -387,24 +346,18 @@ public function testAddUniqueIndexWithoutNameWorks() $table->string('name')->nullable(); }); - $blueprintMySql = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique()->change(); - }); - - $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->string('name')->nullable()->unique()->change(); + })->toSql(); + }; $expected = [ 'alter table `users` modify `name` varchar(255) null', 'alter table `users` add unique `users_name_unique`(`name`)', ]; - $this->assertEquals($expected, $queries); - - $blueprintPostgres = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique()->change(); - }); - - $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); + $this->assertEquals($expected, $getSql('MySql')); $expected = [ 'alter table "users" alter column "name" type varchar(255), alter column "name" drop not null, alter column "name" drop default, alter column "name" drop identity if exists', @@ -412,13 +365,7 @@ public function testAddUniqueIndexWithoutNameWorks() 'comment on column "users"."name" is NULL', ]; - $this->assertEquals($expected, $queries); - - $blueprintSQLite = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique()->change(); - }); - - $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); + $this->assertEquals($expected, $getSql('Postgres')); $expected = [ 'create table "__temp__users" ("name" varchar)', @@ -428,13 +375,7 @@ public function testAddUniqueIndexWithoutNameWorks() 'create unique index "users_name_unique" on "users" ("name")', ]; - $this->assertEquals($expected, $queries); - - $blueprintSqlServer = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique()->change(); - }); - - $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); + $this->assertEquals($expected, $getSql('SQLite')); $expected = [ "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"users\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"users\"') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql)", @@ -442,7 +383,7 @@ public function testAddUniqueIndexWithoutNameWorks() 'create unique index "users_name_unique" on "users" ("name")', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SqlServer')); } public function testAddUniqueIndexWithNameWorks() @@ -451,24 +392,18 @@ public function testAddUniqueIndexWithNameWorks() $table->string('name')->nullable(); }); - $blueprintMySql = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique('index1')->change(); - }); - - $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->unsignedInteger('name')->nullable()->unique('index1')->change(); + })->toSql(); + }; $expected = [ - 'alter table `users` modify `name` varchar(255) null', + 'alter table `users` modify `name` int unsigned null', 'alter table `users` add unique `index1`(`name`)', ]; - $this->assertEquals($expected, $queries); - - $blueprintPostgres = new Blueprint('users', function ($table) { - $table->unsignedInteger('name')->nullable()->unique('index1')->change(); - }); - - $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); + $this->assertEquals($expected, $getSql('MySql')); $expected = [ 'alter table "users" alter column "name" type integer, alter column "name" drop not null, alter column "name" drop default, alter column "name" drop identity if exists', @@ -476,13 +411,7 @@ public function testAddUniqueIndexWithNameWorks() 'comment on column "users"."name" is NULL', ]; - $this->assertEquals($expected, $queries); - - $blueprintSQLite = new Blueprint('users', function ($table) { - $table->unsignedInteger('name')->nullable()->unique('index1')->change(); - }); - - $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); + $this->assertEquals($expected, $getSql('Postgres')); $expected = [ 'create table "__temp__users" ("name" integer)', @@ -492,13 +421,7 @@ public function testAddUniqueIndexWithNameWorks() 'create unique index "index1" on "users" ("name")', ]; - $this->assertEquals($expected, $queries); - - $blueprintSqlServer = new Blueprint('users', function ($table) { - $table->unsignedInteger('name')->nullable()->unique('index1')->change(); - }); - - $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); + $this->assertEquals($expected, $getSql('SQLite')); $expected = [ "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE \"users\" DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID(N'\"users\"') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql)", @@ -506,7 +429,7 @@ public function testAddUniqueIndexWithNameWorks() 'create unique index "index1" on "users" ("name")', ]; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $getSql('SqlServer')); } public function testAddColumnNamedCreateWorks() @@ -524,46 +447,34 @@ public function testAddColumnNamedCreateWorks() public function testDropIndexOnColumnChangeWorks() { - $connection = DB::connection(); - - $connection->getSchemaBuilder()->create('users', function ($table) { + DB::connection()->getSchemaBuilder()->create('users', function ($table) { $table->string('name')->nullable(); }); - $blueprint = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique(false)->change(); - }); + $getSql = function ($grammar) { + return $this->getBlueprint($grammar, 'users', function ($table) { + $table->string('name')->nullable()->unique(false)->change(); + })->toSql(); + }; $this->assertContains( 'alter table `users` drop index `users_name_unique`', - $blueprint->toSql($connection, new MySqlGrammar) + $getSql('MySql'), ); - $blueprint = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique(false)->change(); - }); - $this->assertContains( 'alter table "users" drop constraint "users_name_unique"', - $blueprint->toSql($connection, new PostgresGrammar) + $getSql('Postgres'), ); - $blueprint = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique(false)->change(); - }); - $this->assertContains( 'drop index "users_name_unique"', - $blueprint->toSql($connection, new SQLiteGrammar) + $getSql('SQLite'), ); - $blueprint = new Blueprint('users', function ($table) { - $table->string('name')->nullable()->unique(false)->change(); - }); - $this->assertContains( 'drop index "users_name_unique" on "users"', - $blueprint->toSql($connection, new SqlServerGrammar) + $getSql('SqlServer'), ); } @@ -597,4 +508,17 @@ public function testItEnsuresDroppingForeignKeyIsAvailable() $table->dropForeign('something'); }); } + + protected function getBlueprint( + string $grammar, + string $table, + Closure $callback, + ): Blueprint { + $grammarClass = 'Illuminate\Database\Schema\Grammars\\'.$grammar.'Grammar'; + + $connection = DB::connection(); + $connection->setSchemaGrammar(new $grammarClass($connection)); + + return new Blueprint($connection, $table, $callback); + } } diff --git a/tests/Integration/Database/Sqlite/SchemaBuilderSchemaNameTest.php b/tests/Integration/Database/Sqlite/SchemaBuilderSchemaNameTest.php new file mode 100644 index 000000000000..03f52e9e875b --- /dev/null +++ b/tests/Integration/Database/Sqlite/SchemaBuilderSchemaNameTest.php @@ -0,0 +1,11 @@ +mustRun(); + remote('migrate:install'); } protected function tearDown(): void diff --git a/tests/Integration/Database/TimestampTypeTest.php b/tests/Integration/Database/TimestampTypeTest.php index 009c4ac2eac8..7088dd4c5dd1 100644 --- a/tests/Integration/Database/TimestampTypeTest.php +++ b/tests/Integration/Database/TimestampTypeTest.php @@ -57,14 +57,12 @@ public function testChangeStringColumnToTimestampColumn() $table->string('string_to_timestamp'); }); - $blueprint = new Blueprint('test', function ($table) { + $blueprint = new Blueprint($this->getConnection(), 'test', function ($table) { $table->timestamp('string_to_timestamp')->nullable()->change(); }); - $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $expected = ['alter table `test` modify `string_to_timestamp` timestamp null']; - $this->assertEquals($expected, $queries); + $this->assertEquals($expected, $blueprint->toSql()); } } diff --git a/tests/Integration/Events/ListenerTest.php b/tests/Integration/Events/ListenerTest.php index fbd34776406c..490117eef174 100644 --- a/tests/Integration/Events/ListenerTest.php +++ b/tests/Integration/Events/ListenerTest.php @@ -11,8 +11,6 @@ class ListenerTest extends TestCase { protected function tearDown(): void { - parent::tearDown(); - ListenerTestListener::$ran = false; ListenerTestListenerAfterCommit::$ran = false; diff --git a/tests/Integration/Filesystem/ServeFileTest.php b/tests/Integration/Filesystem/ServeFileTest.php index c5219761ab10..ccc99350e60f 100644 --- a/tests/Integration/Filesystem/ServeFileTest.php +++ b/tests/Integration/Filesystem/ServeFileTest.php @@ -3,8 +3,10 @@ namespace Illuminate\Tests\Integration\Filesystem; use Illuminate\Support\Facades\Storage; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('filesystems.disks.local.serve', true)] class ServeFileTest extends TestCase { protected function setUp(): void @@ -48,11 +50,4 @@ public function testItWill403OnWrongSignature() $response->assertForbidden(); } - - protected function getEnvironmentSetup($app) - { - tap($app['config'], function ($config) { - $config->set('filesystems.disks.local.serve', true); - }); - } } diff --git a/tests/Integration/Foundation/Console/OptimizeCommandTest.php b/tests/Integration/Foundation/Console/OptimizeCommandTest.php index 2ea8d7a3f8bc..194670aaf4b5 100644 --- a/tests/Integration/Foundation/Console/OptimizeCommandTest.php +++ b/tests/Integration/Foundation/Console/OptimizeCommandTest.php @@ -14,6 +14,7 @@ class OptimizeCommandTest extends TestCase protected $files = [ 'bootstrap/cache/config.php', 'bootstrap/cache/events.php', + 'bootstrap/cache/routes-v7.php', ]; protected function getPackageProviders($app): array diff --git a/tests/Integration/Foundation/Support/Providers/RouteServiceProviderHealthTest.php b/tests/Integration/Foundation/Support/Providers/RouteServiceProviderHealthTest.php index 1a341eb33a10..6a331999d88f 100644 --- a/tests/Integration/Foundation/Support/Providers/RouteServiceProviderHealthTest.php +++ b/tests/Integration/Foundation/Support/Providers/RouteServiceProviderHealthTest.php @@ -4,8 +4,10 @@ use Illuminate\Foundation\Application; use Illuminate\Support\Str; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('filesystems.disks.local.serve', false)] class RouteServiceProviderHealthTest extends TestCase { /** diff --git a/tests/Integration/Foundation/Support/Providers/RouteServiceProviderTest.php b/tests/Integration/Foundation/Support/Providers/RouteServiceProviderTest.php index 20005e6270dc..fe106bb275df 100644 --- a/tests/Integration/Foundation/Support/Providers/RouteServiceProviderTest.php +++ b/tests/Integration/Foundation/Support/Providers/RouteServiceProviderTest.php @@ -8,8 +8,10 @@ use Illuminate\Foundation\Support\Providers\RouteServiceProvider; use Illuminate\Support\Facades\Route; use Illuminate\Testing\Assert; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('filesystems.disks.local.serve', false)] class RouteServiceProviderTest extends TestCase { /** diff --git a/tests/Integration/Http/HttpClientTest.php b/tests/Integration/Http/HttpClientTest.php index b66c4c18f5cb..8e2b75dfaaf6 100644 --- a/tests/Integration/Http/HttpClientTest.php +++ b/tests/Integration/Http/HttpClientTest.php @@ -21,7 +21,7 @@ public function testGlobalMiddlewarePersistsBeforeWeDispatchEvent(): void Http::get('laravel.com'); Event::assertDispatched(RequestSending::class, function (RequestSending $event) { - return Collection::make($event->request->header('User-Agent'))->contains('Facade/1.0'); + return (new Collection($event->request->header('User-Agent')))->contains('Facade/1.0'); }); } diff --git a/tests/Integration/Mail/MailRoundRobinTransportTest.php b/tests/Integration/Mail/MailRoundRobinTransportTest.php deleted file mode 100644 index cb1bd3c05222..000000000000 --- a/tests/Integration/Mail/MailRoundRobinTransportTest.php +++ /dev/null @@ -1,27 +0,0 @@ - 'roundrobin', 'mailers' => ['sendmail', 'array']])] - public function testGetRoundRobinTransportWithConfiguredTransports() - { - $transport = app('mailer')->getSymfonyTransport(); - $this->assertInstanceOf(RoundRobinTransport::class, $transport); - } - - #[WithConfig('mail.driver', 'roundrobin')] - #[WithConfig('mail.mailers', ['sendmail', 'array'])] - #[WithConfig('mail.sendmail', '/usr/sbin/sendmail -bs')] - public function testGetRoundRobinTransportWithLaravel6StyleMailConfiguration() - { - $transport = app('mailer')->getSymfonyTransport(); - $this->assertInstanceOf(RoundRobinTransport::class, $transport); - } -} diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php index 0b32efc7aa0c..0ea2eafcc403 100644 --- a/tests/Integration/Migration/MigratorTest.php +++ b/tests/Integration/Migration/MigratorTest.php @@ -32,8 +32,9 @@ protected function setUp(): void protected function tearDown(): void { - parent::tearDown(); Migrator::withoutMigrations([]); + + parent::tearDown(); } public function testMigrate() @@ -43,6 +44,7 @@ public function testMigrate() $this->expectTask('2014_10_12_000000_create_people_table', 'DONE'); $this->expectTask('2015_10_04_000000_modify_people_table', 'DONE'); $this->expectTask('2016_10_04_000000_modify_people_table', 'DONE'); + $this->expectTask('2017_10_04_000000_add_age_to_people', 'SKIPPED'); $this->output->shouldReceive('writeln')->once(); @@ -51,6 +53,7 @@ public function testMigrate() $this->assertTrue(DB::getSchemaBuilder()->hasTable('people')); $this->assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'first_name')); $this->assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'last_name')); + $this->assertFalse(DB::getSchemaBuilder()->hasColumn('people', 'age')); } public function testMigrateWithoutOutput() @@ -63,6 +66,7 @@ public function testMigrateWithoutOutput() $this->assertTrue(DB::getSchemaBuilder()->hasTable('people')); $this->assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'first_name')); $this->assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'last_name')); + $this->assertFalse(DB::getSchemaBuilder()->hasColumn('people', 'age')); } public function testWithSkippedMigrations() @@ -119,7 +123,7 @@ public function testPretendMigrate() $this->expectTwoColumnDetail('2016_10_04_000000_modify_people_table'); $this->expectBulletList(['alter table "people" add column "last_name" varchar']); - $this->output->shouldReceive('writeln')->once(); + $this->output->shouldReceive('writeln')->times(3); $this->subject->run([__DIR__.'/fixtures'], ['pretend' => true]); diff --git a/tests/Integration/Migration/fixtures/2017_10_04_000000_add_age_to_people.php b/tests/Integration/Migration/fixtures/2017_10_04_000000_add_age_to_people.php new file mode 100644 index 000000000000..ab42904f9082 --- /dev/null +++ b/tests/Integration/Migration/fixtures/2017_10_04_000000_add_age_to_people.php @@ -0,0 +1,37 @@ +unsignedInteger('age')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('people', function (Blueprint $table) { + $table->dropColumn('age'); + }); + } +}; diff --git a/tests/Integration/Notifications/DatabaseNotificationTest.php b/tests/Integration/Notifications/DatabaseNotificationTest.php new file mode 100644 index 000000000000..e26908d4ed44 --- /dev/null +++ b/tests/Integration/Notifications/DatabaseNotificationTest.php @@ -0,0 +1,73 @@ +create(); + + $user->notify(new NotificationStub); + + Notification::assertSentTo($user, NotificationStub::class, function ($notification, $channels, $notifiable) use ($user) { + return $notifiable === $user; + }); + } + + /** + * Define database and convert User's ID to UUID. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function defineDatabaseAndConvertUserIdToUuid($app): void + { + Schema::table('users', function (Blueprint $table) { + $table->uuid('id')->change(); + }); + } +} + +class UuidUserFactoryStub extends \Orchestra\Testbench\Factories\UserFactory +{ + protected $model = UuidUserStub::class; +} + +class UuidUserStub extends \Illuminate\Foundation\Auth\User +{ + use HasUuids, Notifiable; + + protected $table = 'users'; + + #[\Override] + public function casts() + { + return array_merge(parent::casts(), ['id' => AsStringable::class]); + } +} + +class NotificationStub extends \Illuminate\Notifications\Notification +{ + public function via($notifiable) + { + return ['mail']; + } +} diff --git a/tests/Integration/Queue/CallQueuedHandlerTest.php b/tests/Integration/Queue/CallQueuedHandlerTest.php index 8afa8fe9e025..b97d8d530a58 100644 --- a/tests/Integration/Queue/CallQueuedHandlerTest.php +++ b/tests/Integration/Queue/CallQueuedHandlerTest.php @@ -90,7 +90,7 @@ public function testJobIsMarkedAsFailedIfModelNotFoundExceptionIsThrown() $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app); $job = m::mock(Job::class); - $job->shouldReceive('resolveName')->andReturn(__CLASS__); + $job->shouldReceive('resolveQueuedJobClass')->andReturn(__CLASS__); $job->shouldReceive('fail')->once(); $instance->call($job, [ @@ -106,7 +106,7 @@ public function testJobIsDeletedIfHasDeleteProperty() $job = m::mock(Job::class); $job->shouldReceive('getConnectionName')->andReturn('connection'); - $job->shouldReceive('resolveName')->andReturn(CallQueuedHandlerExceptionThrower::class); + $job->shouldReceive('resolveQueuedJobClass')->andReturn(CallQueuedHandlerExceptionThrower::class); $job->shouldReceive('markAsFailed')->never(); $job->shouldReceive('isDeleted')->andReturn(false); $job->shouldReceive('delete')->once(); @@ -127,7 +127,7 @@ public function testJobIsDeletedIfHasDeleteAttribute() $job = m::mock(Job::class); $job->shouldReceive('getConnectionName')->andReturn('connection'); - $job->shouldReceive('resolveName')->andReturn(CallQueuedHandlerAttributeExceptionThrower::class); + $job->shouldReceive('resolveQueuedJobClass')->andReturn(CallQueuedHandlerAttributeExceptionThrower::class); $job->shouldReceive('markAsFailed')->never(); $job->shouldReceive('isDeleted')->andReturn(false); $job->shouldReceive('delete')->once(); diff --git a/tests/Integration/Queue/DeleteModelWhenMissingTest.php b/tests/Integration/Queue/DeleteModelWhenMissingTest.php new file mode 100644 index 000000000000..9c9f98a83f9c --- /dev/null +++ b/tests/Integration/Queue/DeleteModelWhenMissingTest.php @@ -0,0 +1,93 @@ +set('queue.default', 'database'); + $this->driver = 'database'; + } + + protected function defineDatabaseMigrations() + { + Schema::create('delete_model_test_models', function (Blueprint $table) { + $table->id(); + $table->string('name'); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::dropIfExists('delete_model_test_models'); + } + + #[\Override] + protected function tearDown(): void + { + DeleteMissingModelJob::$handled = false; + + parent::tearDown(); + } + + public function test_deleteModelWhenMissing_and_display_name(): void + { + $model = MyTestModel::query()->create(['name' => 'test']); + + DeleteMissingModelJob::dispatch($model); + + MyTestModel::query()->where('name', 'test')->delete(); + + $this->runQueueWorkerCommand(['--once' => '1']); + + $this->assertFalse(DeleteMissingModelJob::$handled); + $this->assertNull(\DB::table('failed_jobs')->first()); + } +} + +class DeleteMissingModelJob implements ShouldQueue +{ + use InteractsWithQueue; + use Dispatchable; + use SerializesModels; + + public static bool $handled = false; + + public $deleteWhenMissingModels = true; + + public function __construct(public MyTestModel $model) + { + } + + public function displayName(): string + { + return 'sorry-ma-forgot-to-take-out-the-trash'; + } + + public function handle() + { + self::$handled = true; + } +} + +class MyTestModel extends Model +{ + protected $table = 'delete_model_test_models'; + + public $timestamps = false; + + protected $guarded = []; +} diff --git a/tests/Integration/Queue/DynamoBatchTest.php b/tests/Integration/Queue/DynamoBatchTest.php index a395fc414af0..5fb67dded164 100644 --- a/tests/Integration/Queue/DynamoBatchTest.php +++ b/tests/Integration/Queue/DynamoBatchTest.php @@ -19,7 +19,7 @@ #[RequiresEnv('DYNAMODB_ENDPOINT')] class DynamoBatchTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->afterApplicationCreated(function () { BatchRunRecorder::reset(); diff --git a/tests/Integration/Queue/JobDispatchingTest.php b/tests/Integration/Queue/JobDispatchingTest.php index 6b5f4928538f..eddfd83c23c1 100644 --- a/tests/Integration/Queue/JobDispatchingTest.php +++ b/tests/Integration/Queue/JobDispatchingTest.php @@ -7,7 +7,11 @@ use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\Events\JobQueued; +use Illuminate\Queue\Events\JobQueueing; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Config; use Orchestra\Testbench\Attributes\WithMigration; #[WithMigration] @@ -135,6 +139,64 @@ public function testUniqueJobLockIsReleasedForJobDispatchedAfterResponse() $this->assertFalse(UniqueJob::$ran); } + public function testQueueMayBeNullForJobQueueingAndJobQueuedEvent() + { + Config::set('queue.default', 'database'); + $events = []; + $this->app['events']->listen(function (JobQueueing $e) use (&$events) { + $events[] = $e; + }); + $this->app['events']->listen(function (JobQueued $e) use (&$events) { + $events[] = $e; + }); + + MyTestDispatchableJob::dispatch(); + dispatch(function () { + // + }); + + $this->assertCount(4, $events); + $this->assertInstanceOf(JobQueueing::class, $events[0]); + $this->assertNull($events[0]->queue); + $this->assertInstanceOf(JobQueued::class, $events[1]); + $this->assertNull($events[1]->queue); + $this->assertInstanceOf(JobQueueing::class, $events[2]); + $this->assertNull($events[2]->queue); + $this->assertInstanceOf(JobQueued::class, $events[3]); + $this->assertNull($events[3]->queue); + } + + public function testCanDisableDispatchingAfterResponse() + { + Job::dispatchAfterResponse('test'); + + $this->assertFalse(Job::$ran); + + $this->app->terminate(); + + $this->assertTrue(Job::$ran); + + Bus::withoutDispatchingAfterResponses(); + + Job::$ran = false; + Job::dispatchAfterResponse('test'); + + $this->assertTrue(Job::$ran); + + $this->app->terminate(); + + Bus::withDispatchingAfterResponses(); + + Job::$ran = false; + Job::dispatchAfterResponse('test'); + + $this->assertFalse(Job::$ran); + + $this->app->terminate(); + + $this->assertTrue(Job::$ran); + } + /** * Helpers. */ @@ -178,3 +240,8 @@ public function uniqueId() return self::$value; } } + +class MyTestDispatchableJob implements ShouldQueue +{ + use Dispatchable; +} diff --git a/tests/Integration/Queue/ModelSerializationTest.php b/tests/Integration/Queue/ModelSerializationTest.php index bd3b401c6576..e13cbb4ec293 100644 --- a/tests/Integration/Queue/ModelSerializationTest.php +++ b/tests/Integration/Queue/ModelSerializationTest.php @@ -31,6 +31,8 @@ protected function setUp(): void { parent::setUp(); + Model::preventLazyLoading(false); + Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('email'); @@ -161,6 +163,28 @@ public function testItReloadsRelationships() $this->assertEquals($unSerialized->order->getRelations(), $order->getRelations()); } + public function testItReloadsRelationshipsOnlyOnce() + { + $order = tap(ModelSerializationTestCustomOrder::create(), function (ModelSerializationTestCustomOrder $order) { + $order->wasRecentlyCreated = false; + }); + + $product1 = Product::create(); + $product2 = Product::create(); + + Line::create(['order_id' => $order->id, 'product_id' => $product1->id]); + Line::create(['order_id' => $order->id, 'product_id' => $product2->id]); + + $order->load('line', 'lines', 'products'); + + $this->expectsDatabaseQueryCount(4); + + $serialized = serialize(new ModelRelationSerializationTestClass($order)); + $unSerialized = unserialize($serialized); + + $this->assertEquals($unSerialized->order->getRelations(), $order->getRelations()); + } + public function testItReloadsNestedRelationships() { $order = tap(Order::create(), function (Order $order) { @@ -433,6 +457,29 @@ public function newCollection(array $models = []) } } +class ModelSerializationTestCustomOrder extends Model +{ + public $table = 'orders'; + public $guarded = []; + public $timestamps = false; + public $with = ['line', 'lines', 'products']; + + public function line() + { + return $this->hasOne(Line::class, 'order_id'); + } + + public function lines() + { + return $this->hasMany(Line::class, 'order_id'); + } + + public function products() + { + return $this->belongsToMany(Product::class, 'lines', 'order_id'); + } +} + class Order extends Model { public $guarded = []; diff --git a/tests/Integration/Queue/QueueConnectionTest.php b/tests/Integration/Queue/QueueConnectionTest.php index 7ad7722499a4..35f3596df26b 100644 --- a/tests/Integration/Queue/QueueConnectionTest.php +++ b/tests/Integration/Queue/QueueConnectionTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Integration\Queue; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Database\DatabaseTransactionsManager; use Illuminate\Foundation\Bus\Dispatchable; @@ -19,6 +20,7 @@ class QueueConnectionTest extends TestCase protected function tearDown(): void { QueueConnectionTestJob::$ran = false; + QueueConnectionTestUniqueJob::$ran = false; parent::tearDown(); } @@ -28,6 +30,7 @@ public function testJobWontGetDispatchedInsideATransaction() $this->app->singleton('db.transactions', function () { $transactionManager = m::mock(DatabaseTransactionsManager::class); $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback'); return $transactionManager; }); @@ -40,6 +43,7 @@ public function testJobWillGetDispatchedInsideATransactionWhenExplicitlyIndicate $this->app->singleton('db.transactions', function () { $transactionManager = m::mock(DatabaseTransactionsManager::class); $transactionManager->shouldNotReceive('addCallback')->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback'); return $transactionManager; }); @@ -58,6 +62,7 @@ public function testJobWontGetDispatchedInsideATransactionWhenExplicitlyIndicate $this->app->singleton('db.transactions', function () { $transactionManager = m::mock(DatabaseTransactionsManager::class); $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback'); return $transactionManager; }); @@ -68,6 +73,55 @@ public function testJobWontGetDispatchedInsideATransactionWhenExplicitlyIndicate // This job was dispatched } } + + public function testUniqueJobWontGetDispatchedInsideATransaction() + { + $this->app->singleton('db.transactions', function () { + $transactionManager = m::mock(DatabaseTransactionsManager::class); + $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldReceive('addCallbackForRollback')->once()->andReturn(null); + + return $transactionManager; + }); + + Bus::dispatch(new QueueConnectionTestUniqueJob); + } + + public function testUniqueJobWillGetDispatchedInsideATransactionWhenExplicitlyIndicated() + { + $this->app->singleton('db.transactions', function () { + $transactionManager = m::mock(DatabaseTransactionsManager::class); + $transactionManager->shouldNotReceive('addCallback')->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback')->andReturn(null); + + return $transactionManager; + }); + + try { + Bus::dispatch((new QueueConnectionTestUniqueJob)->beforeCommit()); + } catch (Throwable) { + // This job was dispatched + } + } + + public function testUniqueJobWontGetDispatchedInsideATransactionWhenExplicitlyIndicated() + { + $this->app['config']->set('queue.connections.sqs.after_commit', false); + + $this->app->singleton('db.transactions', function () { + $transactionManager = m::mock(DatabaseTransactionsManager::class); + $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldReceive('addCallbackForRollback')->once()->andReturn(null); + + return $transactionManager; + }); + + try { + Bus::dispatch((new QueueConnectionTestUniqueJob)->afterCommit()); + } catch (SqsException) { + // This job was dispatched + } + } } class QueueConnectionTestJob implements ShouldQueue @@ -81,3 +135,15 @@ public function handle() static::$ran = true; } } + +class QueueConnectionTestUniqueJob implements ShouldQueue, ShouldBeUnique +{ + use Dispatchable, Queueable; + + public static $ran = false; + + public function handle() + { + static::$ran = true; + } +} diff --git a/tests/Integration/Queue/QueuedListenersTest.php b/tests/Integration/Queue/QueuedListenersTest.php index 6268ac040549..56f242d3a809 100644 --- a/tests/Integration/Queue/QueuedListenersTest.php +++ b/tests/Integration/Queue/QueuedListenersTest.php @@ -25,15 +25,32 @@ public function testListenersCanBeQueuedOptionally() return $job->class == QueuedListenersTestListenerShouldQueue::class; }); + $this->assertCount(1, Queue::listenersPushed(QueuedListenersTestListenerShouldQueue::class)); + $this->assertCount( + 0, + Queue::listenersPushed( + QueuedListenersTestListenerShouldQueue::class, + fn ($event, $handler, $queue, $data) => $queue === 'not-a-real-queue' + ) + ); + $this->assertCount( + 1, + Queue::listenersPushed( + QueuedListenersTestListenerShouldQueue::class, + fn (QueuedListenersTestEvent $event) => $event->value === 100 + ) + ); + Queue::assertNotPushed(CallQueuedListener::class, function ($job) { return $job->class == QueuedListenersTestListenerShouldNotQueue::class; }); + $this->assertCount(0, Queue::listenersPushed(QueuedListenersTestListenerShouldNotQueue::class)); } } class QueuedListenersTestEvent { - // + public int $value = 100; } class QueuedListenersTestListenerShouldQueue implements ShouldQueue diff --git a/tests/Integration/Queue/RedisQueueTest.php b/tests/Integration/Queue/RedisQueueTest.php index 037dc69cc25d..fdb8b4608cd1 100644 --- a/tests/Integration/Queue/RedisQueueTest.php +++ b/tests/Integration/Queue/RedisQueueTest.php @@ -31,18 +31,19 @@ class RedisQueueTest extends TestCase */ private $container; + /** {@inheritdoc} */ + #[\Override] protected function setUp(): void { - parent::setUp(); - - $this->setUpRedis(); - } + $this->afterApplicationCreated(function () { + $this->setUpRedis(); + }); - protected function tearDown(): void - { - $this->tearDownRedis(); + $this->beforeApplicationDestroyed(function () { + $this->tearDownRedis(); + }); - parent::tearDown(); + parent::setUp(); } /** diff --git a/tests/Integration/Queue/WorkCommandTest.php b/tests/Integration/Queue/WorkCommandTest.php index 6d3796e01491..f1eb6b83134a 100644 --- a/tests/Integration/Queue/WorkCommandTest.php +++ b/tests/Integration/Queue/WorkCommandTest.php @@ -7,7 +7,6 @@ use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Testing\DatabaseMigrations; -use Illuminate\Queue\Console\WorkCommand; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Exceptions; @@ -34,13 +33,6 @@ protected function setUp(): void $this->markTestSkippedWhenUsingSyncQueueDriver(); } - protected function tearDown(): void - { - WorkCommand::flushState(); - - parent::tearDown(); - } - public function testRunningOneJob() { Queue::push(new FirstJob); diff --git a/tests/Integration/Routing/ImplicitModelRouteBindingTest.php b/tests/Integration/Routing/ImplicitModelRouteBindingTest.php index d2cf32096222..96dcadcae555 100644 --- a/tests/Integration/Routing/ImplicitModelRouteBindingTest.php +++ b/tests/Integration/Routing/ImplicitModelRouteBindingTest.php @@ -9,9 +9,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Concerns\InteractsWithPublishedFiles; use Orchestra\Testbench\TestCase; +#[WithConfig('app.key', 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF')] class ImplicitModelRouteBindingTest extends TestCase { use InteractsWithPublishedFiles; @@ -20,18 +22,6 @@ class ImplicitModelRouteBindingTest extends TestCase 'routes/testbench.php', ]; - protected function tearDown(): void - { - $this->tearDownInteractsWithPublishedFiles(); - - parent::tearDown(); - } - - protected function defineEnvironment($app): void - { - $app['config']->set(['app.key' => 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF']); - } - protected function defineDatabaseMigrations(): void { Schema::create('users', function (Blueprint $table) { diff --git a/tests/Integration/Routing/UrlSigningTest.php b/tests/Integration/Routing/UrlSigningTest.php index f836066d26f9..66b30417673f 100644 --- a/tests/Integration/Routing/UrlSigningTest.php +++ b/tests/Integration/Routing/UrlSigningTest.php @@ -14,13 +14,6 @@ class UrlSigningTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - Carbon::setTestNow(null); - } - protected function defineEnvironment($app): void { $app['config']->set(['app.key' => 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF']); diff --git a/tests/Integration/Session/DatabaseSessionHandlerTest.php b/tests/Integration/Session/DatabaseSessionHandlerTest.php new file mode 100644 index 000000000000..b6e5856d6067 --- /dev/null +++ b/tests/Integration/Session/DatabaseSessionHandlerTest.php @@ -0,0 +1,113 @@ +app['db']->connection(); + $handler = new DatabaseSessionHandler($connection, 'sessions', 1); + $handler->setContainer($this->app); + + // read non-existing session id: + $this->assertEquals('', $handler->read('invalid_session_id')); + + // open and close: + $this->assertTrue($handler->open('', '')); + $this->assertTrue($handler->close()); + + // write and read: + $this->assertTrue($handler->write('valid_session_id_2425', json_encode(['foo' => 'bar']))); + $this->assertEquals(['foo' => 'bar'], json_decode($handler->read('valid_session_id_2425'), true)); + $this->assertEquals(1, $connection->table('sessions')->count()); + + $session = $connection->table('sessions')->first(); + $this->assertNotNull($session->user_agent); + $this->assertNotNull($session->ip_address); + + // re-write and read: + $this->assertTrue($handler->write('valid_session_id_2425', json_encode(['over' => 'ride']))); + $this->assertEquals(['over' => 'ride'], json_decode($handler->read('valid_session_id_2425'), true)); + $this->assertEquals(1, $connection->table('sessions')->count()); + + // handler object writes only one session id: + $this->assertTrue($handler->write('other_id', 'data')); + $this->assertEquals(1, $connection->table('sessions')->count()); + + $handler->setExists(false); + $this->assertTrue($handler->write('other_id', 'data')); + $this->assertEquals(2, $connection->table('sessions')->count()); + + // read expired: + Carbon::setTestNow(Carbon::now()->addMinutes(2)); + $this->assertEquals('', $handler->read('valid_session_id_2425')); + + // rewriting an expired session-id, makes it live: + $this->assertTrue($handler->write('valid_session_id_2425', json_encode(['come' => 'alive']))); + $this->assertEquals(['come' => 'alive'], json_decode($handler->read('valid_session_id_2425'), true)); + } + + public function test_garbage_collector() + { + $connection = $this->app['db']->connection(); + + $handler = new DatabaseSessionHandler($connection, 'sessions', 1, $this->app); + $handler->write('simple_id_1', 'abcd'); + $this->assertEquals(0, $handler->gc(1)); + + Carbon::setTestNow(Carbon::now()->addSeconds(2)); + + $handler = new DatabaseSessionHandler($connection, 'sessions', 1, $this->app); + $handler->write('simple_id_2', 'abcd'); + $this->assertEquals(1, $handler->gc(2)); + $this->assertEquals(1, $connection->table('sessions')->count()); + + Carbon::setTestNow(Carbon::now()->addSeconds(2)); + + $this->assertEquals(1, $handler->gc(1)); + $this->assertEquals(0, $connection->table('sessions')->count()); + } + + public function test_destroy() + { + $connection = $this->app['db']->connection(); + $handler1 = new DatabaseSessionHandler($connection, 'sessions', 1, $this->app); + $handler2 = clone $handler1; + + $handler1->write('id_1', 'some data'); + $handler2->write('id_2', 'some data'); + + // destroy invalid session-id: + $this->assertEquals(true, $handler1->destroy('invalid_session_id')); + // nothing deleted: + $this->assertEquals(2, $connection->table('sessions')->count()); + + // destroy valid session-id: + $this->assertEquals(true, $handler2->destroy('id_1')); + // only one row is deleted: + $this->assertEquals(1, $connection->table('sessions')->where('id', 'id_2')->count()); + } + + public function test_it_can_work_without_container() + { + $connection = $this->app['db']->connection(); + $handler = new DatabaseSessionHandler($connection, 'sessions', 1); + + // write and read: + $this->assertTrue($handler->write('session_id', 'some data')); + $this->assertEquals('some data', $handler->read('session_id')); + $this->assertEquals(1, $connection->table('sessions')->count()); + + $session = $connection->table('sessions')->first(); + $this->assertNull($session->user_agent); + $this->assertNull($session->ip_address); + $this->assertNull($session->user_id); + } +} diff --git a/tests/Integration/Testing/ArtisanCommandTest.php b/tests/Integration/Testing/ArtisanCommandTest.php index fc26269e227f..d86467c396ce 100644 --- a/tests/Integration/Testing/ArtisanCommandTest.php +++ b/tests/Integration/Testing/ArtisanCommandTest.php @@ -55,6 +55,16 @@ protected function setUp(): void Artisan::command('contains', function () { $this->line('My name is Taylor Otwell'); }); + + Artisan::command('new-england', function () { + $this->line('The region of New England consists of the following states:'); + $this->info('Connecticut'); + $this->info('Maine'); + $this->info('Massachusetts'); + $this->info('New Hampshire'); + $this->info('Rhode Island'); + $this->info('Vermont'); + }); } public function test_console_command_that_passes() @@ -275,6 +285,27 @@ public function test_console_command_that_fails_if_the_output_does_not_contain() }); } + public function test_pending_command_can_be_tapped() + { + $newEngland = [ + 'Connecticut', + 'Maine', + 'Massachusetts', + 'New Hampshire', + 'Rhode Island', + 'Vermont', + ]; + + $this->artisan('new-england') + ->expectsOutput('The region of New England consists of the following states:') + ->tap(function ($command) use ($newEngland) { + foreach ($newEngland as $state) { + $command->expectsOutput($state); + } + }) + ->assertExitCode(0); + } + /** * Don't allow Mockery's InvalidCountException to be reported. Mocks setup * in PendingCommand cause PHPUnit tearDown() to later throw the exception. diff --git a/tests/Integration/View/BladeTest.php b/tests/Integration/View/BladeTest.php index 6495175337c4..1acb38372b86 100644 --- a/tests/Integration/View/BladeTest.php +++ b/tests/Integration/View/BladeTest.php @@ -11,8 +11,20 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; +use function Orchestra\Testbench\artisan; +use function Orchestra\Testbench\phpunit_version_compare; + class BladeTest extends TestCase { + /** {@inheritdoc} */ + #[\Override] + protected function tearDown(): void + { + artisan($this, 'view:clear'); + + parent::tearDown(); + } + public function test_rendering_blade_string() { $this->assertSame('Hello Taylor', Blade::render('Hello {{ $name }}', ['name' => 'Taylor'])); @@ -33,8 +45,8 @@ public function test_rendering_blade_long_maxpathlen_string_with_exact_length() // The PHP_MAXPATHLEN restriction is only active, if // open_basedir is set and active. Otherwise, the check // for the PHP_MAXPATHLEN is not active. - if (ini_get('open_basedir') === '') { - $openBaseDir = windows_os() ? explode('\\', __DIR__)[0].'\\'.';'.sys_get_temp_dir() : '/'; + if (ini_get('open_basedir') === '' && phpunit_version_compare('12.1.0', '<')) { + $openBaseDir = explode(DIRECTORY_SEPARATOR, __DIR__)[0].DIRECTORY_SEPARATOR.PATH_SEPARATOR.sys_get_temp_dir(); $iniSet = ini_set( 'open_basedir', $openBaseDir @@ -197,8 +209,6 @@ public function test_bound_name_attribute_can_be_used_if_using_short_slot_names_ public function testViewCacheCommandHandlesConfiguredBladeExtensions() { - $this->artisan('view:clear'); - View::addExtension('sh', 'blade'); $this->artisan('view:cache'); @@ -206,10 +216,10 @@ public function testViewCacheCommandHandlesConfiguredBladeExtensions() $found = collect($compiledFiles) ->contains(fn (SplFileInfo $file) => str_contains($file->getContents(), 'echo "" > output.log')); $this->assertTrue($found); - - $this->artisan('view:clear'); } + /** {@inheritdoc} */ + #[\Override] protected function defineEnvironment($app) { $app['config']->set('view.paths', [__DIR__.'/templates']); diff --git a/tests/Log/ContextTest.php b/tests/Log/ContextTest.php index ba2594342d3c..dfb76df9194b 100644 --- a/tests/Log/ContextTest.php +++ b/tests/Log/ContextTest.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Contracts\Debug\ExceptionHandler; +use Illuminate\Contracts\Log\ContextLogProcessor; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Log\Context\Events\ContextDehydrating as Dehydrating; @@ -13,6 +14,7 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; +use Monolog\LogRecord; use Orchestra\Testbench\TestCase; use RuntimeException; @@ -20,6 +22,13 @@ class ContextTest extends TestCase { use LazilyRefreshDatabase; + #[\Override] + protected function tearDown(): void + { + parent::tearDown(); + MyAddContextProcessor::$wasConstructed = false; + } + public function test_it_can_set_values() { $values = [ @@ -494,6 +503,137 @@ public function test_it_adds_context_to_logged_exceptions() file_put_contents($path, ''); Str::createUuidsNormally(); } + + public function test_scope_sets_keys_and_restores() + { + $contextInClosure = []; + $callback = function () use (&$contextInClosure) { + $contextInClosure = ['data' => Context::all(), 'hidden' => Context::allHidden()]; + + throw new Exception('test_with_sets_keys_and_restores'); + }; + + Context::add('key1', 'value1'); + Context::add('key2', 123); + Context::addHidden([ + 'hiddenKey1' => 'hello', + 'hiddenKey2' => 'world', + ]); + + try { + Context::scope( + $callback, + ['key1' => 'with', 'key3' => 'also-with'], + ['hiddenKey3' => 'foobar'], + ); + + $this->fail('No exception was thrown.'); + } catch (Exception) { + } + + $this->assertEqualsCanonicalizing([ + 'data' => [ + 'key1' => 'with', + 'key2' => 123, + 'key3' => 'also-with', + ], + 'hidden' => [ + 'hiddenKey1' => 'hello', + 'hiddenKey2' => 'world', + 'hiddenKey3' => 'foobar', + ], + ], $contextInClosure); + + $this->assertEqualsCanonicalizing([ + 'key1' => 'value1', + 'key2' => 123, + ], Context::all()); + $this->assertEqualsCanonicalizing([ + 'hiddenKey1' => 'hello', + 'hiddenKey2' => 'world', + ], Context::allHidden()); + } + + public function test_uses_closure_for_context_processor() + { + $path = storage_path('logs/laravel.log'); + file_put_contents($path, ''); + + $this->app->bind( + ContextLogProcessor::class, + fn () => function (LogRecord $record): LogRecord { + $logChannel = Context::getHidden('log_channel_name'); + + return $record->with( + // allow overriding the context from what's been set on the log + context: array_merge(Context::all(), $record->context), + // use the log channel we've set in context, or fallback to the current channel + channel: $logChannel ?? $record->channel, + ); + } + ); + + Context::addHidden('log_channel_name', 'closure-test'); + Context::add(['value_from_context' => 'hello']); + + Log::info('This is an info log.', ['value_from_log_info_context' => 'foo']); + + $log = Str::after(file_get_contents($path), '] '); + $this->assertSame('closure-test.INFO: This is an info log. {"value_from_context":"hello","value_from_log_info_context":"foo"}', Str::trim($log)); + file_put_contents($path, ''); + } + + public function test_can_rebind_to_separate_class() + { + $path = storage_path('logs/laravel.log'); + file_put_contents($path, ''); + + $this->app->bind(ContextLogProcessor::class, MyAddContextProcessor::class); + + Context::add(['this-will-be-included' => false]); + + Log::info('This is an info log.', ['value_from_log_info_context' => 'foo']); + $log = Str::after(file_get_contents($path), '] '); + $this->assertSame( + 'testing.INFO: This is an info log. {"value_from_log_info_context":"foo","inside of MyAddContextProcessor":true}', + Str::trim($log) + ); + $this->assertTrue(MyAddContextProcessor::$wasConstructed); + + file_put_contents($path, ''); + } + + public function test_it_increments_a_counter() + { + Context::increment('foo'); + $this->assertSame(1, Context::get('foo')); + + Context::increment('foo'); + $this->assertSame(2, Context::get('foo')); + } + + public function test_it_custom_increments_a_counter() + { + Context::increment('foo', 2); + $this->assertSame(2, Context::get('foo')); + + Context::increment('foo', 3); + $this->assertSame(5, Context::get('foo')); + } + + public function test_it_decrements_a_counter() + { + Context::increment('foo'); + Context::decrement('foo'); + $this->assertSame(0, Context::get('foo')); + } + + public function test_it_custom_decrements_a_counter() + { + Context::increment('foo', 2); + Context::decrement('foo', 2); + $this->assertSame(0, Context::get('foo')); + } } enum Suit @@ -516,3 +656,19 @@ class ContextModel extends Model { // } + +class MyAddContextProcessor implements ContextLogProcessor +{ + public static bool $wasConstructed = false; + + public function __construct() + { + self::$wasConstructed = true; + } + + #[\Override] + public function __invoke(LogRecord $record): LogRecord + { + return $record->with(context: array_merge($record->context, ['inside of MyAddContextProcessor' => true])); + } +} diff --git a/tests/Log/LogLoggerTest.php b/tests/Log/LogLoggerTest.php index 41163d6d25ee..939021d1214f 100755 --- a/tests/Log/LogLoggerTest.php +++ b/tests/Log/LogLoggerTest.php @@ -47,6 +47,17 @@ public function testContextIsFlushed() $writer->error('foo'); } + public function testContextKeysCanBeRemovedForSubsequentLogs() + { + $writer = new Logger($monolog = m::mock(Monolog::class)); + $writer->withContext(['bar' => 'baz', 'forget' => 'me']); + $writer->withoutContext(['forget']); + + $monolog->shouldReceive('error')->once()->with('foo', ['bar' => 'baz']); + + $writer->error('foo'); + } + public function testLoggerFiresEventsDispatcher() { $writer = new Logger($monolog = m::mock(Monolog::class), $events = new Dispatcher); @@ -92,4 +103,21 @@ public function testListenShortcut() $writer->listen($callback); } + + public function testComplexContextManipulation() + { + $writer = new Logger($monolog = m::mock(Monolog::class)); + + $writer->withContext(['user_id' => 123, 'action' => 'login']); + $writer->withContext(['ip' => '127.0.0.1', 'timestamp' => '1986-10-29']); + $writer->withoutContext(['timestamp']); + + $monolog->shouldReceive('info')->once()->with('User action', [ + 'user_id' => 123, + 'action' => 'login', + 'ip' => '127.0.0.1', + ]); + + $writer->info('User action'); + } } diff --git a/tests/Mail/MailManagerTest.php b/tests/Mail/MailManagerTest.php index 43d974a2e254..0a77128c650c 100644 --- a/tests/Mail/MailManagerTest.php +++ b/tests/Mail/MailManagerTest.php @@ -50,11 +50,7 @@ public function testMailUrlConfig($scheme, $port) $this->assertSame('127.0.0.2', $transport->getStream()->getHost()); $this->assertSame($port, $transport->getStream()->getPort()); $this->assertSame($port === 465, $transport->getStream()->isTLS()); - - if (method_exists($transport, 'isAutoTls')) { - // Only available from Symfony Mailer 7.1 - $this->assertTrue($transport->isAutoTls()); - } + $this->assertTrue($transport->isAutoTls()); } #[TestWith([null, 5876])] @@ -79,11 +75,7 @@ public function testMailUrlConfigWithAutoTls($scheme, $port) $this->assertSame('127.0.0.2', $transport->getStream()->getHost()); $this->assertSame($port, $transport->getStream()->getPort()); $this->assertSame($port === 465, $transport->getStream()->isTLS()); - - if (method_exists($transport, 'isAutoTls')) { - // Only available from Symfony Mailer 7.1 - $this->assertTrue($transport->isAutoTls()); - } + $this->assertTrue($transport->isAutoTls()); } #[TestWith([null, 5876])] @@ -107,14 +99,8 @@ public function testMailUrlConfigWithAutoTlsDisabled($scheme, $port) $this->assertSame('pwd', $transport->getPassword()); $this->assertSame('127.0.0.2', $transport->getStream()->getHost()); $this->assertSame($port, $transport->getStream()->getPort()); - - if (method_exists($transport, 'isAutoTls')) { - // Only available from Symfony Mailer 7.1 - $this->assertFalse($transport->isAutoTls()); - $this->assertSame($port === 465 && $scheme !== 'smtp', $transport->getStream()->isTLS()); - } else { - $this->assertSame($port === 465, $transport->getStream()->isTLS()); - } + $this->assertFalse($transport->isAutoTls()); + $this->assertSame($port === 465 && $scheme !== 'smtp', $transport->getStream()->isTLS()); } public function testBuild() diff --git a/tests/Mail/MailRoundRobinTransportTest.php b/tests/Mail/MailRoundRobinTransportTest.php new file mode 100644 index 000000000000..5b45f720faf0 --- /dev/null +++ b/tests/Mail/MailRoundRobinTransportTest.php @@ -0,0 +1,51 @@ +app['config']->set('mail.default', 'roundrobin'); + + $this->app['config']->set('mail.mailers', [ + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'sendmail', + 'array', + ], + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => '/usr/sbin/sendmail -bs', + ], + + 'array' => [ + 'transport' => 'array', + ], + ]); + + $transport = app('mailer')->getSymfonyTransport(); + $this->assertInstanceOf(RoundRobinTransport::class, $transport); + } + + public function testGetRoundRobinTransportWithLaravel6StyleMailConfiguration() + { + $this->app['config']->set('mail.driver', 'roundrobin'); + + $this->app['config']->set('mail.mailers', [ + 'sendmail', + 'array', + ]); + + $this->app['config']->set('mail.sendmail', '/usr/sbin/sendmail -bs'); + + $transport = app('mailer')->getSymfonyTransport(); + $this->assertInstanceOf(RoundRobinTransport::class, $transport); + } +} diff --git a/tests/Notifications/NotificationChannelManagerTest.php b/tests/Notifications/NotificationChannelManagerTest.php index efcce081aeee..4b272db8399f 100644 --- a/tests/Notifications/NotificationChannelManagerTest.php +++ b/tests/Notifications/NotificationChannelManagerTest.php @@ -2,17 +2,20 @@ namespace Illuminate\Tests\Notifications; +use Exception; use Illuminate\Bus\Queueable; use Illuminate\Container\Container; use Illuminate\Contracts\Bus\Dispatcher as Bus; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\ChannelManager; +use Illuminate\Notifications\Events\NotificationFailed; use Illuminate\Notifications\Events\NotificationSending; use Illuminate\Notifications\Events\NotificationSent; use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; use Illuminate\Notifications\SendQueuedNotifications; +use Illuminate\Support\Collection; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -34,6 +37,7 @@ public function testNotificationCanBeDispatchedToDriver() Container::setInstance($container); $manager = m::mock(ChannelManager::class.'[driver]', [$container]); $manager->shouldReceive('driver')->andReturn($driver = m::mock()); + $events->shouldReceive('listen')->once(); $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); $driver->shouldReceive('send')->once(); $events->shouldReceive('dispatch')->with(m::type(NotificationSent::class)); @@ -49,6 +53,7 @@ public function testNotificationNotSentOnHalt() $container->instance(Dispatcher::class, $events = m::mock()); Container::setInstance($container); $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $events->shouldReceive('listen')->once(); $events->shouldReceive('until')->once()->with(m::type(NotificationSending::class))->andReturn(false); $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); $manager->shouldReceive('driver')->once()->andReturn($driver = m::mock()); @@ -66,6 +71,7 @@ public function testNotificationNotSentWhenCancelled() $container->instance(Dispatcher::class, $events = m::mock()); Container::setInstance($container); $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $events->shouldReceive('listen')->once(); $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); $manager->shouldNotReceive('driver'); $events->shouldNotReceive('dispatch'); @@ -81,6 +87,7 @@ public function testNotificationSentWhenNotCancelled() $container->instance(Dispatcher::class, $events = m::mock()); Container::setInstance($container); $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $events->shouldReceive('listen')->once(); $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); $manager->shouldReceive('driver')->once()->andReturn($driver = m::mock()); $driver->shouldReceive('send')->once(); @@ -89,6 +96,56 @@ public function testNotificationSentWhenNotCancelled() $manager->send([new NotificationChannelManagerTestNotifiable], new NotificationChannelManagerTestNotCancelledNotification); } + public function testNotificationNotSentWhenFailed() + { + $this->expectException(Exception::class); + + $container = new Container; + $container->instance('config', ['app.name' => 'Name', 'app.logo' => 'Logo']); + $container->instance(Bus::class, $bus = m::mock()); + $container->instance(Dispatcher::class, $events = m::mock()); + Container::setInstance($container); + $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $manager->shouldReceive('driver')->andReturn($driver = m::mock()); + $driver->shouldReceive('send')->andThrow(new Exception()); + $events->shouldReceive('listen')->once(); + $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); + $events->shouldReceive('dispatch')->once()->with(m::type(NotificationFailed::class)); + $events->shouldReceive('dispatch')->never()->with(m::type(NotificationSent::class)); + + $manager->send(new NotificationChannelManagerTestNotifiable, new NotificationChannelManagerTestNotification); + } + + public function testNotificationFailedDispatchedOnlyOnceWhenFailed() + { + $this->expectException(Exception::class); + + $container = new Container; + $container->instance('config', ['app.name' => 'Name', 'app.logo' => 'Logo']); + $container->instance(Bus::class, $bus = m::mock()); + $container->instance(Dispatcher::class, $events = m::mock(Dispatcher::class)); + Container::setInstance($container); + $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $manager->shouldReceive('driver')->andReturn($driver = m::mock()); + $driver->shouldReceive('send')->andReturnUsing(function ($notifiable, $notification) use ($events) { + $events->dispatch(new NotificationFailed($notifiable, $notification, 'test')); + throw new Exception(); + }); + $listeners = new Collection(); + $events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true); + $events->shouldReceive('listen')->once()->andReturnUsing(function ($event, $callback) use ($listeners) { + $listeners->push($callback); + }); + $events->shouldReceive('dispatch')->once()->with(m::type(NotificationFailed::class))->andReturnUsing(function ($event) use ($listeners) { + foreach ($listeners as $listener) { + $listener($event); + } + }); + $events->shouldReceive('dispatch')->never()->with(m::type(NotificationSent::class)); + + $manager->send(new NotificationChannelManagerTestNotifiable, new NotificationChannelManagerTestNotification); + } + public function testNotificationCanBeQueued() { $container = new Container; @@ -98,6 +155,7 @@ public function testNotificationCanBeQueued() $bus->shouldReceive('dispatch')->with(m::type(SendQueuedNotifications::class)); Container::setInstance($container); $manager = m::mock(ChannelManager::class.'[driver]', [$container]); + $events->shouldReceive('listen')->once(); $manager->send([new NotificationChannelManagerTestNotifiable], new NotificationChannelManagerTestQueuedNotification); } diff --git a/tests/Notifications/NotificationMailMessageTest.php b/tests/Notifications/NotificationMailMessageTest.php index b901f0aab297..794a1ad3670d 100644 --- a/tests/Notifications/NotificationMailMessageTest.php +++ b/tests/Notifications/NotificationMailMessageTest.php @@ -83,6 +83,16 @@ public function testCcIsSetCorrectly() $message->cc(['test@example.com', 'Test' => 'test@example.com']); $this->assertSame([['test@example.com', null], ['test@example.com', 'Test']], $message->cc); + + $message = new MailMessage; + $message->cc('test@example.com', 'Test') + ->cc(['test@example.com', 'test2@example.com']); + + $this->assertSame([ + ['test@example.com', 'Test'], + ['test@example.com', null], + ['test2@example.com', null], + ], $message->cc); } public function testBccIsSetCorrectly() @@ -102,6 +112,16 @@ public function testBccIsSetCorrectly() $message->bcc(['test@example.com', 'Test' => 'test@example.com']); $this->assertSame([['test@example.com', null], ['test@example.com', 'Test']], $message->bcc); + + $message = new MailMessage; + $message->bcc('test@example.com', 'Test') + ->bcc(['test@example.com', 'test2@example.com']); + + $this->assertSame([ + ['test@example.com', 'Test'], + ['test@example.com', null], + ['test2@example.com', null], + ], $message->bcc); } public function testReplyToIsSetCorrectly() @@ -121,6 +141,16 @@ public function testReplyToIsSetCorrectly() $message->replyTo(['test@example.com', 'Test' => 'test@example.com']); $this->assertSame([['test@example.com', null], ['test@example.com', 'Test']], $message->replyTo); + + $message = new MailMessage; + $message->replyTo('test@example.com', 'Test') + ->replyTo(['test@example.com', 'test2@example.com']); + + $this->assertSame([ + ['test@example.com', 'Test'], + ['test@example.com', null], + ['test2@example.com', null], + ], $message->replyTo); } public function testMetadataIsSetCorrectly() diff --git a/tests/Notifications/NotificationSenderTest.php b/tests/Notifications/NotificationSenderTest.php index 4770fc96cd17..6e3a9954938c 100644 --- a/tests/Notifications/NotificationSenderTest.php +++ b/tests/Notifications/NotificationSenderTest.php @@ -30,6 +30,7 @@ public function testItCanSendQueuedNotificationsWithAStringVia() $bus = m::mock(BusDispatcher::class); $bus->shouldReceive('dispatch'); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); @@ -43,6 +44,7 @@ public function testItCanSendNotificationsWithAnEmptyStringVia() $bus = m::mock(BusDispatcher::class); $bus->shouldNotReceive('dispatch'); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); @@ -56,6 +58,7 @@ public function testItCannotSendNotificationsViaDatabaseForAnonymousNotifiables( $bus = m::mock(BusDispatcher::class); $bus->shouldNotReceive('dispatch'); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); @@ -72,6 +75,7 @@ public function testItCanSendQueuedNotificationsThroughMiddleware() return $job->middleware[0] instanceof TestNotificationMiddleware; }); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); @@ -99,6 +103,7 @@ public function testItCanSendQueuedMultiChannelNotificationsThroughDifferentMidd return empty($job->middleware); }); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); @@ -122,6 +127,7 @@ public function testItCanSendQueuedWithViaConnectionsNotifications() }); $events = m::mock(EventDispatcher::class); + $events->shouldReceive('listen')->once(); $sender = new NotificationSender($manager, $bus, $events); diff --git a/tests/Pagination/CursorPaginatorLoadMorphCountTest.php b/tests/Pagination/CursorPaginatorLoadMorphCountTest.php index 4756ceb26662..71fbc565e444 100644 --- a/tests/Pagination/CursorPaginatorLoadMorphCountTest.php +++ b/tests/Pagination/CursorPaginatorLoadMorphCountTest.php @@ -19,7 +19,8 @@ public function testCollectionLoadMorphCountCanChainOnThePaginator() $items = m::mock(Collection::class); $items->shouldReceive('loadMorphCount')->once()->with('parentable', $relations); - $p = (new class extends AbstractCursorPaginator {})->setCollection($items); + $p = (new class extends AbstractCursorPaginator { + })->setCollection($items); $this->assertSame($p, $p->loadMorphCount('parentable', $relations)); } diff --git a/tests/Pagination/CursorPaginatorLoadMorphTest.php b/tests/Pagination/CursorPaginatorLoadMorphTest.php index 217a8ccf93ed..fe1aa8faa5f7 100644 --- a/tests/Pagination/CursorPaginatorLoadMorphTest.php +++ b/tests/Pagination/CursorPaginatorLoadMorphTest.php @@ -19,7 +19,8 @@ public function testCollectionLoadMorphCanChainOnThePaginator() $items = m::mock(Collection::class); $items->shouldReceive('loadMorph')->once()->with('parentable', $relations); - $p = (new class extends AbstractCursorPaginator {})->setCollection($items); + $p = (new class extends AbstractCursorPaginator { + })->setCollection($items); $this->assertSame($p, $p->loadMorph('parentable', $relations)); } diff --git a/tests/Pagination/CursorPaginatorTest.php b/tests/Pagination/CursorPaginatorTest.php index ce32537c1670..11cfb4c009a7 100644 --- a/tests/Pagination/CursorPaginatorTest.php +++ b/tests/Pagination/CursorPaginatorTest.php @@ -101,7 +101,7 @@ public function testReturnEmptyCursorWhenItemsAreEmpty() { $cursor = new Cursor(['id' => 25], true); - $p = new CursorPaginator(Collection::make(), 25, $cursor, [ + $p = new CursorPaginator(new Collection, 25, $cursor, [ 'path' => 'http://website.com/test', 'cursorName' => 'cursor', 'parameters' => ['id'], diff --git a/tests/Pagination/Fixtures/Models/PaginatorResourceTestModel.php b/tests/Pagination/Fixtures/Models/PaginatorResourceTestModel.php new file mode 100644 index 000000000000..44c65629a175 --- /dev/null +++ b/tests/Pagination/Fixtures/Models/PaginatorResourceTestModel.php @@ -0,0 +1,10 @@ +shouldReceive('loadMorphCount')->once()->with('parentable', $relations); - $p = (new class extends AbstractPaginator {})->setCollection($items); + $p = (new class extends AbstractPaginator { + })->setCollection($items); $this->assertSame($p, $p->loadMorphCount('parentable', $relations)); } diff --git a/tests/Pagination/PaginatorLoadMorphTest.php b/tests/Pagination/PaginatorLoadMorphTest.php index c7b4c1287ad0..c3ab5a5bf902 100644 --- a/tests/Pagination/PaginatorLoadMorphTest.php +++ b/tests/Pagination/PaginatorLoadMorphTest.php @@ -19,7 +19,8 @@ public function testCollectionLoadMorphCanChainOnThePaginator() $items = m::mock(Collection::class); $items->shouldReceive('loadMorph')->once()->with('parentable', $relations); - $p = (new class extends AbstractPaginator {})->setCollection($items); + $p = (new class extends AbstractPaginator { + })->setCollection($items); $this->assertSame($p, $p->loadMorph('parentable', $relations)); } diff --git a/tests/Pagination/PaginatorResourceTest.php b/tests/Pagination/PaginatorResourceTest.php new file mode 100644 index 000000000000..6bd0c5e04d3c --- /dev/null +++ b/tests/Pagination/PaginatorResourceTest.php @@ -0,0 +1,57 @@ +toResourceCollection(PaginatorResourceTestResource::class); + + $this->assertInstanceOf(JsonResource::class, $resource); + } + + public function testItThrowsExceptionWhenResourceCannotBeFound() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Failed to find resource class for model [Illuminate\Tests\Pagination\Fixtures\Models\PaginatorResourceTestModel].'); + + $paginator = new PaginatorResourceTestPaginator([ + new PaginatorResourceTestModel(), + ], 1, 1, 1); + + $paginator->toResourceCollection(); + } + + public function testItCanGuessResourceWhenNotProvided() + { + $paginator = new PaginatorResourceTestPaginator([ + new PaginatorResourceTestModel(), + ], 1, 1, 1); + + class_alias(PaginatorResourceTestResource::class, 'Illuminate\Tests\Pagination\Fixtures\Http\Resources\PaginatorResourceTestModelResource'); + + $resource = $paginator->toResourceCollection(); + + $this->assertInstanceOf(JsonResource::class, $resource); + } +} + +class PaginatorResourceTestResource extends JsonResource +{ + // +} + +class PaginatorResourceTestPaginator extends LengthAwarePaginator +{ + // +} diff --git a/tests/Pagination/UrlWindowTest.php b/tests/Pagination/UrlWindowTest.php index 420fa7135151..0ed0d43a9657 100644 --- a/tests/Pagination/UrlWindowTest.php +++ b/tests/Pagination/UrlWindowTest.php @@ -37,9 +37,7 @@ public function testPresenterCanGetAUrlRangeForAWindowOfLinks() $this->assertEquals(['first' => [1 => '/?page=1', 2 => '/?page=2'], 'slider' => $slider, 'last' => [19 => '/?page=19', 20 => '/?page=20']], $window->get()); - /* - * Test Being Near The End Of The List - */ + // Test Being Near The End Of The List $array = []; for ($i = 1; $i <= 20; $i++) { $array[$i] = 'item'.$i; diff --git a/tests/Process/ProcessTest.php b/tests/Process/ProcessTest.php index 16ed54aa934d..b3fd5903a699 100644 --- a/tests/Process/ProcessTest.php +++ b/tests/Process/ProcessTest.php @@ -181,7 +181,7 @@ public function testBasicProcessFakeWithMultiLineCommand() $factory->preventStrayProcesses(); $factory->fake([ - '*' => 'The output', + '*' => $expectedOutput = 'The output', ]); $result = $factory->run(<<<'COMMAND' @@ -192,6 +192,29 @@ public function testBasicProcessFakeWithMultiLineCommand() COMMAND); $this->assertSame(0, $result->exitCode()); + $this->assertSame("$expectedOutput\n", $result->output()); + } + + public function testProcessFakeWithMultiLineCommand() + { + $factory = new Factory; + + $factory->preventStrayProcesses(); + + $factory->fake([ + '*--branch main*' => 'not this one', + '*--branch develop*' => $expectedOutput = 'yes thank you', + ]); + + $result = $factory->run(<<<'COMMAND' + git clone --depth 1 \ + --single-branch \ + --branch develop \ + git://some-url . + COMMAND); + + $this->assertSame(0, $result->exitCode()); + $this->assertSame("$expectedOutput\n", $result->output()); } public function testProcessFakeExitCodes() @@ -772,6 +795,37 @@ public function testAssertingThatNothingRan() $factory->assertNothingRan(); } + public function testProcessWithMultipleEnvironmentVariablesAndSequences() + { + $factory = new Factory; + + $factory->fake([ + 'printenv TEST_VAR OTHER_VAR' => $factory->sequence() + ->push("test_value\nother_value") + ->push("new_test_value\nnew_other_value"), + ]); + + $result = $factory->env([ + 'TEST_VAR' => 'test_value', + 'OTHER_VAR' => 'other_value', + ])->run('printenv TEST_VAR OTHER_VAR'); + + $this->assertTrue($result->successful()); + $this->assertEquals("test_value\nother_value\n", $result->output()); + + $result = $factory->env([ + 'TEST_VAR' => 'new_test_value', + 'OTHER_VAR' => 'new_other_value', + ])->run('printenv TEST_VAR OTHER_VAR'); + + $this->assertTrue($result->successful()); + $this->assertEquals("new_test_value\nnew_other_value\n", $result->output()); + + $factory->assertRanTimes(function ($process) { + return str_contains($process->command, 'printenv TEST_VAR OTHER_VAR'); + }, 2); + } + protected function ls() { return windows_os() ? 'dir' : 'ls'; diff --git a/tests/Queue/DatabaseFailedJobProviderTest.php b/tests/Queue/DatabaseFailedJobProviderTest.php index 31d1714e2f65..89e38e499c4d 100644 --- a/tests/Queue/DatabaseFailedJobProviderTest.php +++ b/tests/Queue/DatabaseFailedJobProviderTest.php @@ -18,7 +18,7 @@ class DatabaseFailedJobProviderTest extends TestCase protected $provider; - public function setUp(): void + protected function setUp(): void { parent::setUp(); $this->createDatabaseWithFailedJobTable() diff --git a/tests/Queue/QueueBeanstalkdJobTest.php b/tests/Queue/QueueBeanstalkdJobTest.php index 514555001e67..1405cb3f0712 100755 --- a/tests/Queue/QueueBeanstalkdJobTest.php +++ b/tests/Queue/QueueBeanstalkdJobTest.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Queue\Events\JobFailed; use Illuminate\Queue\Jobs\BeanstalkdJob; +use Illuminate\Queue\Jobs\Job; use Mockery as m; use Pheanstalk\Contract\JobIdInterface; use Pheanstalk\Contract\PheanstalkManagerInterface; @@ -39,7 +40,7 @@ public function testFailProperlyCallsTheJobHandler() $job->getPheanstalkJob()->shouldReceive('getData')->andReturn(json_encode(['job' => 'foo', 'uuid' => 'test-uuid', 'data' => ['data']])); $job->getContainer()->shouldReceive('make')->once()->with('foo')->andReturn($handler = m::mock(BeanstalkdJobTestFailedTest::class)); $job->getPheanstalk()->shouldReceive('delete')->once()->with($job->getPheanstalkJob())->andReturnSelf(); - $handler->shouldReceive('failed')->once()->with(['data'], m::type(Exception::class), 'test-uuid'); + $handler->shouldReceive('failed')->once()->with(['data'], m::type(Exception::class), 'test-uuid', m::type(Job::class)); $job->getContainer()->shouldReceive('make')->once()->with(Dispatcher::class)->andReturn($events = m::mock(Dispatcher::class)); $events->shouldReceive('dispatch')->once()->with(m::type(JobFailed::class))->andReturnNull(); diff --git a/tests/Queue/QueueBeanstalkdQueueTest.php b/tests/Queue/QueueBeanstalkdQueueTest.php index ba34a7069305..dfa4eace4a16 100755 --- a/tests/Queue/QueueBeanstalkdQueueTest.php +++ b/tests/Queue/QueueBeanstalkdQueueTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Queue; +use Carbon\Carbon; use Illuminate\Container\Container; use Illuminate\Queue\BeanstalkdQueue; use Illuminate\Queue\Jobs\BeanstalkdJob; @@ -38,6 +39,9 @@ public function testPushProperlyPushesJobOntoBeanstalkd() { $uuid = Str::uuid(); + $time = Carbon::now(); + Carbon::setTestNow($time); + Str::createUuidsUsing(function () use ($uuid) { return $uuid; }); @@ -46,13 +50,14 @@ public function testPushProperlyPushesJobOntoBeanstalkd() $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->shouldReceive('useTube')->once()->with(m::type(TubeName::class)); $pheanstalk->shouldReceive('useTube')->once()->with(m::type(TubeName::class)); - $pheanstalk->shouldReceive('put')->twice()->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data']]), 1024, 0, 60); + $pheanstalk->shouldReceive('put')->twice()->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'delay' => null]), 1024, 0, 60); $this->queue->push('foo', ['data'], 'stack'); $this->queue->push('foo', ['data']); $this->container->shouldHaveReceived('bound')->with('events')->times(4); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -64,17 +69,21 @@ public function testDelayedPushProperlyPushesJobOntoBeanstalkd() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $this->setQueue('default', 60); $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->shouldReceive('useTube')->once()->with(m::type(TubeName::class)); $pheanstalk->shouldReceive('useTube')->once()->with(m::type(TubeName::class)); - $pheanstalk->shouldReceive('put')->twice()->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data']]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); + $pheanstalk->shouldReceive('put')->twice()->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'delay' => 5]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); $this->queue->later(5, 'foo', ['data'], 'stack'); $this->queue->later(5, 'foo', ['data']); $this->container->shouldHaveReceived('bound')->with('events')->times(4); + Carbon::setTestNow(); Str::createUuidsNormally(); } diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 3714b99a80b5..9702a4d6695e 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Queue; +use Carbon\Carbon; use Illuminate\Container\Container; use Illuminate\Database\Connection; use Illuminate\Queue\DatabaseQueue; @@ -69,6 +70,9 @@ public function testDelayedPushProperlyPushesJobOntoDatabase() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $queue = $this->getMockBuilder(DatabaseQueue::class) ->onlyMethods(['currentTime']) ->setConstructorArgs([$database = m::mock(Connection::class), 'table', 'default']) @@ -76,9 +80,9 @@ public function testDelayedPushProperlyPushesJobOntoDatabase() $queue->expects($this->any())->method('currentTime')->willReturn('time'); $queue->setContainer($container = m::spy(Container::class)); $database->shouldReceive('table')->with('table')->andReturn($query = m::mock(stdClass::class)); - $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) use ($uuid) { + $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) use ($uuid, $time) { $this->assertSame('default', $array['queue']); - $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data']]), $array['payload']); + $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'delay' => 10]), $array['payload']); $this->assertEquals(0, $array['attempts']); $this->assertNull($array['reserved_at']); $this->assertIsInt($array['available_at']); @@ -88,6 +92,7 @@ public function testDelayedPushProperlyPushesJobOntoDatabase() $container->shouldHaveReceived('bound')->with('events')->twice(); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -130,22 +135,25 @@ public function testBulkBatchPushesOntoDatabase() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $database = m::mock(Connection::class); $queue = $this->getMockBuilder(DatabaseQueue::class)->onlyMethods(['currentTime', 'availableAt'])->setConstructorArgs([$database, 'table', 'default'])->getMock(); $queue->expects($this->any())->method('currentTime')->willReturn('created'); $queue->expects($this->any())->method('availableAt')->willReturn('available'); $database->shouldReceive('table')->with('table')->andReturn($query = m::mock(stdClass::class)); - $query->shouldReceive('insert')->once()->andReturnUsing(function ($records) use ($uuid) { + $query->shouldReceive('insert')->once()->andReturnUsing(function ($records) use ($uuid, $time) { $this->assertEquals([[ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data']]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 'available', 'created_at' => 'created', ], [ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data']]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 'available', @@ -155,6 +163,7 @@ public function testBulkBatchPushesOntoDatabase() $queue->bulk(['foo', 'bar'], ['data'], 'queue'); + Carbon::setTestNow(); Str::createUuidsNormally(); } diff --git a/tests/Queue/QueueRedisQueueTest.php b/tests/Queue/QueueRedisQueueTest.php index 007f743653d8..7ea0bfdbe076 100644 --- a/tests/Queue/QueueRedisQueueTest.php +++ b/tests/Queue/QueueRedisQueueTest.php @@ -27,16 +27,20 @@ public function testPushProperlyPushesJobOntoRedis() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis = m::mock(Factory::class), 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $queue->setContainer($container = m::spy(Container::class)); $redis->shouldReceive('connection')->once()->andReturn($redis); - $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'id' => 'foo', 'attempts' => 0])); + $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])); $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -48,11 +52,14 @@ public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis = m::mock(Factory::class), 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $queue->setContainer($container = m::spy(Container::class)); $redis->shouldReceive('connection')->once()->andReturn($redis); - $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0])); + $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])); Queue::createPayloadUsing(function ($connection, $queue, $payload) { return ['custom' => 'taylor']; @@ -64,6 +71,7 @@ public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook() Queue::createPayloadUsing(null); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -75,11 +83,14 @@ public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['getRandomId'])->setConstructorArgs([$redis = m::mock(Factory::class), 'default'])->getMock(); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); $queue->setContainer($container = m::spy(Container::class)); $redis->shouldReceive('connection')->once()->andReturn($redis); - $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0])); + $redis->shouldReceive('eval')->once()->with(LuaScripts::push(), 2, 'queues:default', 'queues:default:notify', json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])); Queue::createPayloadUsing(function ($connection, $queue, $payload) { return ['custom' => 'taylor']; @@ -95,6 +106,7 @@ public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook() Queue::createPayloadUsing(null); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -106,6 +118,9 @@ public function testDelayedPushProperlyPushesJobOntoRedis() return $uuid; }); + $time = Carbon::now(); + Carbon::setTestNow($time); + $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['availableAt', 'getRandomId'])->setConstructorArgs([$redis = m::mock(Factory::class), 'default'])->getMock(); $queue->setContainer($container = m::spy(Container::class)); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); @@ -115,13 +130,14 @@ public function testDelayedPushProperlyPushesJobOntoRedis() $redis->shouldReceive('zadd')->once()->with( 'queues:default:delayed', 2, - json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'id' => 'foo', 'attempts' => 0]) + json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1]) ); $id = $queue->later(1, 'foo', ['data']); $this->assertSame('foo', $id); $container->shouldHaveReceived('bound')->with('events')->twice(); + Carbon::setTestNow(); Str::createUuidsNormally(); } @@ -133,22 +149,24 @@ public function testDelayedPushWithDateTimeProperlyPushesJobOntoRedis() return $uuid; }); - $date = Carbon::now(); + $time = $date = Carbon::now(); + Carbon::setTestNow($time); $queue = $this->getMockBuilder(RedisQueue::class)->onlyMethods(['availableAt', 'getRandomId'])->setConstructorArgs([$redis = m::mock(Factory::class), 'default'])->getMock(); $queue->setContainer($container = m::spy(Container::class)); $queue->expects($this->once())->method('getRandomId')->willReturn('foo'); - $queue->expects($this->once())->method('availableAt')->with($date)->willReturn(2); + $queue->expects($this->once())->method('availableAt')->with($date)->willReturn(5); $redis->shouldReceive('connection')->once()->andReturn($redis); $redis->shouldReceive('zadd')->once()->with( 'queues:default:delayed', - 2, - json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'id' => 'foo', 'attempts' => 0]) + 5, + json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $time->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 5]) ); - $queue->later($date, 'foo', ['data']); + $queue->later($date->addSeconds(5), 'foo', ['data']); $container->shouldHaveReceived('bound')->with('events')->twice(); + Carbon::setTestNow(); Str::createUuidsNormally(); } } diff --git a/tests/Queue/QueueSyncQueueTest.php b/tests/Queue/QueueSyncQueueTest.php index a361bf5e773b..901f66c2d75e 100755 --- a/tests/Queue/QueueSyncQueueTest.php +++ b/tests/Queue/QueueSyncQueueTest.php @@ -6,6 +6,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Queue\QueueableEntity; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; use Illuminate\Database\DatabaseTransactionsManager; @@ -60,6 +61,28 @@ public function testFailedJobGetsHandledWhenAnExceptionIsThrown() Container::setInstance(); } + public function testFailedJobHasAccessToJobInstance() + { + unset($_SERVER['__sync.failed']); + + $sync = new SyncQueue; + $container = new Container; + $container->bind(\Illuminate\Contracts\Events\Dispatcher::class, \Illuminate\Events\Dispatcher::class); + $container->bind(\Illuminate\Contracts\Bus\Dispatcher::class, \Illuminate\Bus\Dispatcher::class); + $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); + $sync->setContainer($container); + + SyncQueue::createPayloadUsing(function ($connection, $queue, $payload) { + return ['data' => ['extra' => 'extraValue']]; + }); + + try { + $sync->push(new FailingSyncQueueJob()); + } catch (LogicException $e) { + $this->assertSame('extraValue', $_SERVER['__sync.failed']); + } + } + public function testCreatesPayloadObject() { $sync = new SyncQueue; @@ -87,6 +110,7 @@ public function testItAddsATransactionCallbackForAfterCommitJobs() $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); $transactionManager = m::mock(DatabaseTransactionsManager::class); $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback'); $container->instance('db.transactions', $transactionManager); $sync->setContainer($container); @@ -100,11 +124,40 @@ public function testItAddsATransactionCallbackForInterfaceBasedAfterCommitJobs() $container->bind(\Illuminate\Contracts\Container\Container::class, \Illuminate\Container\Container::class); $transactionManager = m::mock(DatabaseTransactionsManager::class); $transactionManager->shouldReceive('addCallback')->once()->andReturn(null); + $transactionManager->shouldNotReceive('addCallbackForRollback'); $container->instance('db.transactions', $transactionManager); $sync->setContainer($container); $sync->push(new SyncQueueAfterCommitInterfaceJob()); } + + public function testItAddsATransactionCallbackForAfterCommitUniqueJobs() + { + $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); + $transactionManager->shouldReceive('addCallbackForRollback')->once()->andReturn(null); + $container->instance('db.transactions', $transactionManager); + + $sync->setContainer($container); + $sync->push(new SyncQueueAfterCommitUniqueJob()); + } + + public function testItAddsATransactionCallbackForInterfaceBasedAfterCommitUniqueJobs() + { + $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); + $transactionManager->shouldReceive('addCallbackForRollback')->once()->andReturn(null); + $container->instance('db.transactions', $transactionManager); + + $sync->setContainer($container); + $sync->push(new SyncQueueAfterCommitInterfaceUniqueJob()); + } } class SyncQueueTestEntity implements QueueableEntity @@ -146,6 +199,23 @@ public function failed() } } +class FailingSyncQueueJob implements ShouldQueue +{ + use InteractsWithQueue; + + public function handle() + { + throw new LogicException(); + } + + public function failed() + { + $payload = $this->job->payload(); + + $_SERVER['__sync.failed'] = $payload['data']['extra']; + } +} + class SyncQueueJob implements ShouldQueue { use InteractsWithQueue; @@ -182,3 +252,23 @@ public function handle() { } } + +class SyncQueueAfterCommitUniqueJob implements ShouldBeUnique +{ + use InteractsWithQueue; + + public $afterCommit = true; + + public function handle() + { + } +} + +class SyncQueueAfterCommitInterfaceUniqueJob implements ShouldBeUnique, ShouldQueueAfterCommit +{ + use InteractsWithQueue; + + public function handle() + { + } +} diff --git a/tests/Queue/QueueWorkerTest.php b/tests/Queue/QueueWorkerTest.php index 33aa4c8dc785..a9eebdeae4b4 100755 --- a/tests/Queue/QueueWorkerTest.php +++ b/tests/Queue/QueueWorkerTest.php @@ -100,6 +100,24 @@ public function testWorkerStopsWhenMemoryExceeded() $this->events->shouldHaveReceived('dispatch')->with(m::type(JobProcessed::class))->once(); } + public function testWorkerMemoryExceededWhenMemoryIsZero() + { + $worker = new Worker(...$this->workerDependencies()); + $this->assertFalse($worker->memoryExceeded(0)); + } + + public function testWorkerMemoryExceededWhenMemoryGreaterThanZero() + { + $worker = new Worker(...$this->workerDependencies()); + $this->assertTrue($worker->memoryExceeded(1)); + } + + public function testWorkerMemoryExceededWhenMemoryIsNegative() + { + $worker = new Worker(...$this->workerDependencies()); + $this->assertFalse($worker->memoryExceeded(-1)); + } + public function testJobCanBeFiredBasedOnPriority() { $worker = $this->getWorker('default', [ @@ -644,6 +662,11 @@ public function timeout() { return time() + 60; } + + public function resolveQueuedJobClass() + { + return 'WorkerFakeJob'; + } } class LoopBreakerException extends RuntimeException diff --git a/tests/Routing/RoutingRouteTest.php b/tests/Routing/RoutingRouteTest.php index 9caddf307069..c44c2fd69a80 100644 --- a/tests/Routing/RoutingRouteTest.php +++ b/tests/Routing/RoutingRouteTest.php @@ -781,9 +781,7 @@ public function testRouteDomainRegistration() public function testMatchesMethodAgainstRequests() { - /* - * Basic - */ + // Basic $request = Request::create('foo/bar', 'GET'); $route = new Route('GET', 'foo/{bar}', function () { // @@ -796,9 +794,7 @@ public function testMatchesMethodAgainstRequests() }); $this->assertFalse($route->matches($request)); - /* - * Method checks - */ + // Method checks $request = Request::create('foo/bar', 'GET'); $route = new Route('GET', 'foo/{bar}', function () { // @@ -811,9 +807,7 @@ public function testMatchesMethodAgainstRequests() }); $this->assertFalse($route->matches($request)); - /* - * Domain checks - */ + // Domain checks $request = Request::create('http://something.foo.com/foo/bar', 'GET'); $route = new Route('GET', 'foo/{bar}', ['domain' => '{foo}.foo.com', function () { // @@ -826,9 +820,7 @@ public function testMatchesMethodAgainstRequests() }]); $this->assertFalse($route->matches($request)); - /* - * HTTPS checks - */ + // HTTPS checks $request = Request::create('https://foo.com/foo/bar', 'GET'); $route = new Route('GET', 'foo/{bar}', ['https', function () { // @@ -847,9 +839,7 @@ public function testMatchesMethodAgainstRequests() }]); $this->assertFalse($route->matches($request)); - /* - * HTTP checks - */ + // HTTP checks $request = Request::create('https://foo.com/foo/bar', 'GET'); $route = new Route('GET', 'foo/{bar}', ['http', function () { // @@ -906,9 +896,7 @@ public function testWherePatternsProperlyFilter() $route->where('bar', '123|456'); $this->assertFalse($route->matches($request)); - /* - * Optional - */ + // Optional $request = Request::create('foo/123', 'GET'); $route = new Route('GET', 'foo/{bar?}', function () { // @@ -944,9 +932,7 @@ public function testWherePatternsProperlyFilter() $route->where('bar', '[0-9]+'); $this->assertFalse($route->matches($request)); - /* - * Conditional - */ + // Conditional $route = new Route('GET', '{subdomain}.awesome.test', function () { // }); @@ -1196,9 +1182,7 @@ public function testGroupMerging() public function testRouteGrouping() { - /* - * getPrefix() method - */ + // getPrefix() method $router = $this->getRouter(); $router->group(['prefix' => 'foo'], function () use ($router) { $router->get('bar', function () { @@ -1275,9 +1259,7 @@ public function testRouteGroupingWithAs() public function testNestedRouteGroupingWithAs() { - /* - * nested with all layers present - */ + // nested with all layers present $router = $this->getRouter(); $router->group(['prefix' => 'foo', 'as' => 'Foo::'], function () use ($router) { $router->group(['prefix' => 'bar', 'as' => 'Bar::'], function () use ($router) { @@ -1290,9 +1272,7 @@ public function testNestedRouteGroupingWithAs() $route = $routes->getByName('Foo::Bar::baz'); $this->assertSame('foo/bar/baz', $route->uri()); - /* - * nested with layer skipped - */ + // nested with layer skipped $router = $this->getRouter(); $router->group(['prefix' => 'foo', 'as' => 'Foo::'], function () use ($router) { $router->group(['prefix' => 'bar'], function () use ($router) { @@ -1308,9 +1288,7 @@ public function testNestedRouteGroupingWithAs() public function testNestedRouteGroupingPrefixing() { - /* - * nested with layer skipped - */ + // nested with layer skipped $router = $this->getRouter(); $router->group(['prefix' => 'foo', 'as' => 'Foo::'], function () use ($router) { $router->prefix('bar')->get('baz', ['as' => 'baz', function () { @@ -1340,9 +1318,7 @@ public function testRouteMiddlewareMergeWithMiddlewareAttributesAsStrings() public function testRoutePrefixing() { - /* - * Prefix route - */ + // Prefix route $router = $this->getRouter(); $router->get('foo/bar', function () { return 'hello'; @@ -1352,9 +1328,7 @@ public function testRoutePrefixing() $routes[0]->prefix('prefix'); $this->assertSame('prefix/foo/bar', $routes[0]->uri()); - /* - * Use empty prefix - */ + // Use empty prefix $router = $this->getRouter(); $router->get('foo/bar', function () { return 'hello'; @@ -1364,9 +1338,7 @@ public function testRoutePrefixing() $routes[0]->prefix('/'); $this->assertSame('foo/bar', $routes[0]->uri()); - /* - * Prefix homepage - */ + // Prefix homepage $router = $this->getRouter(); $router->get('/', function () { return 'hello'; @@ -1376,9 +1348,7 @@ public function testRoutePrefixing() $routes[0]->prefix('prefix'); $this->assertSame('prefix', $routes[0]->uri()); - /* - * Prefix homepage with empty prefix - */ + // Prefix homepage with empty prefix $router = $this->getRouter(); $router->get('/', function () { return 'hello'; diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index 73d72a17011d..f03e4b10bb8c 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -31,9 +31,7 @@ public function testBasicGeneration() $this->assertSame('https://www.foo.com/foo/bar/baz/boom', $url->to('foo/bar', ['baz', 'boom'], true)); $this->assertSame('https://www.foo.com/foo/bar/baz?foo=bar', $url->to('foo/bar?foo=bar', ['baz'], true)); - /* - * Test HTTPS request URL generation... - */ + // Test HTTPS request URL generation... $url = new UrlGenerator( new RouteCollection, Request::create('https://www.foo.com/') @@ -204,78 +202,54 @@ public function testBasicRouteGeneration() Request::create('http://www.foo.com/') ); - /* - * Empty Named Route - */ + // Empty Named Route $route = new Route(['GET'], '/', ['as' => 'plain']); $routes->add($route); - /* - * Named Routes - */ + // Named Routes $route = new Route(['GET'], 'foo/bar', ['as' => 'foo']); $routes->add($route); - /* - * Parameters... - */ + // Parameters... $route = new Route(['GET'], 'foo/bar/{baz}/breeze/{boom}', ['as' => 'bar']); $routes->add($route); - /* - * Single Parameter... - */ + // Single Parameter... $route = new Route(['GET'], 'foo/bar/{baz}', ['as' => 'foobar']); $routes->add($route); - /* - * Optional parameter - */ + // Optional parameter $route = new Route(['GET'], 'foo/bar/{baz?}', ['as' => 'optional']); $routes->add($route); - /* - * HTTPS... - */ + // HTTPS... $route = new Route(['GET'], 'foo/baz', ['as' => 'baz', 'https']); $routes->add($route); - /* - * Controller Route Route - */ + // Controller Route Route $route = new Route(['GET'], 'foo/bam', ['controller' => 'foo@bar']); $routes->add($route); - /* - * Non ASCII routes - */ + // Non ASCII routes $route = new Route(['GET'], 'foo/bar/åαф/{baz}', ['as' => 'foobarbaz']); $routes->add($route); - /* - * Fragments - */ + // Fragments $route = new Route(['GET'], 'foo/bar#derp', ['as' => 'fragment']); $routes->add($route); - /* - * Invoke action - */ + // Invoke action $route = new Route(['GET'], 'foo/invoke', ['controller' => 'InvokableActionStub']); $routes->add($route); - /* - * With Default Parameter - */ + // With Default Parameter $url->defaults(['locale' => 'en']); $route = new Route(['GET'], 'foo', ['as' => 'defaults', 'domain' => '{locale}.example.com', function () { // }]); $routes->add($route); - /* - * With backed enum name and domain - */ + // With backed enum name and domain $route = (new Route(['GET'], 'backed-enum', ['as' => 'prefixed.']))->name(RouteNameEnum::UserIndex)->domain(RouteDomainEnum::DashboardDomain); $routes->add($route); @@ -321,9 +295,7 @@ public function testFluentRouteNameDefinitions() Request::create('http://www.foo.com/') ); - /* - * Named Routes - */ + // Named Routes $route = new Route(['GET'], 'foo/bar', []); $route->name('foo'); $routes->add($route); @@ -341,9 +313,7 @@ public function testControllerRoutesWithADefaultNamespace() $url->setRootControllerNamespace('namespace'); - /* - * Controller Route Route - */ + // Controller Route Route $route = new Route(['GET'], 'foo/bar', ['controller' => 'namespace\foo@bar']); $routes->add($route); @@ -428,15 +398,8 @@ public function testRoutableInterfaceRoutingAsQueryString() $this->assertSame('/foo?foo=routable', $url->route('query-string', ['foo' => $model], false)); } - /** - * @todo Fix bug related to route keys - * - * @link https://github.com/laravel/framework/pull/42425 - */ public function testRoutableInterfaceRoutingWithSeparateBindingFieldOnlyForSecondParameter() { - $this->markTestSkipped('See https://github.com/laravel/framework/pull/43255'); - $url = new UrlGenerator( $routes = new RouteCollection, Request::create('http://www.foo.com/') @@ -478,9 +441,7 @@ public function testRoutesMaintainRequestScheme() Request::create('https://www.foo.com/') ); - /* - * Named Routes - */ + // Named Routes $route = new Route(['GET'], 'foo/bar', ['as' => 'foo']); $routes->add($route); @@ -494,9 +455,7 @@ public function testHttpOnlyRoutes() Request::create('https://www.foo.com/') ); - /* - * Named Routes - */ + // Named Routes $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'http']); $routes->add($route); @@ -513,9 +472,7 @@ public function testRoutesWithDomains() $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'domain' => 'sub.foo.com']); $routes->add($route); - /* - * Wildcards & Domains... - */ + // Wildcards & Domains... $route = new Route(['GET'], 'foo/bar/{baz}', ['as' => 'bar', 'domain' => 'sub.{foo}.com']); $routes->add($route); @@ -534,9 +491,7 @@ public function testRoutesWithDomainsAndPorts() $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'domain' => 'sub.foo.com']); $routes->add($route); - /* - * Wildcards & Domains... - */ + // Wildcards & Domains... $route = new Route(['GET'], 'foo/bar/{baz}', ['as' => 'bar', 'domain' => 'sub.{foo}.com']); $routes->add($route); @@ -546,9 +501,7 @@ public function testRoutesWithDomainsAndPorts() public function testRoutesWithDomainsStripsProtocols() { - /* - * http:// Route - */ + // http:// Route $url = new UrlGenerator( $routes = new RouteCollection, Request::create('http://www.foo.com/') @@ -559,9 +512,7 @@ public function testRoutesWithDomainsStripsProtocols() $this->assertSame('http://sub.foo.com/foo/bar', $url->route('foo')); - /* - * https:// Route - */ + // https:// Route $url = new UrlGenerator( $routes = new RouteCollection, Request::create('https://www.foo.com/') @@ -580,9 +531,7 @@ public function testHttpsRoutesWithDomains() Request::create('https://foo.com/') ); - /* - * When on HTTPS, no need to specify 443 - */ + // When on HTTPS, no need to specify 443 $route = new Route(['GET'], 'foo/bar', ['as' => 'baz', 'domain' => 'sub.foo.com']); $routes->add($route); @@ -716,9 +665,7 @@ public function testUseRootUrl() $url->useOrigin('http://www.foo.com/'); $this->assertSame('http://www.foo.com/bar', $url->to('/bar')); - /* - * Route Based... - */ + // Route Based... $url = new UrlGenerator( $routes = new RouteCollection, Request::create('http://www.foo.com/') @@ -821,6 +768,12 @@ public function testSignedUrl() $request = Request::create($url->signedRoute('foo').'?tampered=true'); $this->assertFalse($url->hasValidSignature($request)); + + $request = Request::create($url->signedRoute('foo').'&tampered=true'); + + $this->assertTrue($url->hasValidSignature($request, ignoreQuery: ['tampered'])); + + $this->assertTrue($url->hasValidSignature($request, ignoreQuery: fn ($parameter) => $parameter === 'tampered')); } public function testSignedUrlImplicitModelBinding() @@ -996,6 +949,1039 @@ public function testMissingNamedRouteResolution() $this->assertSame('test-url', $url->route('foo')); } + + public function testPassedParametersHavePrecedenceOverDefaults() + { + $url = new UrlGenerator( + $routes = new RouteCollection, + Request::create('https://www.foo.com/') + ); + + $url->defaults([ + 'tenant' => 'defaultTenant', + ]); + + $route = new Route(['GET'], 'bar/{tenant}/{post}', ['as' => 'bar', fn () => '']); + $routes->add($route); + + // Named parameters + $this->assertSame( + 'https://www.foo.com/bar/concreteTenant/concretePost', + $url->route('bar', [ + 'tenant' => tap(new RoutableInterfaceStub, fn ($x) => $x->key = 'concreteTenant'), + 'post' => tap(new RoutableInterfaceStub, fn ($x) => $x->key = 'concretePost'), + ]), + ); + + // Positional parameters + $this->assertSame( + 'https://www.foo.com/bar/concreteTenant/concretePost', + $url->route('bar', [ + tap(new RoutableInterfaceStub, fn ($x) => $x->key = 'concreteTenant'), + tap(new RoutableInterfaceStub, fn ($x) => $x->key = 'concretePost'), + ]), + ); + } + + public function testComplexRouteGenerationWithDefaultsAndBindingFields() + { + $url = new UrlGenerator( + $routes = new RouteCollection, + Request::create('https://www.foo.com/') + ); + + $url->defaults([ + 'tenant' => 'defaultTenant', + 'tenant:slug' => 'defaultTenantSlug', + 'team' => 'defaultTeam', + 'team:slug' => 'defaultTeamSlug', + 'user' => 'defaultUser', + 'user:slug' => 'defaultUserSlug', + ]); + + $keyParam = fn ($value) => tap(new RoutableInterfaceStub, fn ($routable) => $routable->key = $value); + $slugParam = fn ($value) => tap(new RoutableInterfaceStub, fn ($routable) => $routable->slug = $value); + + /** + * One parameter with a default value, one without a default value. + * + * No binding fields. + */ + $route = new Route(['GET'], 'tenantPost/{tenant}/{post}', ['as' => 'tenantPost', fn () => '']); + $routes->add($route); + + // tenantPost: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPost/concreteTenant/concretePost', + $url->route('tenantPost', [$keyParam('concreteTenant'), $keyParam('concretePost')]), + ); + + // tenantPost: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantPost/concreteTenant/concretePost', + $url->route('tenantPost', ['tenant' => $keyParam('concreteTenant'), 'post' => $keyParam('concretePost')]), + ); + + // tenantPost: Tenant (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPost/defaultTenant/concretePost', + $url->route('tenantPost', [$keyParam('concretePost')]), + ); + + // tenantPost: Tenant (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPost/defaultTenant/concretePost', + $url->route('tenantPost', ['post' => $keyParam('concretePost')]), + ); + + /** + * One parameter with a default value, one without a default value. + * + * Binding field for the first {tenant} parameter with a default value. + */ + $route = new Route(['GET'], 'tenantSlugPost/{tenant:slug}/{post}', ['as' => 'tenantSlugPost', fn () => '']); + $routes->add($route); + + // tenantSlugPost: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/concreteTenantSlug/concretePost', + $url->route('tenantSlugPost', [$slugParam('concreteTenantSlug'), $keyParam('concretePost')]), + ); + + // tenantSlugPost: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/concreteTenantSlug/concretePost', + $url->route('tenantSlugPost', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost')]), + ); + + // tenantSlugPost: Tenant (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/defaultTenantSlug/concretePost', + $url->route('tenantSlugPost', [$keyParam('concretePost')]), + ); + + // tenantSlugPost: Tenant (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/defaultTenantSlug/concretePost', + $url->route('tenantSlugPost', ['post' => $keyParam('concretePost')]), + ); + + /** + * One parameter with a default value, one without a default value. + * + * Binding field for the second parameter without a default value. + * + * This is the only route in this test where we use a binding field + * for a parameter that does not have a default value and is not + * the first parameter. This is the simplest scenario so it doesn't + * need to be tested as repetitively as the other scenarios which are + * all special in some way. + */ + $route = new Route(['GET'], 'tenantPostSlug/{tenant}/{post:slug}', ['as' => 'tenantPostSlug', fn () => '']); + $routes->add($route); + + // tenantPostSlug: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostSlug/concreteTenant/concretePostSlug', + $url->route('tenantPostSlug', [$keyParam('concreteTenant'), $slugParam('concretePostSlug')]), + ); + + // tenantPostSlug: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantPostSlug/concreteTenant/concretePostSlug', + $url->route('tenantPostSlug', ['tenant' => $keyParam('concreteTenant'), 'post' => $slugParam('concretePostSlug')]), + ); + + // tenantPostSlug: Tenant (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostSlug/defaultTenant/concretePostSlug', + $url->route('tenantPostSlug', [$slugParam('concretePostSlug')]), + ); + + // tenantPostSlug: Tenant (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostSlug/defaultTenant/concretePostSlug', + $url->route('tenantPostSlug', ['post' => $slugParam('concretePostSlug')]), + ); + + /** + * Two parameters with a default value, one without. + * + * Having established that passing parameters by key works fine above, + * we mainly test positional parameters in variations of this route. + */ + $route = new Route(['GET'], 'tenantTeamPost/{tenant}/{team}/{post}', ['as' => 'tenantTeamPost', fn () => '']); + $routes->add($route); + + // tenantTeamPost: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamPost/concreteTenant/concreteTeam/concretePost', + $url->route('tenantTeamPost', [$keyParam('concreteTenant'), $keyParam('concreteTeam'), $keyParam('concretePost')]), + ); + + // tenantTeamPost: Tenant (with default) omitted, team and post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamPost/defaultTenant/concreteTeam/concretePost', + $url->route('tenantTeamPost', [$keyParam('concreteTeam'), $keyParam('concretePost')]), + ); + + // tenantTeamPost: Tenant and team (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamPost/defaultTenant/defaultTeam/concretePost', + $url->route('tenantTeamPost', [$keyParam('concretePost')]), + ); + + // tenantTeamPost: Tenant passed by key, team (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamPost/concreteTenant/defaultTeam/concretePost', + $url->route('tenantTeamPost', ['tenant' => $keyParam('concreteTenant'), $keyParam('concretePost')]), + ); + + /** + * Two parameters with a default value, one without. + * + * The first {tenant} parameter also has a binding field. + */ + $route = new Route(['GET'], 'tenantSlugTeamPost/{tenant:slug}/{team}/{post}', ['as' => 'tenantSlugTeamPost', fn () => '']); + $routes->add($route); + + // tenantSlugTeamPost: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamPost/concreteTenantSlug/concreteTeam/concretePost', + $url->route('tenantSlugTeamPost', [$slugParam('concreteTenantSlug'), $keyParam('concreteTeam'), $keyParam('concretePost')]), + ); + + // tenantSlugTeamPost: Tenant (with default) omitted, team and post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamPost/defaultTenantSlug/concreteTeam/concretePost', + $url->route('tenantSlugTeamPost', [$keyParam('concreteTeam'), $keyParam('concretePost')]), + ); + + // tenantSlugTeamPost: Tenant and team (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamPost/defaultTenantSlug/defaultTeam/concretePost', + $url->route('tenantSlugTeamPost', [$keyParam('concretePost')]), + ); + + // tenantSlugTeamPost: Tenant passed by key, team (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamPost/concreteTenantSlug/defaultTeam/concretePost', + $url->route('tenantSlugTeamPost', ['tenant' => $slugParam('concreteTenantSlug'), $keyParam('concretePost')]), + ); + + /** + * Two parameters with a default value, one without. + * + * The second {team} parameter also has a binding field. + */ + $route = new Route(['GET'], 'tenantTeamSlugPost/{tenant}/{team:slug}/{post}', ['as' => 'tenantTeamSlugPost', fn () => '']); + $routes->add($route); + + // tenantTeamSlugPost: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamSlugPost/concreteTenant/concreteTeamSlug/concretePost', + $url->route('tenantTeamSlugPost', [$keyParam('concreteTenant'), $slugParam('concreteTeamSlug'), $keyParam('concretePost')]), + ); + + // tenantTeamSlugPost: Tenant (with default) omitted, team and post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamSlugPost/defaultTenant/concreteTeamSlug/concretePost', + $url->route('tenantTeamSlugPost', [$slugParam('concreteTeamSlug'), $keyParam('concretePost')]), + ); + + // tenantTeamSlugPost: Tenant and team (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamSlugPost/defaultTenant/defaultTeamSlug/concretePost', + $url->route('tenantTeamSlugPost', [$keyParam('concretePost')]), + ); + + // tenantTeamSlugPost: Tenant passed by key, team (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantTeamSlugPost/concreteTenantSlug/defaultTeamSlug/concretePost', + $url->route('tenantTeamSlugPost', ['tenant' => $keyParam('concreteTenantSlug'), $keyParam('concretePost')]), + ); + + /** + * Two parameters with a default value, one without. + * + * Both parameters with default values, {tenant} and {team}, also have binding fields. + */ + $route = new Route(['GET'], 'tenantSlugTeamSlugPost/{tenant:slug}/{team:slug}/{post}', ['as' => 'tenantSlugTeamSlugPost', fn () => '']); + $routes->add($route); + + // tenantSlugTeamSlugPost: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamSlugPost/concreteTenantSlug/concreteTeamSlug/concretePost', + $url->route('tenantSlugTeamSlugPost', [$slugParam('concreteTenantSlug'), $slugParam('concreteTeamSlug'), $keyParam('concretePost')]), + ); + + // tenantSlugTeamSlugPost: Tenant (with default) omitted, team and post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamSlugPost/defaultTenantSlug/concreteTeamSlug/concretePost', + $url->route('tenantSlugTeamSlugPost', [$slugParam('concreteTeamSlug'), $keyParam('concretePost')]), + ); + + // tenantSlugTeamSlugPost: Tenant and team (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamSlugPost/defaultTenantSlug/defaultTeamSlug/concretePost', + $url->route('tenantSlugTeamSlugPost', [$keyParam('concretePost')]), + ); + + // tenantSlugTeamSlugPost: Tenant passed by key, team (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugTeamSlugPost/concreteTenantSlug/defaultTeamSlug/concretePost', + $url->route('tenantSlugTeamSlugPost', ['tenant' => $slugParam('concreteTenantSlug'), $keyParam('concretePost')]), + ); + + /** + * One parameter without a default value, one with a default value. + * + * Importantly, the parameter with the default value comes second. + */ + $route = new Route(['GET'], 'postUser/{post}/{user}', ['as' => 'postUser', fn () => '']); + $routes->add($route); + + // postUser: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/postUser/concretePost/concreteUser', + $url->route('postUser', [$keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // postUser: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/postUser/concretePost/concreteUser', + // Reversed order just to check it doesn't matter with named parameters + $url->route('postUser', ['user' => $keyParam('concreteUser'), 'post' => $keyParam('concretePost')]), + ); + + // postUser: User (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/postUser/concretePost/defaultUser', + $url->route('postUser', [$keyParam('concretePost')]), + ); + + // postUser: User (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/postUser/concretePost/defaultUser', + $url->route('postUser', ['post' => $keyParam('concretePost')]), + ); + + /** + * One parameter without a default value, one with a default value. + * + * Importantly, the parameter with the default value comes second. + * + * In this variation the first parameter, without a default value, + * also has a binding field. + */ + $route = new Route(['GET'], 'postSlugUser/{post:slug}/{user}', ['as' => 'postSlugUser', fn () => '']); + $routes->add($route); + + // postSlugUser: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/postSlugUser/concretePostSlug/concreteUser', + $url->route('postSlugUser', [$slugParam('concretePostSlug'), $keyParam('concreteUser')]), + ); + + // postSlugUser: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/postSlugUser/concretePostSlug/concreteUser', + $url->route('postSlugUser', ['post' => $slugParam('concretePostSlug'), 'user' => $keyParam('concreteUser')]), + ); + + // postSlugUser: User (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/postSlugUser/concretePostSlug/defaultUser', + $url->route('postSlugUser', [$slugParam('concretePostSlug')]), + ); + + // postSlugUser: User (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/postSlugUser/concretePostSlug/defaultUser', + $url->route('postSlugUser', ['post' => $slugParam('concretePostSlug')]), + ); + + /** + * One parameter without a default value, one with a default value. + * + * Importantly, the parameter with the default value comes second. + * + * In this variation the second parameter, with a default value, + * also has a binding field. + */ + $route = new Route(['GET'], 'postUserSlug/{post}/{user:slug}', ['as' => 'postUserSlug', fn () => '']); + $routes->add($route); + + // postUserSlug: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/postUserSlug/concretePost/concreteUserSlug', + $url->route('postUserSlug', [$keyParam('concretePost'), $slugParam('concreteUserSlug')]), + ); + + // postUserSlug: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/postUserSlug/concretePost/concreteUserSlug', + $url->route('postUserSlug', ['post' => $keyParam('concretePost'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // postUserSlug: User (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/postUserSlug/concretePost/defaultUserSlug', + $url->route('postUserSlug', [$keyParam('concretePost')]), + ); + + // postUserSlug: User (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/postUserSlug/concretePost/defaultUserSlug', + $url->route('postUserSlug', ['post' => $keyParam('concretePost')]), + ); + + /** + * One parameter without a default value, one with a default value. + * + * Importantly, the parameter with the default value comes second. + * + * In this variation, both parameters have binding fields. + */ + $route = new Route(['GET'], 'postSlugUserSlug/{post:slug}/{user:slug}', ['as' => 'postSlugUserSlug', fn () => '']); + $routes->add($route); + + // postSlugUserSlug: Both parameters passed positionally + $this->assertSame( + 'https://www.foo.com/postSlugUserSlug/concretePostSlug/concreteUserSlug', + $url->route('postSlugUserSlug', [$slugParam('concretePostSlug'), $slugParam('concreteUserSlug')]), + ); + + // postSlugUserSlug: Both parameters passed with keys + $this->assertSame( + 'https://www.foo.com/postSlugUserSlug/concretePostSlug/concreteUserSlug', + $url->route('postSlugUserSlug', ['post' => $slugParam('concretePostSlug'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // postSlugUserSlug: User (with default) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/postSlugUserSlug/concretePostSlug/defaultUserSlug', + $url->route('postSlugUserSlug', [$slugParam('concretePostSlug')]), + ); + + // postSlugUserSlug: User (with default) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/postSlugUserSlug/concretePostSlug/defaultUserSlug', + $url->route('postSlugUserSlug', ['post' => $slugParam('concretePostSlug')]), + ); + + /** + * Parameter without a default value in between two parameters with default values. + */ + $route = new Route(['GET'], 'tenantPostUser/{tenant}/{post}/{user}', ['as' => 'tenantPostUser', fn () => '']); + $routes->add($route); + + // tenantPostUser: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUser/concreteTenant/concretePost/concreteUser', + $url->route('tenantPostUser', [$keyParam('concreteTenant'), $keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantPostUser: Tenant parameter omitted, post and user passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/concreteUser', + $url->route('tenantPostUser', [$keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantPostUser: Both tenant and user (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/defaultUser', + $url->route('tenantPostUser', [$keyParam('concretePost')]), + ); + + // tenantPostUser: All parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantPostUser/concreteTenant/concretePost/concreteUser', + $url->route('tenantPostUser', ['tenant' => $keyParam('concreteTenant'), 'post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantPostUser: Both tenant and user (with defaults) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/defaultUser', + $url->route('tenantPostUser', ['post' => $keyParam('concretePost')]), + ); + + // tenantPostUser: Tenant parameter (with default) omitted, post and user passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/concreteUser', + $url->route('tenantPostUser', ['post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantPostUser: User parameter (with default) omitted, tenant and post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUser/concreteTenant/concretePost/defaultUser', + $url->route('tenantPostUser', ['tenant' => $keyParam('concreteTenant'), 'post' => $keyParam('concretePost')]), + ); + + /** + * Parameter without a default value in between two parameters with a default value. + * + * In this variation of this route, the first {tenant} parameter, with a default value, + * also has a binding field. + */ + $route = new Route(['GET'], 'tenantSlugPostUser/{tenant:slug}/{post}/{user}', ['as' => 'tenantSlugPostUser', fn () => '']); + $routes->add($route); + + // tenantSlugPostUser: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', [$slugParam('concreteTenantSlug'), $keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Tenant parameter omitted, post and user passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', [$keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Both tenant and user (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', [$keyParam('concretePost')]), + ); + + // tenantSlugPostUser: All parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Both tenant and user (with defaults) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', ['post' => $keyParam('concretePost')]), + ); + + // tenantSlugPostUser: Tenant parameter (with default) omitted, post and user passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', ['post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: User parameter (with default) omitted, tenant and post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost')]), + ); + + /** + * Parameter without a default value in between two parameters with a default value. + * + * In this variation of this route, the last {user} parameter, with a default value, + * also has a binding field. + */ + $route = new Route(['GET'], 'tenantPostUserSlug/{tenant}/{post}/{user:slug}', ['as' => 'tenantPostUserSlug', fn () => '']); + $routes->add($route); + + // tenantPostUserSlug: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/concreteTenant/concretePost/concreteUserSlug', + $url->route('tenantPostUserSlug', [$keyParam('concreteTenant'), $keyParam('concretePost'), $slugParam('concreteUserSlug')]), + ); + + // tenantPostUserSlug: Tenant parameter omitted, post and user passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/defaultTenant/concretePost/concreteUserSlug', + $url->route('tenantPostUserSlug', [$keyParam('concretePost'), $slugParam('concreteUserSlug')]), + ); + + // tenantPostUserSlug: Both tenant and user (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/defaultTenant/concretePost/defaultUserSlug', + $url->route('tenantPostUserSlug', [$keyParam('concretePost')]), + ); + + // tenantPostUserSlug: All parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/concreteTenant/concretePost/concreteUserSlug', + $url->route('tenantPostUserSlug', ['tenant' => $keyParam('concreteTenant'), 'post' => $keyParam('concretePost'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // tenantPostUserSlug: Both tenant and user (with defaults) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/defaultTenant/concretePost/defaultUserSlug', + $url->route('tenantPostUserSlug', ['post' => $keyParam('concretePost')]), + ); + + // tenantPostUserSlug: Tenant parameter (with default) omitted, post and user passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/defaultTenant/concretePost/concreteUserSlug', + $url->route('tenantPostUserSlug', ['post' => $keyParam('concretePost'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // tenantPostUserSlug: User parameter (with default) omitted, tenant and post passed using key + $this->assertSame( + 'https://www.foo.com/tenantPostUserSlug/concreteTenant/concretePost/defaultUserSlug', + $url->route('tenantPostUserSlug', ['tenant' => $keyParam('concreteTenant'), 'post' => $keyParam('concretePost')]), + ); + + /** + * Parameter without a default value in between two parameters with a default value. + * + * In this variation of this route, the first {tenant} parameter, with a default value, + * also has a binding field. + */ + $route = new Route(['GET'], 'tenantSlugPostUser/{tenant:slug}/{post}/{user}', ['as' => 'tenantSlugPostUser', fn () => '']); + $routes->add($route); + + // tenantSlugPostUser: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', [$slugParam('concreteTenantSlug'), $keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Tenant parameter omitted, post and user passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', [$keyParam('concretePost'), $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Both tenant and user (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', [$keyParam('concretePost')]), + ); + + // tenantSlugPostUser: All parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: Both tenant and user (with defaults) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', ['post' => $keyParam('concretePost')]), + ); + + // tenantSlugPostUser: Tenant parameter (with default) omitted, post and user passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/defaultTenantSlug/concretePost/concreteUser', + $url->route('tenantSlugPostUser', ['post' => $keyParam('concretePost'), 'user' => $keyParam('concreteUser')]), + ); + + // tenantSlugPostUser: User parameter (with default) omitted, tenant and post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUser/concreteTenantSlug/concretePost/defaultUser', + $url->route('tenantSlugPostUser', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost')]), + ); + + /** + * Parameter without a default value in between two parameters with a default value. + * + * In this variation of this route, both fields with a default value, {tenant} and + * {user}, also have binding fields. + */ + $route = new Route(['GET'], 'tenantSlugPostUserSlug/{tenant:slug}/{post}/{user:slug}', ['as' => 'tenantSlugPostUserSlug', fn () => '']); + $routes->add($route); + + // tenantSlugPostUserSlug: All parameters passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/concreteTenantSlug/concretePost/concreteUserSlug', + $url->route('tenantSlugPostUserSlug', [$slugParam('concreteTenantSlug'), $keyParam('concretePost'), $slugParam('concreteUserSlug')]), + ); + + // tenantSlugPostUserSlug: Tenant parameter omitted, post and user passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/defaultTenantSlug/concretePost/concreteUserSlug', + $url->route('tenantSlugPostUserSlug', [$keyParam('concretePost'), $slugParam('concreteUserSlug')]), + ); + + // tenantSlugPostUserSlug: Both tenant and user (with defaults) omitted, post passed positionally + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/defaultTenantSlug/concretePost/defaultUserSlug', + $url->route('tenantSlugPostUserSlug', [$keyParam('concretePost')]), + ); + + // tenantSlugPostUserSlug: All parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/concreteTenantSlug/concretePost/concreteUserSlug', + $url->route('tenantSlugPostUserSlug', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // tenantSlugPostUserSlug: Both tenant and user (with defaults) omitted, post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/defaultTenantSlug/concretePost/defaultUserSlug', + $url->route('tenantSlugPostUserSlug', ['post' => $keyParam('concretePost')]), + ); + + // tenantSlugPostUserSlug: Tenant parameter (with default) omitted, post and user passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/defaultTenantSlug/concretePost/concreteUserSlug', + $url->route('tenantSlugPostUserSlug', ['post' => $keyParam('concretePost'), 'user' => $slugParam('concreteUserSlug')]), + ); + + // tenantSlugPostUserSlug: User parameter (with default) omitted, tenant and post passed using key + $this->assertSame( + 'https://www.foo.com/tenantSlugPostUserSlug/concreteTenantSlug/concretePost/defaultUserSlug', + $url->route('tenantSlugPostUserSlug', ['tenant' => $slugParam('concreteTenantSlug'), 'post' => $keyParam('concretePost')]), + ); + } + + public function testComplexRouteGenerationWithDefaultsAndMixedParameterSyntax() + { + $url = new UrlGenerator( + $routes = new RouteCollection, + Request::create('https://www.foo.com/') + ); + + $url->defaults([ + 'tenant' => 'defaultTenant', + 'user' => 'defaultUser', + ]); + + /** + * Parameter without a default value in between two parameters with default values. + */ + $route = new Route(['GET'], 'tenantPostUser/{tenant}/{post}/{user}', ['as' => 'tenantPostUser', fn () => '']); + $routes->add($route); + + // If the required post parameter is specified using a key, + // the positional parameter is used for the user parameter. + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/concreteUser', + $url->route('tenantPostUser', ['post' => 'concretePost', 'concreteUser']), + ); + + /** + * Two parameters without default values in between two parameters with default values. + */ + $route = new Route(['GET'], 'tenantPostCommentUser/{tenant}/{post}/{comment}/{user}', ['as' => 'tenantPostCommentUser', fn () => '']); + $routes->add($route); + + // Pass first required parameter using a key, second positionally + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/defaultUser', + $url->route('tenantPostCommentUser', ['post' => 'concretePost', 'concreteComment']), + ); + + // Pass first required parameter positionally, second using a key + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/defaultUser', + $url->route('tenantPostCommentUser', ['concretePost', 'comment' => 'concreteComment']), + ); + + // Verify that this is order-independent + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/defaultUser', + $url->route('tenantPostCommentUser', ['comment' => 'concreteComment', 'concretePost']), + ); + + // Both required params passed with keys, positional parameter goes to the user param + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['post' => 'concretePost', 'comment' => 'concreteComment', 'concreteUser']), + ); + + // First required param passed with a key, remaining params go to the last two route params + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['post' => 'concretePost', 'concreteComment', 'concreteUser']), + ); + + // Comment parameter passed with a key, remaining params filled (last to last) + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['concretePost', 'comment' => 'concreteComment', 'concreteUser']), + ); + + // Verify that this is order-independent + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/defaultTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['comment' => 'concreteComment', 'concretePost', 'concreteUser']), + ); + + // Both default parameters passed positionally, required parameters passed with keys + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/concreteTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['concreteTenant', 'post' => 'concretePost', 'comment' => 'concreteComment', 'concreteUser']), + ); + + // Verify that the positional parameters may come anywhere in the array + $this->assertSame( + 'https://www.foo.com/tenantPostCommentUser/concreteTenant/concretePost/concreteComment/concreteUser', + $url->route('tenantPostCommentUser', ['post' => 'concretePost', 'comment' => 'concreteComment', 'concreteTenant', 'concreteUser']), + ); + } + + public function testDefaultsCanBeCombinedWithExtraQueryParameters() + { + $url = new UrlGenerator( + $routes = new RouteCollection, + Request::create('https://www.foo.com/') + ); + + $url->defaults([ + 'tenant' => 'defaultTenant', + 'tenant:slug' => 'defaultTenantSlug', + 'user' => 'defaultUser', + ]); + + $slugParam = fn ($value) => tap(new RoutableInterfaceStub, fn ($routable) => $routable->slug = $value); + + /** + * One parameter with a default value, one parameter without a default value. + */ + $route = new Route(['GET'], 'tenantPost/{tenant}/{post}', ['as' => 'tenantPost', fn () => '']); + $routes->add($route); + + // tenantPost: Extra positional parameters without values are interpreted as query strings + $this->assertSame( + 'https://www.foo.com/tenantPost/concreteTenant/concretePost?extraQuery', + $url->route('tenantPost', ['concreteTenant', 'concretePost', 'extraQuery']), + ); + + // tenantPost: Query parameters without values go at the end + $this->assertSame( + 'https://www.foo.com/tenantPost/concreteTenant/concretePost?extra=query&extraQuery', + $url->route('tenantPost', ['concreteTenant', 'concretePost', 'extraQuery', 'extra' => 'query']), + ); + + // tenantPost: Defaults can be used with *named* query parameters + $this->assertSame( + 'https://www.foo.com/tenantPost/defaultTenant/concretePost?extra=query', + $url->route('tenantPost', ['concretePost', 'extra' => 'query']), + ); + + // tenantPost: Named query parameters can be placed anywhere in the parameters array + $this->assertSame( + 'https://www.foo.com/tenantPost/concreteTenant/concretePost?extra=query', + $url->route('tenantPost', ['concreteTenant', 'extra' => 'query', 'concretePost']), + ); + + /** + * One parameter with a default value, one parameter without a default value. + * + * The first parameter with a default value, {tenant}, also has a binding field. + */ + $route = new Route(['GET'], 'tenantSlugPost/{tenant:slug}/{post}', ['as' => 'tenantSlugPost', fn () => '']); + $routes->add($route); + + // tenantSlugPost: Extra positional parameters without values are interpreted as query strings + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/concreteTenantSlug/concretePost?extraQuery', + $url->route('tenantSlugPost', [$slugParam('concreteTenantSlug'), 'concretePost', 'extraQuery']), + ); + + // tenantSlugPost: Query parameters without values go at the end + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/concreteTenantSlug/concretePost?extra=query&extraQuery', + $url->route('tenantSlugPost', [$slugParam('concreteTenantSlug'), 'concretePost', 'extraQuery', 'extra' => 'query']), + ); + + // tenantSlugPost: Defaults can be used with *named* query parameters + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/defaultTenantSlug/concretePost?extra=query', + $url->route('tenantSlugPost', ['concretePost', 'extra' => 'query']), + ); + + // tenantSlugPost: Named query parameters can be placed anywhere in the parameters array + $this->assertSame( + 'https://www.foo.com/tenantSlugPost/concreteTenantSlug/concretePost?extra=query', + $url->route('tenantSlugPost', [$slugParam('concreteTenantSlug'), 'extra' => 'query', 'concretePost']), + ); + + /** + * Parameter without a default value in between two parameters with default values. + */ + $route = new Route(['GET'], 'tenantPostUser/{tenant}/{post}/{user}', ['as' => 'tenantPostUser', fn () => '']); + $routes->add($route); + + // tenantPostUser: Query string parameters may be passed positionally if + // all route parameters are passed as well, i.e. defaults are not used. + $this->assertSame( + 'https://www.foo.com/tenantPostUser/concreteTenant/concretePost/concreteUser?extraQuery', + $url->route('tenantPostUser', ['concreteTenant', 'concretePost', 'concreteUser', 'extraQuery']), + ); + + // tenantPostUser: Query string parameters can be passed as key-value + // pairs if all route params are passed as well, i.e. no defaults. + $this->assertSame( + 'https://www.foo.com/tenantPostUser/concreteTenant/concretePost/concreteUser?extraQuery', + $url->route('tenantPostUser', ['concreteTenant', 'concretePost', 'concreteUser', 'extraQuery']), + ); + + // tenantPostUser: With omitted default parameters, query string parameters + // can only be specified using key-value pairs. Positional query string + // parameters would be interpreted as route parameters instead. + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/concreteUser?extra=query', + $url->route('tenantPostUser', ['concretePost', 'concreteUser', 'extra' => 'query']), + ); + + // tenantPostUser: Use defaults for tenant and user, pass post positionally + // and add an extra query string parameter as a key-value pair. + $this->assertSame( + 'https://www.foo.com/tenantPostUser/defaultTenant/concretePost/defaultUser?extra=query', + $url->route('tenantPostUser', ['concretePost', 'extra' => 'query']), + ); + } + + public function testUrlGenerationWithOptionalParameters(): void + { + $url = new UrlGenerator( + $routes = new RouteCollection, + Request::create('https://www.foo.com/') + ); + + $url->defaults([ + 'tenant' => 'defaultTenant', + 'user' => 'defaultUser', + ]); + + /** + * Route with one required parameter and one optional parameter. + */ + $route = new Route(['GET'], 'postOptionalMethod/{post}/{method?}', ['as' => 'postOptionalMethod', fn () => '']); + $routes->add($route); + + $this->assertSame( + 'https://www.foo.com/postOptionalMethod/1', + $url->route('postOptionalMethod', 1), + ); + + $this->assertSame( + 'https://www.foo.com/postOptionalMethod/1/2', + $url->route('postOptionalMethod', [1, 2]), + ); + + /** + * Route with two optional parameters. + */ + $route = new Route(['GET'], 'optionalPostOptionalMethod/{post}/{method?}', ['as' => 'optionalPostOptionalMethod', fn () => '']); + $routes->add($route); + + $this->assertSame( + 'https://www.foo.com/optionalPostOptionalMethod/1', + $url->route('optionalPostOptionalMethod', 1), + ); + + $this->assertSame( + 'https://www.foo.com/optionalPostOptionalMethod/1/2', + $url->route('optionalPostOptionalMethod', [1, 2]), + ); + + /** + * Route with one default parameter, one required parameter, and one optional parameter. + */ + $route = new Route(['GET'], 'tenantPostOptionalMethod/{tenant}/{post}/{method?}', ['as' => 'tenantPostOptionalMethod', fn () => '']); + $routes->add($route); + + // Passing one parameter + $this->assertSame( + 'https://www.foo.com/tenantPostOptionalMethod/defaultTenant/concretePost', + $url->route('tenantPostOptionalMethod', ['concretePost']), + ); + + // Passing two parameters: optional parameter is prioritized over parameter with a default value + $this->assertSame( + 'https://www.foo.com/tenantPostOptionalMethod/defaultTenant/concretePost/concreteMethod', + $url->route('tenantPostOptionalMethod', ['concretePost', 'concreteMethod']), + ); + + // Passing all three parameters + $this->assertSame( + 'https://www.foo.com/tenantPostOptionalMethod/concreteTenant/concretePost/concreteMethod', + $url->route('tenantPostOptionalMethod', ['concreteTenant', 'concretePost', 'concreteMethod']), + ); + + /** + * Route with two default parameters, one required parameter, and one optional parameter. + */ + $route = new Route(['GET'], 'tenantUserPostOptionalMethod/{tenant}/{user}/{post}/{method?}', ['as' => 'tenantUserPostOptionalMethod', fn () => '']); + $routes->add($route); + + // Passing one parameter + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/defaultUser/concretePost', + $url->route('tenantUserPostOptionalMethod', ['concretePost']), + ); + + // Passing two parameters: optional parameter is prioritized over parameters with default values + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/defaultUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['concretePost', 'concreteMethod']), + ); + + // Passing three parameters: only the leftmost parameter with a default value uses its default value + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/concreteUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['concreteUser', 'concretePost', 'concreteMethod']), + ); + + // Same as the assertion above, but using some named parameters + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/concreteUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['user' => 'concreteUser', 'concretePost', 'concreteMethod']), + ); + + // Also using a named parameter, but this time for the post parameter + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/concreteUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['concreteUser', 'post' => 'concretePost', 'concreteMethod']), + ); + + // Also using a named parameter, but this time for the optional method parameter + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/defaultTenant/concreteUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['concreteUser', 'concretePost', 'method' => 'concreteMethod']), + ); + + // Passing all four parameters + $this->assertSame( + 'https://www.foo.com/tenantUserPostOptionalMethod/concreteTenant/concreteUser/concretePost/concreteMethod', + $url->route('tenantUserPostOptionalMethod', ['concreteTenant', 'concreteUser', 'concretePost', 'concreteMethod']), + ); + + /** + * Route with a default parameter, a required parameter, another default parameter, and finally an optional parameter. + * + * This tests interleaved default parameters when combined with optional parameters. + */ + $route = new Route(['GET'], 'tenantPostUserOptionalMethod/{tenant}/{post}/{user}/{method?}', ['as' => 'tenantPostUserOptionalMethod', fn () => '']); + $routes->add($route); + + // Passing one parameter + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/defaultTenant/concretePost/defaultUser', + $url->route('tenantPostUserOptionalMethod', ['concretePost']), + ); + + // Passing two parameters: optional parameter is prioritized over parameters with default values + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/defaultTenant/concretePost/defaultUser/concreteMethod', + $url->route('tenantPostUserOptionalMethod', ['concretePost', 'concreteMethod']), + ); + + // Same as the assertion above, but using some named parameters + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/defaultTenant/concretePost/defaultUser/concreteMethod', + $url->route('tenantPostUserOptionalMethod', ['post' => 'concretePost', 'concreteMethod']), + ); + + // Also using a named parameter, but this time for the optional parameter + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/defaultTenant/concretePost/defaultUser/concreteMethod', + $url->route('tenantPostUserOptionalMethod', ['concretePost', 'method' => 'concreteMethod']), + ); + + // Passing three parameters: only the leftmost parameter with a default value uses its default value + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/defaultTenant/concretePost/concreteUser/concreteMethod', + $url->route('tenantPostUserOptionalMethod', ['concretePost', 'concreteUser', 'concreteMethod']), + ); + + // Passing all four parameters + $this->assertSame( + 'https://www.foo.com/tenantPostUserOptionalMethod/concreteTenant/concretePost/concreteUser/concreteMethod', + $url->route('tenantPostUserOptionalMethod', ['concreteTenant', 'concretePost', 'concreteUser', 'concreteMethod']), + ); + } } class RoutableInterfaceStub implements UrlRoutable diff --git a/tests/Session/CacheBasedSessionHandlerTest.php b/tests/Session/CacheBasedSessionHandlerTest.php new file mode 100644 index 000000000000..af6cf68fdc01 --- /dev/null +++ b/tests/Session/CacheBasedSessionHandlerTest.php @@ -0,0 +1,89 @@ +cacheMock = Mockery::mock(CacheContract::class); + $this->sessionHandler = new CacheBasedSessionHandler(cache: $this->cacheMock, minutes: 10); + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } + + public function test_open() + { + $result = $this->sessionHandler->open('path', 'session_name'); + $this->assertTrue($result); + } + + public function test_close() + { + $result = $this->sessionHandler->close(); + $this->assertTrue($result); + } + + public function test_read_returns_data_from_cache() + { + $this->cacheMock->shouldReceive('get')->once()->with('session_id', '')->andReturn('session_data'); + + $data = $this->sessionHandler->read(sessionId: 'session_id'); + $this->assertEquals('session_data', $data); + } + + public function test_read_returns_empty_string_if_no_data() + { + $this->cacheMock->shouldReceive('get')->once()->with('some_id', '')->andReturn(''); + + $data = $this->sessionHandler->read(sessionId: 'some_id'); + $this->assertEquals('', $data); + } + + public function test_write_stores_data_in_cache() + { + $this->cacheMock->shouldReceive('put')->once()->with('session_id', 'session_data', 600) // 10 minutes in seconds + ->andReturn(true); + + $result = $this->sessionHandler->write(sessionId: 'session_id', data: 'session_data'); + + $this->assertTrue($result); + } + + public function test_destroy_removes_data_from_cache() + { + $this->cacheMock->shouldReceive('forget')->once()->with('session_id')->andReturn(true); + + $result = $this->sessionHandler->destroy(sessionId: 'session_id'); + + $this->assertTrue($result); + } + + public function test_gc_returns_zero() + { + $result = $this->sessionHandler->gc(lifetime: 120); + + $this->assertEquals(0, $result); + } + + public function test_get_cache_returns_cache_instance() + { + $cacheInstance = $this->sessionHandler->getCache(); + + $this->assertSame($this->cacheMock, $cacheInstance); + } +} diff --git a/tests/Session/FileSessionHandlerTest.php b/tests/Session/FileSessionHandlerTest.php new file mode 100644 index 000000000000..ba27b7019ade --- /dev/null +++ b/tests/Session/FileSessionHandlerTest.php @@ -0,0 +1,138 @@ +files = Mockery::mock(Filesystem::class); + + // Initialize the FileSessionHandler with the mocked Filesystem + $this->sessionHandler = new FileSessionHandler($this->files, '/path/to/sessions', 30); + } + + protected function tearDown(): void + { + Mockery::close(); + } + + public function test_open() + { + $this->assertTrue($this->sessionHandler->open('/path/to/sessions', 'session_name')); + } + + public function test_close() + { + $this->assertTrue($this->sessionHandler->close()); + } + + public function test_read_returns_data_when_file_exists_and_is_valid() + { + $sessionId = 'session_id'; + $path = '/path/to/sessions/'.$sessionId; + Carbon::setTestNow(Carbon::parse('2025-02-02 01:30:00')); + // Set up expectations + $this->files->shouldReceive('isFile')->with($path)->andReturn(true); + + $minutesAgo30 = Carbon::parse('2025-02-02 01:00:00')->getTimestamp(); + $this->files->shouldReceive('lastModified')->with($path)->andReturn($minutesAgo30); + $this->files->shouldReceive('sharedGet')->with($path)->once()->andReturn('session_data'); + + $result = $this->sessionHandler->read($sessionId); + + $this->assertEquals('session_data', $result); + } + + public function test_read_returns_data_when_file_exists_but_expired() + { + $sessionId = 'session_id'; + $path = '/path/to/sessions/'.$sessionId; + Carbon::setTestNow(Carbon::parse('2025-02-02 01:30:01')); + // Set up expectations + $this->files->shouldReceive('isFile')->with($path)->andReturn(true); + + $minutesAgo30 = Carbon::parse('2025-02-02 01:00:00')->getTimestamp(); + $this->files->shouldReceive('lastModified')->with($path)->andReturn($minutesAgo30); + $this->files->shouldReceive('sharedGet')->never(); + + $result = $this->sessionHandler->read($sessionId); + + $this->assertEquals('', $result); + } + + public function test_read_returns_empty_string_when_file_does_not_exist() + { + $sessionId = 'non_existing_session_id'; + $path = '/path/to/sessions/'.$sessionId; + + // Set up expectations + $this->files->shouldReceive('isFile')->with($path)->andReturn(false); + + $result = $this->sessionHandler->read($sessionId); + + $this->assertEquals('', $result); + } + + public function test_write_stores_data() + { + $sessionId = 'session_id'; + $data = 'session_data'; + + // Set up expectations + $this->files->shouldReceive('put')->with('/path/to/sessions/'.$sessionId, $data, true)->once()->andReturn(null); + + $result = $this->sessionHandler->write($sessionId, $data); + + $this->assertTrue($result); + } + + public function test_destroy_deletes_session_file() + { + $sessionId = 'session_id'; + + // Set up expectations + $this->files->shouldReceive('delete')->with('/path/to/sessions/'.$sessionId)->once()->andReturn(null); + + $result = $this->sessionHandler->destroy($sessionId); + + $this->assertTrue($result); + } + + public function test_gc_deletes_old_session_files() + { + $session = new FileSessionHandler($this->files, join_paths(__DIR__, 'tmp'), 30); + // Set up expectations for Filesystem + $this->files->shouldReceive('delete')->with(join_paths(__DIR__, 'tmp', 'a2'))->once()->andReturn(false); + $this->files->shouldReceive('delete')->with(join_paths(__DIR__, 'tmp', 'a3'))->once()->andReturn(true); + + mkdir(__DIR__.'/tmp'); + touch(__DIR__.'/tmp/a1', time() - 3); // last modified: 3 sec ago + touch(__DIR__.'/tmp/a2', time() - 5); // last modified: 5 sec ago + touch(__DIR__.'/tmp/a3', time() - 7); // last modified: 7 sec ago + + // act: + $count = $session->gc(5); + + $this->assertEquals(2, $count); + + unlink(__DIR__.'/tmp/a1'); + unlink(__DIR__.'/tmp/a2'); + unlink(__DIR__.'/tmp/a3'); + + rmdir(__DIR__.'/tmp'); + } +} diff --git a/tests/Support/Fixtures/ClassesWithAttributes.php b/tests/Support/Fixtures/ClassesWithAttributes.php new file mode 100644 index 000000000000..baed29f97fd3 --- /dev/null +++ b/tests/Support/Fixtures/ClassesWithAttributes.php @@ -0,0 +1,41 @@ +assertSame($instance->null(), $instance->null()); $this->assertSame(1, $instance->i); } + + public function testExtendedStaticClassOnceCalls() + { + $first = MyClass::staticRand(); + $second = MyExtendedClass::staticRand(); + + $this->assertNotSame($first, $second); + } } $letter = 'a'; @@ -392,3 +400,7 @@ public function callRand() return once(fn () => $this->rand()); } } + +class MyExtendedClass extends MyClass +{ +} diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 73676e981acf..e5d15a06362f 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -6,6 +6,8 @@ use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\ItemNotFoundException; +use Illuminate\Support\MultipleItemsFoundException; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use stdClass; @@ -514,6 +516,106 @@ public function testGet() $this->assertSame('bar', Arr::get(['' => ['' => 'bar']], '.')); } + public function testItGetsAString() + { + $test_array = ['string' => 'foo bar', 'integer' => 1234]; + + // Test string values are returned as strings + $this->assertSame( + 'foo bar', Arr::string($test_array, 'string') + ); + + // Test that default string values are returned for missing keys + $this->assertSame( + 'default', Arr::string($test_array, 'missing_key', 'default') + ); + + // Test that an exception is raised if the value is not a string + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('#^Array value for key \[integer\] must be a string, (.*) found.#'); + Arr::string($test_array, 'integer'); + } + + public function testItGetsAnInteger() + { + $test_array = ['string' => 'foo bar', 'integer' => 1234]; + + // Test integer values are returned as integers + $this->assertSame( + 1234, Arr::integer($test_array, 'integer') + ); + + // Test that default integer values are returned for missing keys + $this->assertSame( + 999, Arr::integer($test_array, 'missing_key', 999) + ); + + // Test that an exception is raised if the value is not an integer + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('#^Array value for key \[string\] must be an integer, (.*) found.#'); + Arr::integer($test_array, 'string'); + } + + public function testItGetsAFloat() + { + $test_array = ['string' => 'foo bar', 'float' => 12.34]; + + // Test float values are returned as floats + $this->assertSame( + 12.34, Arr::float($test_array, 'float') + ); + + // Test that default float values are returned for missing keys + $this->assertSame( + 56.78, Arr::float($test_array, 'missing_key', 56.78) + ); + + // Test that an exception is raised if the value is not a float + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('#^Array value for key \[string\] must be a float, (.*) found.#'); + Arr::float($test_array, 'string'); + } + + public function testItGetsABoolean() + { + $test_array = ['string' => 'foo bar', 'boolean' => true]; + + // Test boolean values are returned as booleans + $this->assertSame( + true, Arr::boolean($test_array, 'boolean') + ); + + // Test that default boolean values are returned for missing keys + $this->assertSame( + true, Arr::boolean($test_array, 'missing_key', true) + ); + + // Test that an exception is raised if the value is not a boolean + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('#^Array value for key \[string\] must be a boolean, (.*) found.#'); + Arr::boolean($test_array, 'string'); + } + + public function testItGetsAnArray() + { + $test_array = ['string' => 'foo bar', 'array' => ['foo', 'bar']]; + + // Test array values are returned as arrays + $this->assertSame( + ['foo', 'bar'], Arr::array($test_array, 'array') + ); + + // Test that default array values are returned for missing keys + $this->assertSame( + [1, 'two'], Arr::array($test_array, 'missing_key', [1, 'two']) + ); + + // Test that an exception is raised if the value is not an array + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('#^Array value for key \[string\] must be an array, (.*) found.#'); + Arr::array($test_array, 'string'); + } + public function testHas() { $array = ['products.desk' => ['price' => 100]]; @@ -1067,6 +1169,35 @@ public function testShuffleKeepsSameValues() $this->assertEquals($input, $shuffled); } + public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists() + { + $this->assertSame('foo', Arr::sole(['foo'])); + + $array = [ + ['name' => 'foo'], + ['name' => 'bar'], + ]; + + $this->assertSame( + ['name' => 'foo'], + Arr::sole($array, fn (array $value) => $value['name'] === 'foo') + ); + } + + public function testSoleThrowsExceptionIfNoItemsExist() + { + $this->expectException(ItemNotFoundException::class); + + Arr::sole(['foo'], fn (string $value) => $value === 'baz'); + } + + public function testSoleThrowsExceptionIfMoreThanOneItemExists() + { + $this->expectExceptionObject(new MultipleItemsFoundException(2)); + + Arr::sole(['baz', 'foo', 'baz'], fn (string $value) => $value === 'baz'); + } + public function testEmptyShuffle() { $this->assertEquals([], Arr::shuffle([])); @@ -1533,4 +1664,41 @@ public function testSelect() [], ], Arr::select($array, null)); } + + public function testReject() + { + $array = [1, 2, 3, 4, 5, 6]; + + // Test rejection behavior (removing even numbers) + $result = Arr::reject($array, function ($value) { + return $value % 2 === 0; + }); + + $this->assertEquals([ + 0 => 1, + 2 => 3, + 4 => 5, + ], $result); + + // Test key preservation with associative array + $assocArray = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]; + + $result = Arr::reject($assocArray, function ($value) { + return $value > 2; + }); + + $this->assertEquals([ + 'a' => 1, + 'b' => 2, + ], $result); + } + + public function testPartition() + { + $array = ['John', 'Jane', 'Greg']; + + $result = Arr::partition($array, fn (string $value) => str_contains($value, 'J')); + + $this->assertEquals([[0 => 'John', 1 => 'Jane'], [2 => 'Greg']], $result); + } } diff --git a/tests/Support/SupportBenchmarkTest.php b/tests/Support/SupportBenchmarkTest.php new file mode 100644 index 000000000000..f155971cd827 --- /dev/null +++ b/tests/Support/SupportBenchmarkTest.php @@ -0,0 +1,24 @@ +assertIsNumeric(Benchmark::measure(fn () => 1 + 1)); + + $this->assertIsArray(Benchmark::measure([ + 'first' => fn () => 1 + 1, + 'second' => fn () => 2 + 2, + ], 3)); + } + + public function testValue(): void + { + $this->assertIsArray(Benchmark::value(fn () => 1 + 1)); + } +} diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 93c32f416fa4..2edd11ca6718 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -1736,8 +1736,26 @@ public function testUniqueStrict($collection) #[DataProvider('collectionClassProvider')] public function testCollapse($collection) { + // Normal case: a two-dimensional array with different elements $data = new $collection([[$object1 = new stdClass], [$object2 = new stdClass]]); $this->assertEquals([$object1, $object2], $data->collapse()->all()); + + // Case including numeric and string elements + $data = new $collection([[1], [2], [3], ['foo', 'bar'], new $collection(['baz', 'boom'])]); + $this->assertEquals([1, 2, 3, 'foo', 'bar', 'baz', 'boom'], $data->collapse()->all()); + + // Case with empty two-dimensional arrays + $data = new $collection([[], [], []]); + $this->assertEquals([], $data->collapse()->all()); + + // Case with both empty arrays and arrays with elements + $data = new $collection([[], [1, 2], [], ['foo', 'bar']]); + $this->assertEquals([1, 2, 'foo', 'bar'], $data->collapse()->all()); + + // Case including collections and arrays + $collection = new $collection(['baz', 'boom']); + $data = new $collection([[1], [2], [3], ['foo', 'bar'], $collection]); + $this->assertEquals([1, 2, 3, 'foo', 'bar', 'baz', 'boom'], $data->collapse()->all()); } #[DataProvider('collectionClassProvider')] @@ -2127,6 +2145,24 @@ public function testChunkWhenGivenLessThanZero($collection) ); } + #[DataProvider('collectionClassProvider')] + public function testChunkPreservingKeys($collection) + { + $data = new $collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]); + + $this->assertEquals( + [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4], ['e' => 5]], + $data->chunk(2)->toArray() + ); + + $data = new $collection([1, 2, 3, 4, 5]); + + $this->assertEquals( + [[0 => 1, 1 => 2], [0 => 3, 1 => 4], [0 => 5]], + $data->chunk(2, false)->toArray() + ); + } + #[DataProvider('collectionClassProvider')] public function testSplitIn($collection) { @@ -2173,6 +2209,21 @@ public function testChunkWhileOnContiguouslyIncreasingIntegers($collection) $this->assertEquals([8 => 19, 9 => 20, 10 => 21], $data->last()->toArray()); } + #[DataProvider('collectionClassProvider')] + public function testChunkWhilePreservingStringKeys($collection) + { + $data = (new $collection(['a' => 1, 'b' => 1, 'c' => 2, 'd' => 2, 'e' => 3, 'f' => 3, 'g' => 3])) + ->chunkWhile(function ($current, $key, $chunk) { + return $chunk->last() === $current; + }); + + $this->assertInstanceOf($collection, $data); + $this->assertInstanceOf($collection, $data->first()); + $this->assertEquals(['a' => 1, 'b' => 1], $data->first()->toArray()); + $this->assertEquals(['c' => 2, 'd' => 2], $data->get(1)->toArray()); + $this->assertEquals(['e' => 3, 'f' => 3, 'g' => 3], $data->last()->toArray()); + } + #[DataProvider('collectionClassProvider')] public function testEvery($collection) { @@ -2335,9 +2386,11 @@ public function testImplode($collection) #[DataProvider('collectionClassProvider')] public function testImplodeModels($collection) { - $model = new class extends Model {}; + $model = new class extends Model { + }; $model->setAttribute('email', 'foo'); - $modelTwo = new class extends Model {}; + $modelTwo = new class extends Model { + }; $modelTwo->setAttribute('email', 'bar'); $data = new $collection([$model, $modelTwo]); @@ -2776,6 +2829,35 @@ public function testRangeMethod($collection) ); } + #[DataProvider('collectionClassProvider')] + public function testFromJson($collection) + { + $json = json_encode($array = ['foo' => 'bar', 'baz' => 'quz']); + + $instance = $collection::fromJson($json); + + $this->assertSame($array, $instance->toArray()); + } + + #[DataProvider('collectionClassProvider')] + public function testFromJsonWithDepth($collection) + { + $json = json_encode(['foo' => ['baz' => ['quz']], 'bar' => 'baz']); + + $instance = $collection::fromJson($json, 1); + + $this->assertEmpty($instance->toArray()); + $this->assertSame(JSON_ERROR_DEPTH, json_last_error()); + } + + #[DataProvider('collectionClassProvider')] + public function testFromJsonWithFlags($collection) + { + $instance = $collection::fromJson('{"int":99999999999999999999999}', 512, JSON_BIGINT_AS_STRING); + + $this->assertSame(['int' => '99999999999999999999999'], $instance->toArray()); + } + #[DataProvider('collectionClassProvider')] public function testConstructMakeFromObject($collection) { diff --git a/tests/Support/SupportFacadesEventTest.php b/tests/Support/SupportFacadesEventTest.php index 3a438ff08ef3..928ce440e3f0 100644 --- a/tests/Support/SupportFacadesEventTest.php +++ b/tests/Support/SupportFacadesEventTest.php @@ -3,6 +3,8 @@ namespace Illuminate\Tests\Support; use Illuminate\Cache\CacheManager; +use Illuminate\Cache\Events\CacheFlushed; +use Illuminate\Cache\Events\CacheFlushing; use Illuminate\Cache\Events\CacheMissed; use Illuminate\Cache\Events\RetrievingKey; use Illuminate\Config\Repository as ConfigRepository; @@ -87,6 +89,17 @@ public function testFakeSwapsDispatchersInResolvedCacheRepositories() Event::assertDispatched(CacheMissed::class); } + public function testCacheFlushDispatchesEvent() + { + $arrayRepository = Cache::store('array'); + Event::fake(); + + $arrayRepository->clear(); + + Event::assertDispatched(CacheFlushing::class); + Event::assertDispatched(CacheFlushed::class); + } + protected function getCacheConfig() { return [ diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index e23877584968..ee67f818a3cd 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -134,6 +134,8 @@ public function testWhen() $this->assertEquals('False', when([], 'True', 'False')); // Empty Array = Falsy $this->assertTrue(when(true, fn ($value) => $value, fn ($value) => ! $value)); // lazy evaluation $this->assertTrue(when(false, fn ($value) => $value, fn ($value) => ! $value)); // lazy evaluation + $this->assertEquals('Hello', when(fn () => true, 'Hello')); // lazy evaluation condition + $this->assertEquals('World', when(fn () => false, 'Hello', 'World')); // lazy evaluation condition } public function testFilled() diff --git a/tests/Support/SupportLazyCollectionTest.php b/tests/Support/SupportLazyCollectionTest.php index 5865c09e888b..5fe161cd9025 100644 --- a/tests/Support/SupportLazyCollectionTest.php +++ b/tests/Support/SupportLazyCollectionTest.php @@ -286,4 +286,165 @@ public function testUniqueDoubleEnumeration() $this->assertSame([1, 2], $data->all()); } + + public function testAfter() + { + $data = new LazyCollection([1, '2', 3, 4]); + + // Test finding item after value with non-strict comparison + $result = $data->after(1); + $this->assertSame('2', $result); + + // Test with strict comparison + $result = $data->after('2', true); + $this->assertSame(3, $result); + + $users = new LazyCollection([ + ['name' => 'Taylor', 'age' => 35], + ['name' => 'Jeffrey', 'age' => 45], + ['name' => 'Mohamed', 'age' => 35], + ]); + + // Test finding item after the one that matches a condition + $result = $users->after(function ($user) { + return $user['name'] === 'Jeffrey'; + }); + + $this->assertSame(['name' => 'Mohamed', 'age' => 35], $result); + } + + public function testBefore() + { + // Test finding item before value with non-strict comparison + $data = new LazyCollection([1, 2, '3', 4]); + $result = $data->before(2); + $this->assertSame(1, $result); + + // Test finding item before value with strict comparison + $result = $data->before(4, true); + $this->assertSame('3', $result); + + // Test finding item before the one that matches a callback condition + $users = new LazyCollection([ + ['name' => 'Taylor', 'age' => 35], + ['name' => 'Jeffrey', 'age' => 45], + ['name' => 'Mohamed', 'age' => 35], + ]); + $result = $users->before(function ($user) { + return $user['name'] === 'Jeffrey'; + }); + $this->assertSame(['name' => 'Taylor', 'age' => 35], $result); + } + + public function testShuffle() + { + $data = new LazyCollection([1, 2, 3, 4, 5]); + $shuffled = $data->shuffle(); + + $this->assertCount(5, $shuffled); + $this->assertEquals([1, 2, 3, 4, 5], $shuffled->sort()->values()->all()); + + // Test shuffling associative array maintains key-value pairs + $users = new LazyCollection([ + 'first' => ['name' => 'Taylor'], + 'second' => ['name' => 'Jeffrey'], + ]); + $shuffled = $users->shuffle(); + + $this->assertCount(2, $shuffled); + $this->assertTrue($shuffled->contains('name', 'Taylor')); + $this->assertTrue($shuffled->contains('name', 'Jeffrey')); + } + + public function testCollapseWithKeys() + { + $collection = new LazyCollection([ + ['a' => 1, 'b' => 2], + ['c' => 3, 'd' => 4], + ]); + $collapsed = $collection->collapseWithKeys(); + + $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], $collapsed->all()); + + $collection = new LazyCollection([ + ['a' => 1], + new LazyCollection(['b' => 2]), + ]); + $collapsed = $collection->collapseWithKeys(); + + $this->assertEquals(['a' => 1, 'b' => 2], $collapsed->all()); + } + + public function testContainsOneItem() + { + $collection = new LazyCollection([5]); + $this->assertTrue($collection->containsOneItem()); + + $emptyCollection = new LazyCollection([]); + $this->assertFalse($emptyCollection->containsOneItem()); + + $multipleCollection = new LazyCollection([1, 2, 3]); + $this->assertFalse($multipleCollection->containsOneItem()); + } + + public function testDoesntContain() + { + $collection = new LazyCollection([1, 2, 3, 4, 5]); + + $this->assertTrue($collection->doesntContain(10)); + $this->assertFalse($collection->doesntContain(3)); + $this->assertTrue($collection->doesntContain('value', '>', 10)); + $this->assertTrue($collection->doesntContain(function ($value) { + return $value > 10; + })); + + $users = new LazyCollection([ + [ + 'name' => 'Taylor', + 'role' => 'developer', + ], + [ + 'name' => 'Jeffrey', + 'role' => 'designer', + ], + ]); + + $this->assertTrue($users->doesntContain('name', 'Adam')); + $this->assertFalse($users->doesntContain('name', 'Taylor')); + } + + public function testDot() + { + $collection = new LazyCollection([ + 'foo' => [ + 'bar' => 'baz', + ], + 'user' => [ + 'name' => 'Taylor', + 'profile' => [ + 'age' => 30, + ], + ], + 'users' => [ + 0 => [ + 'name' => 'Taylor', + ], + 1 => [ + 'name' => 'Jeffrey', + ], + ], + ]); + + $dotted = $collection->dot(); + + $expected = [ + 'foo.bar' => 'baz', + 'user.name' => 'Taylor', + 'user.profile.age' => 30, + 'users.0.name' => 'Taylor', + 'users.1.name' => 'Jeffrey', + ]; + + $this->assertEquals($expected, $dotted->all()); + } } diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index b72143bde68e..8f7567433baa 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -311,9 +311,18 @@ public function testSummarize() public function testPairs() { - $this->assertSame([[1, 10], [11, 20], [21, 25]], Number::pairs(25, 10)); - $this->assertSame([[0, 10], [10, 20], [20, 25]], Number::pairs(25, 10, 0)); - $this->assertSame([[0, 2.5], [2.5, 5.0], [5.0, 7.5], [7.5, 10.0]], Number::pairs(10, 2.5, 0)); + $this->assertSame([[0, 10], [10, 20], [20, 25]], Number::pairs(25, 10, 0, 0)); + $this->assertSame([[0, 9], [10, 19], [20, 25]], Number::pairs(25, 10, 0, 1)); + $this->assertSame([[1, 11], [11, 21], [21, 25]], Number::pairs(25, 10, 1, 0)); + $this->assertSame([[1, 10], [11, 20], [21, 25]], Number::pairs(25, 10, 1, 1)); + $this->assertSame([[0, 1000], [1000, 2000], [2000, 2500]], Number::pairs(2500, 1000, 0, 0)); + $this->assertSame([[0, 999], [1000, 1999], [2000, 2500]], Number::pairs(2500, 1000, 0, 1)); + $this->assertSame([[1, 1001], [1001, 2001], [2001, 2500]], Number::pairs(2500, 1000, 1, 0)); + $this->assertSame([[1, 1000], [1001, 2000], [2001, 2500]], Number::pairs(2500, 1000, 1, 1)); + $this->assertSame([[0, 2.5], [2.5, 5.0], [5.0, 7.5], [7.5, 10.0]], Number::pairs(10, 2.5, 0, 0)); + $this->assertSame([[0, 2.0], [2.5, 4.5], [5.0, 7.0], [7.5, 9.5]], Number::pairs(10, 2.5, 0, 0.5)); + $this->assertSame([[0.5, 3.0], [3.0, 5.5], [5.5, 8.0], [8.0, 10]], Number::pairs(10, 2.5, 0.5, 0)); + $this->assertSame([[0.5, 2.5], [3.0, 5.0], [5.5, 7.5], [8.0, 10.0]], Number::pairs(10, 2.5, 0.5, 0.5)); } public function testTrim() diff --git a/tests/Support/SupportReflectorTest.php b/tests/Support/SupportReflectorTest.php index 2933ff090795..67e438361d8f 100644 --- a/tests/Support/SupportReflectorTest.php +++ b/tests/Support/SupportReflectorTest.php @@ -75,6 +75,63 @@ public function testIsCallable() $this->assertFalse(Reflector::isCallable(['TotallyMissingClass', 'foo'])); $this->assertTrue(Reflector::isCallable(['TotallyMissingClass', 'foo'], true)); } + + public function testGetClassAttributes() + { + require_once __DIR__.'/Fixtures/ClassesWithAttributes.php'; + + $this->assertSame([], Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class)->toArray()); + + $this->assertSame( + [Fixtures\ChildClass::class => [], Fixtures\ParentClass::class => []], + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class, true)->toArray() + ); + + $this->assertSame( + ['quick', 'brown', 'fox'], + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->map->string->all() + ); + + $this->assertSame( + ['quick', 'brown', 'fox', 'lazy', 'dog'], + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->flatten()->map->string->all() + ); + + $this->assertSame(7, Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\NumAttr::class)->sum->number); + $this->assertSame(12, Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\NumAttr::class, true)->flatten()->sum->number); + $this->assertSame(5, Reflector::getClassAttributes(Fixtures\ParentClass::class, Fixtures\NumAttr::class)->sum->number); + $this->assertSame(5, Reflector::getClassAttributes(Fixtures\ParentClass::class, Fixtures\NumAttr::class, true)->flatten()->sum->number); + + $this->assertSame( + [Fixtures\ChildClass::class, Fixtures\ParentClass::class], + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->keys()->all() + ); + + $this->assertContainsOnlyInstancesOf( + Fixtures\StrAttr::class, + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->all() + ); + + $this->assertContainsOnlyInstancesOf( + Fixtures\StrAttr::class, + Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->flatten()->all() + ); + } + + public function testGetClassAttribute() + { + require_once __DIR__.'/Fixtures/ClassesWithAttributes.php'; + + $this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class)); + $this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class, true)); + $this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\ParentOnlyAttr::class)); + $this->assertInstanceOf(Fixtures\ParentOnlyAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\ParentOnlyAttr::class, true)); + $this->assertInstanceOf(Fixtures\StrAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class)); + $this->assertInstanceOf(Fixtures\StrAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)); + $this->assertSame('quick', Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->string); + $this->assertSame('quick', Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->string); + $this->assertSame('lazy', Reflector::getClassAttribute(Fixtures\ParentClass::class, Fixtures\StrAttr::class)->string); + } } class A diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 8d99606015d5..b93bb509cf13 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -543,6 +543,52 @@ public function testIs() $this->assertTrue(Str::is([null], null)); } + public function testIsWithMultilineStrings() + { + $this->assertFalse(Str::is('/', "/\n")); + $this->assertTrue(Str::is('/*', "/\n")); + $this->assertTrue(Str::is('*/*', "/\n")); + $this->assertTrue(Str::is('*/*', "\n/\n")); + + $this->assertTrue(Str::is('*', "\n")); + $this->assertTrue(Str::is('*', "\n\n")); + $this->assertFalse(Str::is('', "\n")); + $this->assertFalse(Str::is('', "\n\n")); + + $multilineValue = <<<'VALUE' + assertTrue(Str::is($multilineValue, $multilineValue)); + $this->assertTrue(Str::is('*', $multilineValue)); + $this->assertTrue(Str::is("*namespace Illuminate\Tests\*", $multilineValue)); + $this->assertFalse(Str::is("namespace Illuminate\Tests\*", $multilineValue)); + $this->assertFalse(Str::is("*namespace Illuminate\Tests", $multilineValue)); + $this->assertTrue(Str::is('assertTrue(Str::is("assertFalse(Str::is('use Exception;', $multilineValue)); + $this->assertFalse(Str::is('use Exception;*', $multilineValue)); + $this->assertTrue(Str::is('*use Exception;', $multilineValue)); + + $this->assertTrue(Str::is("assertTrue(Str::is(<<<'PATTERN' + assertTrue(Str::is(<<<'PATTERN' + assertTrue(Str::isUrl('https://laravel.com')); @@ -562,6 +608,12 @@ public function testIsUuidWithInvalidUuid($uuid) $this->assertFalse(Str::isUuid($uuid)); } + #[DataProvider('uuidVersionList')] + public function testIsUuidWithVersion($uuid, $version, $passes) + { + $this->assertSame(Str::isUuid($uuid, $version), $passes); + } + public function testIsJson() { $this->assertTrue(Str::isJson('1')); @@ -1286,6 +1338,60 @@ public static function invalidUuidList() ]; } + public static function uuidVersionList() + { + return [ + ['00000000-0000-0000-0000-000000000000', null, true], + ['00000000-0000-0000-0000-000000000000', 0, true], + ['00000000-0000-0000-0000-000000000000', 1, false], + ['00000000-0000-0000-0000-000000000000', 42, false], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', null, true], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 1, true], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 4, false], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 42, false], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', null, true], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 1, false], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 2, true], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 42, false], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', null, true], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 1, false], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 3, true], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 42, false], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', null, true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 1, false], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 4, true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 42, false], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', null, true], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 1, false], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 5, true], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 42, false], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', null, true], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 1, false], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 6, true], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 42, false], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', null, true], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 1, false], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 7, true], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 42, false], + ['07e80a1f-1629-831f-811f-c595103c91b5', null, true], + ['07e80a1f-1629-831f-811f-c595103c91b5', 1, false], + ['07e80a1f-1629-831f-811f-c595103c91b5', 8, true], + ['07e80a1f-1629-831f-811f-c595103c91b5', 42, false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', null, true], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 1, false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 42, false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'max', true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', null, true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 1, false], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 4, true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 42, false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', null, false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 1, false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 4, false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 42, false], + ]; + } + public static function strContainsProvider() { return [ @@ -1652,6 +1758,62 @@ public function testChopEnd() $this->assertSame($expected, Str::chopEnd($subject, $needle)); } } + + public function testReplaceMatches() + { + // Test basic string replacement + $this->assertSame('foo bar bar', Str::replaceMatches('/baz/', 'bar', 'foo baz bar')); + $this->assertSame('foo baz baz', Str::replaceMatches('/404/', 'found', 'foo baz baz')); + + // Test with array of patterns + $this->assertSame('foo XXX YYY', Str::replaceMatches(['/bar/', '/baz/'], ['XXX', 'YYY'], 'foo bar baz')); + + // Test with callback + $result = Str::replaceMatches('/ba(.)/', function ($match) { + return 'ba'.strtoupper($match[1]); + }, 'foo baz bar'); + + $this->assertSame('foo baZ baR', $result); + + $result = Str::replaceMatches('/(\d+)/', function ($match) { + return $match[1] * 2; + }, 'foo 123 bar 456'); + + $this->assertSame('foo 246 bar 912', $result); + + // Test with limit parameter + $this->assertSame('foo baz baz', Str::replaceMatches('/ba(.)/', 'ba$1', 'foo baz baz', 1)); + + $result = Str::replaceMatches('/ba(.)/', function ($match) { + return 'ba'.strtoupper($match[1]); + }, 'foo baz baz bar', 1); + + $this->assertSame('foo baZ baz bar', $result); + } + + public function testPluralPascal(): void + { + // Test basic functionality with default count + $this->assertSame('UserGroups', Str::pluralPascal('UserGroup')); + $this->assertSame('ProductCategories', Str::pluralPascal('ProductCategory')); + + // Test with different count values and array + $this->assertSame('UserGroups', Str::pluralPascal('UserGroup', 0)); // plural + $this->assertSame('UserGroup', Str::pluralPascal('UserGroup', 1)); // singular + $this->assertSame('UserGroups', Str::pluralPascal('UserGroup', 2)); // plural + $this->assertSame('UserGroups', Str::pluralPascal('UserGroup', [])); // plural (empty array count is 0) + + // Test with Countable + $countable = new class implements \Countable + { + public function count(): int + { + return 3; + } + }; + + $this->assertSame('UserGroups', Str::pluralPascal('UserGroup', $countable)); + } } class StringableObjectStub diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 493bef738972..c6fb80f01ddf 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -827,6 +827,38 @@ public function testIs() $this->assertFalse($this->stringable('test')->is([])); } + public function testIsWithMultilineStrings() + { + $this->assertFalse($this->stringable("/\n")->is('/')); + $this->assertTrue($this->stringable("/\n")->is('/*')); + $this->assertTrue($this->stringable("/\n")->is('*/*')); + $this->assertTrue($this->stringable("\n/\n")->is('*/*')); + + $this->assertTrue($this->stringable("\n")->is('*')); + $this->assertTrue($this->stringable("\n\n")->is('*')); + $this->assertFalse($this->stringable("\n")->is('')); + $this->assertFalse($this->stringable("\n\n")->is('')); + + $multilineValue = <<<'VALUE' + assertTrue($this->stringable($multilineValue)->is($multilineValue)); + $this->assertTrue($this->stringable($multilineValue)->is('*')); + $this->assertTrue($this->stringable($multilineValue)->is("*namespace Illuminate\Tests\*")); + $this->assertFalse($this->stringable($multilineValue)->is("namespace Illuminate\Tests\*")); + $this->assertFalse($this->stringable($multilineValue)->is("*namespace Illuminate\Tests")); + $this->assertTrue($this->stringable($multilineValue)->is('assertTrue($this->stringable($multilineValue)->is("assertFalse($this->stringable($multilineValue)->is('use Exception;')); + $this->assertFalse($this->stringable($multilineValue)->is('use Exception;*')); + $this->assertTrue($this->stringable($multilineValue)->is('*use Exception;')); + } + public function testKebab() { $this->assertSame('laravel-php-framework', (string) $this->stringable('LaravelPhpFramework')->kebab()); diff --git a/tests/Support/SupportUriTest.php b/tests/Support/SupportUriTest.php index 95e05ea8791e..1f86588a1eb8 100644 --- a/tests/Support/SupportUriTest.php +++ b/tests/Support/SupportUriTest.php @@ -126,4 +126,109 @@ public function test_decoding_the_entire_uri() $this->assertEquals('https://laravel.com/docs/11.x/installation?tags[0]=first&tags[1]=second', $uri->decode()); } + + public function test_with_query_if_missing() + { + // Test adding new parameters while preserving existing ones + $uri = Uri::of('https://laravel.com?existing=value'); + + $uri = $uri->withQueryIfMissing([ + 'new' => 'parameter', + 'existing' => 'new_value', + ]); + + $this->assertEquals('existing=value&new=parameter', $uri->query()->decode()); + + // Test adding complex nested arrays to empty query string + $uri = Uri::of('https://laravel.com'); + + $uri = $uri->withQueryIfMissing([ + 'name' => 'Taylor', + 'role' => [ + 'title' => 'Developer', + 'focus' => 'PHP', + ], + 'tags' => [ + 'person', + 'employee', + ], + ]); + + $this->assertEquals('name=Taylor&role[title]=Developer&role[focus]=PHP&tags[0]=person&tags[1]=employee', $uri->query()->decode()); + + // Test partial array merging and preserving indexed arrays + $uri = Uri::of('https://laravel.com?name=Taylor&tags[0]=person'); + + $uri = $uri->withQueryIfMissing([ + 'name' => 'Changed', + 'age' => 38, + 'tags' => ['should', 'not', 'change'], + ]); + + $this->assertEquals('name=Taylor&tags[0]=person&age=38', $uri->query()->decode()); + $this->assertEquals(['name' => 'Taylor', 'tags' => ['person'], 'age' => 38], $uri->query()->all()); + + $uri = Uri::of('https://laravel.com?user[name]=Taylor'); + + $uri = $uri->withQueryIfMissing([ + 'user' => [ + 'name' => 'Should Not Change', + 'age' => 38, + ], + 'settings' => [ + 'theme' => 'dark', + ], + ]); + $this->assertEquals([ + 'user' => [ + 'name' => 'Taylor', + ], + 'settings' => [ + 'theme' => 'dark', + ], + ], $uri->query()->all()); + } + + public function test_with_query_prevents_empty_query_string() + { + $uri = Uri::of('https://laravel.com'); + + $this->assertEquals('https://laravel.com', (string) $uri); + $this->assertEquals('https://laravel.com', (string) $uri->withQuery([])); + } + + public function test_path_segments() + { + $uri = Uri::of('https://laravel.com'); + + $this->assertEquals([], $uri->pathSegments()->toArray()); + + $uri = Uri::of('https://laravel.com/one/two/three'); + + $this->assertEquals(['one', 'two', 'three'], $uri->pathSegments()->toArray()); + $this->assertEquals('one', $uri->pathSegments()->first()); + + $uri = Uri::of('https://laravel.com/one/two/three?foo=bar'); + + $this->assertEquals(3, $uri->pathSegments()->count()); + + $uri = Uri::of('https://laravel.com/one/two/three/?foo=bar'); + + $this->assertEquals(3, $uri->pathSegments()->count()); + + $uri = Uri::of('https://laravel.com/one/two/three/#foo=bar'); + + $this->assertEquals(3, $uri->pathSegments()->count()); + } + + public function test_macroable() + { + Uri::macro('myMacro', function () { + return $this->withPath('foobar'); + }); + + $uri = new Uri('https://laravel.com/'); + + $this->assertSame('https://laravel.com/foobar', (string) $uri->myMacro()); + } } diff --git a/tests/Testing/Concerns/InteractsWithDatabaseTest.php b/tests/Testing/Concerns/InteractsWithDatabaseTest.php index 9b9168930c61..eb6b7cd42655 100644 --- a/tests/Testing/Concerns/InteractsWithDatabaseTest.php +++ b/tests/Testing/Concerns/InteractsWithDatabaseTest.php @@ -2,13 +2,8 @@ namespace Illuminate\Tests\Testing\Concerns; -use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; -use Illuminate\Database\Query\Grammars\MariaDbGrammar; -use Illuminate\Database\Query\Grammars\MySqlGrammar; -use Illuminate\Database\Query\Grammars\PostgresGrammar; -use Illuminate\Database\Query\Grammars\SQLiteGrammar; -use Illuminate\Database\Query\Grammars\SqlServerGrammar; use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Facade; @@ -30,7 +25,7 @@ protected function tearDown(): void public function testCastToJsonSqlite() { - $grammar = new SQLiteGrammar(); + $grammar = 'SQLite'; $this->assertEquals(<<<'TEXT' '["foo","bar"]' @@ -53,7 +48,7 @@ public function testCastToJsonSqlite() public function testCastToJsonPostgres() { - $grammar = new PostgresGrammar(); + $grammar = 'Postgres'; $this->assertEquals(<<<'TEXT' '["foo","bar"]' @@ -76,7 +71,7 @@ public function testCastToJsonPostgres() public function testCastToJsonSqlServer() { - $grammar = new SqlServerGrammar(); + $grammar = 'SqlServer'; $this->assertEquals(<<<'TEXT' json_query('["foo","bar"]') @@ -99,7 +94,7 @@ public function testCastToJsonSqlServer() public function testCastToJsonMySql() { - $grammar = new MySqlGrammar(); + $grammar = 'MySql'; $this->assertEquals(<<<'TEXT' cast('["foo","bar"]' as json) @@ -122,7 +117,7 @@ public function testCastToJsonMySql() public function testCastToJsonMariaDb() { - $grammar = new MariaDbGrammar(); + $grammar = 'MariaDb'; $this->assertEquals(<<<'TEXT' json_query('["foo","bar"]', '$') @@ -145,7 +140,9 @@ public function testCastToJsonMariaDb() protected function castAsJson($value, $grammar) { - $connection = m::mock(ConnectionInterface::class); + $connection = m::mock(Connection::class); + $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$grammar.'Grammar'; + $grammar = new $grammarClass($connection); $connection->shouldReceive('getQueryGrammar')->andReturn($grammar); diff --git a/tests/Testing/Console/RouteListCommandTest.php b/tests/Testing/Console/RouteListCommandTest.php index 61723bb9bce1..c6deeeb8df21 100644 --- a/tests/Testing/Console/RouteListCommandTest.php +++ b/tests/Testing/Console/RouteListCommandTest.php @@ -9,8 +9,10 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Facade; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('filesystems.disks.local.serve', false)] class RouteListCommandTest extends TestCase { use InteractsWithDeprecationHandling; diff --git a/tests/Testing/Fluent/AssertTest.php b/tests/Testing/Fluent/AssertTest.php index c4771c583285..b248977c36e0 100644 --- a/tests/Testing/Fluent/AssertTest.php +++ b/tests/Testing/Fluent/AssertTest.php @@ -580,6 +580,72 @@ public function testAssertWhereFailsUsingBackedEnum() $assert->where('bar', BackedEnum::test_empty); } + public function testAssertWhereNullMatchesValue() + { + $assert = AssertableJson::fromArray([ + 'bar' => null, + ]); + + $assert->whereNull('bar'); + } + + public function testAssertWhereNullFailsWhenNotNull() + { + $assert = AssertableJson::fromArray([ + 'bar' => 'value', + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Property [bar] should be null.'); + + $assert->whereNull('bar'); + } + + public function testAssertWhereNullFailsWhenMissing() + { + $assert = AssertableJson::fromArray([ + 'bar' => 'value', + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Property [baz] does not exist.'); + + $assert->whereNull('baz'); + } + + public function testAssertWhereNotNullMatchesValue() + { + $assert = AssertableJson::fromArray([ + 'bar' => 'value', + ]); + + $assert->whereNotNull('bar'); + } + + public function testAssertWhereNotNullFailsWhenNull() + { + $assert = AssertableJson::fromArray([ + 'bar' => null, + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Property [bar] should not be null.'); + + $assert->whereNotNull('bar'); + } + + public function testAssertWhereNotNullFailsWhenMissing() + { + $assert = AssertableJson::fromArray([ + 'bar' => 'value', + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Property [baz] does not exist.'); + + $assert->whereNotNull('baz'); + } + public function testAssertWhereContainsFailsWithEmptyValue() { $assert = AssertableJson::fromArray([]); diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 9de88ddff3f3..cae497bdd133 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -1437,6 +1437,27 @@ public function testAssertJsonPathWithClosureCanFail() $response->assertJsonPath('data.foo', fn ($value) => $value === null); } + public function testAssertJsonPathWithEnum() + { + $response = TestResponse::fromBaseResponse(new Response([ + 'data' => ['status' => 'booked'], + ])); + + $response->assertJsonPath('data.status', TestStatus::Booked); + } + + public function testAssertJsonPathWithEnumCanFail() + { + $response = TestResponse::fromBaseResponse(new Response([ + 'data' => ['status' => 'failed'], + ])); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that two strings are identical.'); + + $response->assertJsonPath('data.status', TestStatus::Booked); + } + public function testAssertJsonPathCanonicalizing() { $response = TestResponse::fromBaseResponse(new Response(new JsonSerializableSingleResourceStub)); @@ -2954,3 +2975,8 @@ class AnotherTestModel extends Model { protected $guarded = []; } + +enum TestStatus: string +{ + case Booked = 'booked'; +} diff --git a/tests/Translation/TranslationFileLoaderTest.php b/tests/Translation/TranslationFileLoaderTest.php index b7e74f18bfaa..dd386932f79d 100755 --- a/tests/Translation/TranslationFileLoaderTest.php +++ b/tests/Translation/TranslationFileLoaderTest.php @@ -146,6 +146,20 @@ public function testLoadMethodWithNamespacesProperlyCallsLoaderAndLoadsLocalOver $this->assertEquals(['foo' => 'override-2', 'baz' => 'boom-2'], $loader->load('en', 'foo', 'namespace')); } + public function testLoadMethodWithNamespacesProperlyCallsLoaderAndLoadsLocalOverridesWithMultiplePathsWithMissingKey() + { + $loader = new FileLoader($files = m::mock(Filesystem::class), [__DIR__, __DIR__.'/second']); + $files->shouldReceive('exists')->once()->with('test-namespace-dir/en/foo.php')->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/vendor/namespace/en/foo.php')->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/second/vendor/namespace/en/foo.php')->andReturn(true); + $files->shouldReceive('getRequire')->once()->with('test-namespace-dir/en/foo.php')->andReturn(['foo' => 'bar']); + $files->shouldReceive('getRequire')->once()->with(__DIR__.'/vendor/namespace/en/foo.php')->andReturn(['foo' => 'override', 'baz' => 'boom']); + $files->shouldReceive('getRequire')->once()->with(__DIR__.'/second/vendor/namespace/en/foo.php')->andReturn(['baz' => 'boom-2']); + $loader->addNamespace('namespace', 'test-namespace-dir'); + + $this->assertEquals(['foo' => 'override', 'baz' => 'boom-2'], $loader->load('en', 'foo', 'namespace')); + } + public function testEmptyArraysReturnedWhenFilesDontExist() { $loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__); diff --git a/tests/Validation/ValidationAnyOfRuleTest.php b/tests/Validation/ValidationAnyOfRuleTest.php new file mode 100644 index 000000000000..f0806182c693 --- /dev/null +++ b/tests/Validation/ValidationAnyOfRuleTest.php @@ -0,0 +1,376 @@ + $rule]; + $requiredIdRule = ['id' => ['required', $rule]]; + + $validator = new Validator(resolve('translator'), [ + 'id' => 'taylor@laravel.com', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [], $requiredIdRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '3c8ff5cb-4bc1-457b-a477-1833c477b254', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => null, + ], $idRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '', + ], $requiredIdRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => 'abc', + ], $idRule); + $this->assertFalse($validator->passes()); + } + + public function testBasicStringValidation() + { + $rule = Rule::anyOf([ + 'required|uuid:4', + 'required|email', + ]); + $idRule = ['id' => $rule]; + $requiredIdRule = ['id' => ['required', $rule]]; + + $validator = new Validator(resolve('translator'), [ + 'id' => 'test@example.com', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [], $requiredIdRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '3c8ff5cb-4bc1-457b-a477-1833c477b254', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => null, + ], $idRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '', + ], $idRule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => '', + ], $requiredIdRule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'id' => 'abc', + ], $idRule); + $this->assertFalse($validator->passes()); + } + + public function testTaggedUnionObjects() + { + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => TaggedUnionDiscriminatorType::EMAIL->value, + 'email' => 'taylor@laravel.com', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => TaggedUnionDiscriminatorType::EMAIL->value, + 'email' => 'invalid-email', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => TaggedUnionDiscriminatorType::URL->value, + 'url' => 'http://laravel.com', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => TaggedUnionDiscriminatorType::URL->value, + 'url' => 'not-a-url', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => TaggedUnionDiscriminatorType::EMAIL->value, + 'url' => 'url-should-not-be-present-with-email-discriminator', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'data' => [ + 'type' => 'doesnt-exist', + 'email' => 'taylor@laravel.com', + ], + ], ['data' => Rule::anyOf($this->taggedUnionRules)]); + $this->assertFalse($validator->passes()); + } + + public function testNestedValidation() + { + $validator = new Validator(resolve('translator'), [ + 'user' => [ + 'identifier' => 1, + 'properties' => [ + 'name' => 'Taylor', + 'surname' => 'Otwell', + ], + ], + ], $this->nestedRules); + $this->assertTrue($validator->passes()); + $validator->setRules($this->dotNotationNestedRules); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'user' => [ + 'identifier' => 'taylor@laravel.com', + 'properties' => [ + 'bio' => 'biography', + 'name' => 'Taylor', + 'surname' => 'Otwell', + ], + ], + ], $this->nestedRules); + $this->assertTrue($validator->passes()); + $validator->setRules($this->dotNotationNestedRules); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'user' => [ + 'identifier' => 'taylor@laravel.com', + 'properties' => [ + 'name' => null, + 'surname' => 'Otwell', + ], + ], + ], $this->nestedRules); + $this->assertFalse($validator->passes()); + $validator->setRules($this->dotNotationNestedRules); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'user' => [ + 'properties' => [ + 'name' => 'Taylor', + 'surname' => 'Otwell', + ], + ], + ], $this->nestedRules); + $this->assertFalse($validator->passes()); + $validator->setRules($this->dotNotationNestedRules); + $this->assertFalse($validator->passes()); + } + + public function testStarRuleSimple() + { + $rule = [ + 'persons.*.age' => ['required', Rule::anyOf([ + ['min:10'], + ['integer'], + ])], + ]; + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['age' => 12], + ['age' => 'foobar'], + ], + ], $rule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['age' => 'foobarbazqux'], + ['month' => 12], + ], + ], $rule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['age' => 12], + ['age' => 'foobarbazqux'], + ], + ], $rule); + $this->assertTrue($validator->passes()); + } + + public function testStarRuleNested() + { + $rule = [ + 'persons.*.birth' => ['required', Rule::anyOf([ + ['year' => 'required|integer'], + 'required|min:10', + ])], + ]; + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['age' => ['year' => 12]], + ], + ], $rule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['birth' => ['month' => 12]], + ], + ], $rule); + $this->assertFalse($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['birth' => ['year' => 12]], + ], + ], $rule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['birth' => 'foobarbazqux'], + ['birth' => [ + 'year' => 12, + ]], + ], + ], $rule); + $this->assertTrue($validator->passes()); + + $validator = new Validator(resolve('translator'), [ + 'persons' => [ + ['birth' => 'foobar'], + ['birth' => [ + 'year' => 12, + ]], + ], + ], $rule); + $this->assertFalse($validator->passes()); + } + + protected function setUpRuleSets() + { + $this->taggedUnionRules = [ + [ + 'type' => ['required', Rule::in([TaggedUnionDiscriminatorType::EMAIL])], + 'email' => ['required', 'email:rfc'], + ], + [ + 'type' => ['required', Rule::in([TaggedUnionDiscriminatorType::URL])], + 'url' => ['required', 'url:http,https'], + ], + ]; + + // Using AnyOf as nesting feature + $this->nestedRules = [ + 'user' => Rule::anyOf([ + [ + 'identifier' => ['required', Rule::anyOf([ + 'email:rfc', + 'integer', + ])], + 'properties' => ['required', Rule::anyOf([ + [ + 'bio' => 'nullable', + 'name' => 'required', + 'surname' => 'required', + ], + ])], + ], + ]), + ]; + + $this->dotNotationNestedRules = [ + 'user.identifier' => ['required', Rule::anyOf([ + 'email:rfc', + 'integer', + ])], + 'user.properties.bio' => 'nullable', + 'user.properties.name' => 'required', + 'user.properties.surname' => 'required', + ]; + } + + protected function setUp(): void + { + parent::setUp(); + + $container = Container::getInstance(); + $container->bind('translator', function () { + return new Translator( + new ArrayLoader, + 'en' + ); + }); + + Facade::setFacadeApplication($container); + (new ValidationServiceProvider($container))->register(); + + $this->setUpRuleSets(); + } + + protected function tearDown(): void + { + Container::setInstance(null); + Facade::clearResolvedInstances(); + Facade::setFacadeApplication(null); + } +} diff --git a/tests/Validation/ValidationArrayRuleTest.php b/tests/Validation/ValidationArrayRuleTest.php index 9df05e81430a..1057faa7adf1 100644 --- a/tests/Validation/ValidationArrayRuleTest.php +++ b/tests/Validation/ValidationArrayRuleTest.php @@ -18,6 +18,9 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('array', (string) $rule); + $rule = Rule::array([]); + $this->assertSame('array', (string) $rule); + $rule = Rule::array('key_1', 'key_2', 'key_3'); $this->assertSame('array:key_1,key_2,key_3', (string) $rule); @@ -37,6 +40,12 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule = Rule::array([ArrayKeysBacked::key_1, ArrayKeysBacked::key_2, ArrayKeysBacked::key_3]); $this->assertSame('array:key_1,key_2,key_3', (string) $rule); + + $rule = Rule::array(['key_1', 'key_1']); + $this->assertSame('array:key_1,key_1', (string) $rule); + + $rule = Rule::array([1, 2, 3]); + $this->assertSame('array:1,2,3', (string) $rule); } public function testArrayValidation() @@ -46,6 +55,18 @@ public function testArrayValidation() $v = new Validator($trans, ['foo' => 'not an array'], ['foo' => Rule::array()]); $this->assertTrue($v->fails()); + $v = new Validator($trans, ['foo' => (object) ['key_1' => 'bar']], ['foo' => Rule::array()]); + $this->assertTrue($v->fails()); + + $v = new Validator($trans, ['foo' => null], ['foo' => ['nullable', Rule::array()]]); + $this->assertTrue($v->passes()); + + $v = new Validator($trans, ['foo' => []], ['foo' => Rule::array()]); + $this->assertTrue($v->passes()); + + $v = new Validator($trans, ['foo' => ['key_1' => []]], ['foo' => Rule::array(['key_1'])]); + $this->assertTrue($v->passes()); + $v = new Validator($trans, ['foo' => ['bar']], ['foo' => (string) Rule::array()]); $this->assertTrue($v->passes()); diff --git a/tests/Validation/ValidationDatabasePresenceVerifierTest.php b/tests/Validation/ValidationDatabasePresenceVerifierTest.php index 96f484499059..cae7acd00bc8 100644 --- a/tests/Validation/ValidationDatabasePresenceVerifierTest.php +++ b/tests/Validation/ValidationDatabasePresenceVerifierTest.php @@ -60,4 +60,18 @@ public function testBasicCountWithClosures() $this->assertEquals(100, $verifier->getCount('table', 'column', 'value', null, null, $extra)); } + + public function testGetCountWithValidExcludeId() + { + $verifier = new DatabasePresenceVerifier($db = m::mock(ConnectionResolverInterface::class)); + $verifier->setConnection('connection'); + $db->shouldReceive('connection')->once()->with('connection')->andReturn($conn = m::mock(stdClass::class)); + $conn->shouldReceive('table')->once()->with('table')->andReturn($builder = m::mock(stdClass::class)); + $builder->shouldReceive('useWritePdo')->once()->andReturn($builder); + $builder->shouldReceive('where')->with('column', '=', 'value')->andReturn($builder); + $builder->shouldReceive('where')->with('id', '<>', 123)->andReturn($builder); + $builder->shouldReceive('count')->once()->andReturn(100); + + $this->assertEquals(100, $verifier->getCount('table', 'column', 'value', 123, 'id', [])); + } } diff --git a/tests/Validation/ValidationDateRuleTest.php b/tests/Validation/ValidationDateRuleTest.php index 1db535b0da5d..04f99784839c 100644 --- a/tests/Validation/ValidationDateRuleTest.php +++ b/tests/Validation/ValidationDateRuleTest.php @@ -24,7 +24,7 @@ public function testDefaultDateRule() public function testDateFormatRule() { $rule = Rule::date()->format('d/m/Y'); - $this->assertEquals('date|date_format:d/m/Y', (string) $rule); + $this->assertEquals('date_format:d/m/Y', (string) $rule); } public function testAfterTodayRule() @@ -49,30 +49,45 @@ public function testAfterSpecificDateRule() { $rule = Rule::date()->after(Carbon::parse('2024-01-01')); $this->assertEquals('date|after:2024-01-01', (string) $rule); + + $rule = Rule::date()->format('d/m/Y')->after(Carbon::parse('2024-01-01')); + $this->assertEquals('date_format:d/m/Y|after:01/01/2024', (string) $rule); } public function testBeforeSpecificDateRule() { $rule = Rule::date()->before(Carbon::parse('2024-01-01')); $this->assertEquals('date|before:2024-01-01', (string) $rule); + + $rule = Rule::date()->format('d/m/Y')->before(Carbon::parse('2024-01-01')); + $this->assertEquals('date_format:d/m/Y|before:01/01/2024', (string) $rule); } public function testAfterOrEqualSpecificDateRule() { $rule = Rule::date()->afterOrEqual(Carbon::parse('2024-01-01')); $this->assertEquals('date|after_or_equal:2024-01-01', (string) $rule); + + $rule = Rule::date()->format('d/m/Y')->afterOrEqual(Carbon::parse('2024-01-01')); + $this->assertEquals('date_format:d/m/Y|after_or_equal:01/01/2024', (string) $rule); } public function testBeforeOrEqualSpecificDateRule() { $rule = Rule::date()->beforeOrEqual(Carbon::parse('2024-01-01')); $this->assertEquals('date|before_or_equal:2024-01-01', (string) $rule); + + $rule = Rule::date()->format('d/m/Y')->beforeOrEqual(Carbon::parse('2024-01-01')); + $this->assertEquals('date_format:d/m/Y|before_or_equal:01/01/2024', (string) $rule); } public function testBetweenDatesRule() { $rule = Rule::date()->between(Carbon::parse('2024-01-01'), Carbon::parse('2024-02-01')); $this->assertEquals('date|after:2024-01-01|before:2024-02-01', (string) $rule); + + $rule = Rule::date()->format('d/m/Y')->between(Carbon::parse('2024-01-01'), Carbon::parse('2024-02-01')); + $this->assertEquals('date_format:d/m/Y|after:01/01/2024|before:01/02/2024', (string) $rule); } public function testBetweenOrEqualDatesRule() @@ -87,7 +102,7 @@ public function testChainedRules() ->format('Y-m-d') ->after('2024-01-01 00:00:00') ->before('2025-01-01 00:00:00'); - $this->assertEquals('date|date_format:Y-m-d|after:2024-01-01 00:00:00|before:2025-01-01 00:00:00', (string) $rule); + $this->assertEquals('date_format:Y-m-d|after:2024-01-01 00:00:00|before:2025-01-01 00:00:00', (string) $rule); $rule = Rule::date() ->format('Y-m-d') @@ -97,7 +112,7 @@ public function testChainedRules() ->unless(true, function ($rule) { $rule->before('2025-01-01'); }); - $this->assertSame('date|date_format:Y-m-d|after:2024-01-01', (string) $rule); + $this->assertSame('date_format:Y-m-d|after:2024-01-01', (string) $rule); } public function testDateValidation() diff --git a/tests/Validation/ValidationDimensionsRuleTest.php b/tests/Validation/ValidationDimensionsRuleTest.php index 6bd2d0326e06..92ff39ca2b20 100644 --- a/tests/Validation/ValidationDimensionsRuleTest.php +++ b/tests/Validation/ValidationDimensionsRuleTest.php @@ -53,6 +53,42 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $this->assertSame('dimensions:min_ratio=0.5,max_ratio=0.33333333333333', (string) $rule); } + public function testItCorrectlyFormatsWithSpecialValues() + { + $rule = new Dimensions(); + + $this->assertSame('dimensions:', (string) $rule); + + $rule = Rule::dimensions()->width(-100)->height(-200); + + $this->assertSame('dimensions:width=-100,height=-200', (string) $rule); + + $rule = Rule::dimensions()->width('300')->height('400'); + + $this->assertSame('dimensions:width=300,height=400', (string) $rule); + } + + public function testDimensionsRuleMaintainsCorrectOrder() + { + $rule = Rule::dimensions()->minWidth(100)->width(200)->maxWidth(300); + + $this->assertSame('dimensions:min_width=100,width=200,max_width=300', (string) $rule); + } + + public function testOverridingValues() + { + $rule = Rule::dimensions()->width(100)->width(500); + + $this->assertSame('dimensions:width=500', (string) $rule); + } + + public function testRatioBetweenOverridesMinAndMaxRatio() + { + $rule = Rule::dimensions()->minRatio(0.5)->maxRatio(2.0)->ratioBetween(1, 1.5); + + $this->assertSame('dimensions:min_ratio=1,max_ratio=1.5', (string) $rule); + } + public function testGeneratesTheCorrectValidationMessages() { $rule = Rule::dimensions() diff --git a/tests/Validation/ValidationEmailRuleTest.php b/tests/Validation/ValidationEmailRuleTest.php index b6382f508bc4..cbff32a5edb1 100644 --- a/tests/Validation/ValidationEmailRuleTest.php +++ b/tests/Validation/ValidationEmailRuleTest.php @@ -11,6 +11,7 @@ use Illuminate\Validation\Rules\Email; use Illuminate\Validation\ValidationServiceProvider; use Illuminate\Validation\Validator; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; @@ -33,6 +34,18 @@ public function testBasic() ['The '.self::ATTRIBUTE_REPLACED.' must be a valid email address.'] ); + $this->fails( + Email::default(), + 12345, + [Email::class] + ); + + $this->fails( + Rule::email(), + 12345, + [Email::class] + ); + $this->passes( Email::default(), 'taylor@laravel.com' @@ -43,6 +56,16 @@ public function testBasic() 'taylor@laravel.com' ); + $this->passes( + Rule::email(), + ['taylor@laravel.com'], + ); + + $this->passes( + Email::default(), + ['taylor@laravel.com'], + ); + $this->passes(Email::default(), null); $this->passes(Rule::email(), null); @@ -143,6 +166,7 @@ public function testRfcCompliantStrict() ); } + #[RequiresPhpExtension('intl')] public function testValidateMxRecord() { $this->fails( @@ -674,6 +698,7 @@ public function testEmailsThatFailBothNativeValidationAndRfcCompliantStrict($ema ); } + #[RequiresPhpExtension('intl')] public function testCombiningRules() { $this->passes( diff --git a/tests/Validation/ValidationEnumRuleTest.php b/tests/Validation/ValidationEnumRuleTest.php index 68200d3390cb..bf9ec319dc66 100644 --- a/tests/Validation/ValidationEnumRuleTest.php +++ b/tests/Validation/ValidationEnumRuleTest.php @@ -249,6 +249,22 @@ public function testValidationFailsWhenProvidingStringToIntegerType() $this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status')); } + public function testValidationFailsWhenUsingDifferentCase() + { + $v = new Validator( + resolve('translator'), + [ + 'status' => 'DONE', + ], + [ + 'status' => new Enum(StringStatus::class), + ] + ); + + $this->assertTrue($v->fails()); + $this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status')); + } + public static function conditionalCasesDataProvider(): array { return [ diff --git a/tests/Validation/ValidationExcludeIfTest.php b/tests/Validation/ValidationExcludeIfTest.php index 5467f66c55d2..a616f642cc13 100644 --- a/tests/Validation/ValidationExcludeIfTest.php +++ b/tests/Validation/ValidationExcludeIfTest.php @@ -42,7 +42,7 @@ public function testItValidatesCallableAndBooleanAreAcceptableArguments() new ExcludeIf(true); new ExcludeIf(fn () => true); - foreach ([1, 1.1, 'phpinfo', new stdClass] as $condition) { + foreach ([1, 1.1, 'phpinfo', new stdClass, null] as $condition) { try { new ExcludeIf($condition); $this->fail('The ExcludeIf constructor must not accept '.gettype($condition)); diff --git a/tests/Validation/ValidationFileRuleTest.php b/tests/Validation/ValidationFileRuleTest.php index 1705a3ef198b..0d95bb2a8e66 100644 --- a/tests/Validation/ValidationFileRuleTest.php +++ b/tests/Validation/ValidationFileRuleTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Facade; use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; +use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\File; use Illuminate\Validation\ValidationServiceProvider; use Illuminate\Validation\Validator; @@ -194,6 +195,39 @@ public function testImage() ); } + public function testImageFailsOnSvgByDefault() + { + $maliciousSvgFileWithXSS = UploadedFile::fake()->createWithContent( + name: 'foo.svg', + content: <<<'XML' + + XSS Logo + + + XML + ); + + $this->fails( + File::image(), + $maliciousSvgFileWithXSS, + ['validation.image'] + ); + $this->fails( + Rule::imageFile(), + $maliciousSvgFileWithXSS, + ['validation.image'] + ); + + $this->passes( + File::image(allowSvg: true), + $maliciousSvgFileWithXSS + ); + $this->passes( + Rule::imageFile(allowSvg: true), + $maliciousSvgFileWithXSS + ); + } + public function testSize() { $this->fails( @@ -384,6 +418,27 @@ public function testItCanSetDefaultUsing() ); } + public function testFileSizeConversionWithDifferentUnits() + { + $this->passes( + File::image()->size('5MB'), + UploadedFile::fake()->create('foo.png', 5000) + ); + + $this->passes( + File::image()->size(' 2gb '), + UploadedFile::fake()->create('foo.png', 2 * 1000000) + ); + + $this->passes( + File::image()->size('1Tb'), + UploadedFile::fake()->create('foo.png', 1000000000) + ); + + $this->expectException(\InvalidArgumentException::class); + File::image()->size('10xyz'); + } + protected function setUp(): void { $container = Container::getInstance(); diff --git a/tests/Validation/ValidationForEachTest.php b/tests/Validation/ValidationForEachTest.php index b4b6bde450e5..1f68abe883f0 100644 --- a/tests/Validation/ValidationForEachTest.php +++ b/tests/Validation/ValidationForEachTest.php @@ -319,6 +319,37 @@ public function testConditionalRulesCanBeAddedToForEachWithObject() ], $v->getMessageBag()->toArray()); } + public function testForEachWithEmptyAndNullValues() + { + $data = [ + 'items' => [ + ['discounts' => null], + ['discounts' => []], + ['discounts' => [null]], + ], + ]; + + $rules = [ + 'items.*' => Rule::forEach(function () { + return [ + 'discounts' => 'required|array', + 'discounts.*' => 'required|array', + ]; + }), + ]; + + $v = new Validator($this->getIlluminateArrayTranslator(), $data, $rules); + $this->assertFalse($v->passes()); + $this->assertEquals( + [ + 'items.0.discounts' => ['validation.required'], + 'items.1.discounts' => ['validation.required'], + 'items.2.discounts.0' => ['validation.required'], + ], + $v->getMessageBag()->toArray() + ); + } + public function getIlluminateArrayTranslator() { return new Translator( diff --git a/tests/Validation/ValidationImageFileRuleTest.php b/tests/Validation/ValidationImageFileRuleTest.php index 63786a8fc61e..78a743436719 100644 --- a/tests/Validation/ValidationImageFileRuleTest.php +++ b/tests/Validation/ValidationImageFileRuleTest.php @@ -44,7 +44,7 @@ public function testDimensionsWithCustomImageSizeMethod() ); } - public function testDimentionWithTheRatioMethod() + public function testDimensionWithTheRatioMethod() { $this->fails( File::image()->dimensions(Rule::dimensions()->ratio(1)), @@ -58,7 +58,7 @@ public function testDimentionWithTheRatioMethod() ); } - public function testDimentionWithTheMinRatioMethod() + public function testDimensionWithTheMinRatioMethod() { $this->fails( File::image()->dimensions(Rule::dimensions()->minRatio(1 / 2)), @@ -72,7 +72,7 @@ public function testDimentionWithTheMinRatioMethod() ); } - public function testDimentionWithTheMaxRatioMethod() + public function testDimensionWithTheMaxRatioMethod() { $this->fails( File::image()->dimensions(Rule::dimensions()->maxRatio(1 / 2)), @@ -86,7 +86,7 @@ public function testDimentionWithTheMaxRatioMethod() ); } - public function testDimentionWithTheRatioBetweenMethod() + public function testDimensionWithTheRatioBetweenMethod() { $this->fails( File::image()->dimensions(Rule::dimensions()->ratioBetween(1 / 2, 1 / 3)), diff --git a/tests/Validation/ValidationPasswordRuleTest.php b/tests/Validation/ValidationPasswordRuleTest.php index 8fb9673ebff9..dec04626b3e1 100644 --- a/tests/Validation/ValidationPasswordRuleTest.php +++ b/tests/Validation/ValidationPasswordRuleTest.php @@ -339,6 +339,42 @@ public function message() ]); } + public function testCanRetrieveAllRulesApplied() + { + $password = Password::min(2) + ->max(4) + ->mixedCase() + ->numbers() + ->letters() + ->symbols(); + + $this->assertSame($password->appliedRules(), [ + 'min' => 2, + 'max' => 4, + 'mixedCase' => true, + 'letters' => true, + 'numbers' => true, + 'symbols' => true, + 'uncompromised' => false, + 'compromisedThreshold' => 0, + 'customRules' => [], + ]); + + $password = Password::min(2); + + $this->assertSame($password->appliedRules(), [ + 'min' => 2, + 'max' => null, + 'mixedCase' => false, + 'letters' => false, + 'numbers' => false, + 'symbols' => false, + 'uncompromised' => false, + 'compromisedThreshold' => 0, + 'customRules' => [], + ]); + } + protected function passes($rule, $values) { $this->assertValidationRules($rule, $values, true, []); diff --git a/tests/Validation/ValidationRuleParserTest.php b/tests/Validation/ValidationRuleParserTest.php index 8fbb48ef86ed..8da3e84afdd3 100644 --- a/tests/Validation/ValidationRuleParserTest.php +++ b/tests/Validation/ValidationRuleParserTest.php @@ -398,7 +398,7 @@ public function testExplodeHandlesDateRuleWithAdditionalRules() ])); $rules = [ - 'date' => Rule::date()->format('Y-m-d'), + 'date' => Rule::date()->after('today'), ]; $results = $parser->explode($rules); @@ -406,7 +406,7 @@ public function testExplodeHandlesDateRuleWithAdditionalRules() $this->assertEquals([ 'date' => [ 'date', - 'date_format:Y-m-d', + 'after:today', ], ], $results->rules); } diff --git a/tests/Validation/ValidationUniqueRuleTest.php b/tests/Validation/ValidationUniqueRuleTest.php index 8b4c85054ca5..69a4d0933088 100644 --- a/tests/Validation/ValidationUniqueRuleTest.php +++ b/tests/Validation/ValidationUniqueRuleTest.php @@ -2,12 +2,30 @@ namespace Illuminate\Tests\Validation; +use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Eloquent\Model; +use Illuminate\Translation\ArrayLoader; +use Illuminate\Translation\Translator; +use Illuminate\Validation\DatabasePresenceVerifier; use Illuminate\Validation\Rules\Unique; +use Illuminate\Validation\Validator; use PHPUnit\Framework\TestCase; class ValidationUniqueRuleTest extends TestCase { + protected function setUp(): void + { + $db = new DB; + $db->addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + + $db->bootEloquent(); + $this->createSchema(); + } + public function testItCorrectlyFormatsAStringVersionOfTheRule() { $rule = new Unique('table'); @@ -80,6 +98,10 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule = new Unique('table'); $rule->where('foo', '"bar"'); $this->assertSame('unique:table,NULL,NULL,id,foo,"""bar"""', (string) $rule); + + $rule = new Unique(EloquentModelWithConnection::class, 'column'); + $rule->where('foo', 'bar'); + $this->assertSame('unique:mysql.table,column,NULL,id,foo,"bar"', (string) $rule); } public function testItIgnoresSoftDeletes() @@ -103,6 +125,96 @@ public function testItOnlyTrashedSoftDeletes() $rule->onlyTrashed('softdeleted_at'); $this->assertSame('unique:table,NULL,NULL,id,softdeleted_at,"NOT_NULL"', (string) $rule); } + + public function testItHandlesNullPrimaryKeyInIgnoreModel() + { + $model = new EloquentModelStub(['id_column' => null]); + + $rule = new Unique('table', 'column'); + $rule->ignore($model); + $rule->where('foo', 'bar'); + $this->assertSame('unique:table,column,NULL,id_column,foo,"bar"', (string) $rule); + + $rule = new Unique('table', 'column'); + $rule->ignore($model, 'id_column'); + $rule->where('foo', 'bar'); + $this->assertSame('unique:table,column,NULL,id_column,foo,"bar"', (string) $rule); + } + + public function testItHandlesWhereWithSpecialValues() + { + $rule = new Unique('table', 'column'); + $rule->where('foo', null); + $this->assertSame('unique:table,column,NULL,id,foo,"NULL"', (string) $rule); + + $rule = new Unique('table', 'column'); + $rule->whereNot('foo', 'bar'); + $this->assertSame('unique:table,column,NULL,id,foo,"!bar"', (string) $rule); + + $rule = new Unique('table', 'column'); + $rule->whereNull('foo'); + $this->assertSame('unique:table,column,NULL,id,foo,"NULL"', (string) $rule); + + $rule = new Unique('table', 'column'); + $rule->whereNotNull('foo'); + $this->assertSame('unique:table,column,NULL,id,foo,"NOT_NULL"', (string) $rule); + + $rule = new Unique('table', 'column'); + $rule->where('foo', 0); + $this->assertSame('unique:table,column,NULL,id,foo,"0"', (string) $rule); + } + + public function testItValidatesUniqueRuleWithWhereInAndWhereNotIn() + { + EloquentModelStub::create(['id_column' => 1, 'type' => 'admin']); + EloquentModelStub::create(['id_column' => 2, 'type' => 'moderator']); + EloquentModelStub::create(['id_column' => 3, 'type' => 'editor']); + EloquentModelStub::create(['id_column' => 4, 'type' => 'user']); + + $rule = new Unique(table: 'table', column: 'id_column'); + $rule->whereIn(column: 'type', values: ['admin', 'moderator', 'editor']) + ->whereNotIn(column: 'type', values: ['editor']); + + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, [], ['id_column' => $rule]); + $v->setPresenceVerifier(new DatabasePresenceVerifier(Model::getConnectionResolver())); + + $v->setData(['id_column' => 1]); + $this->assertFalse($v->passes()); + + $v->setData(['id_column' => 2]); + $this->assertFalse($v->passes()); + + $v->setData(['id_column' => 3]); + $this->assertTrue($v->passes()); + + $v->setData(['id_column' => 4]); + $this->assertTrue($v->passes()); + + $v->setData(['id_column' => 5]); + $this->assertTrue($v->passes()); + } + + protected function createSchema(): void + { + $this->connection()->getSchemaBuilder()->create('table', function ($table) { + $table->unsignedInteger('id_column'); + $table->string('type'); + $table->timestamps(); + }); + } + + protected function connection(): ConnectionInterface + { + return Model::getConnectionResolver()->connection(); + } + + protected function getIlluminateArrayTranslator(): Translator + { + return new Translator( + new ArrayLoader, locale: 'en' + ); + } } class EloquentModelStub extends Model @@ -136,3 +248,8 @@ public function __construct($bar, $baz) $this->baz = $baz; } } + +class EloquentModelWithConnection extends EloquentModelStub +{ + protected $connection = 'mysql'; +} diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 735f75b323cb..167711851fab 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -925,7 +925,8 @@ public function testCustomException() $v = new Validator($trans, ['name' => ''], ['name' => 'required']); - $exception = new class($v) extends ValidationException {}; + $exception = new class($v) extends ValidationException { + }; $v->setException($exception); try { @@ -4959,6 +4960,12 @@ public function testValidateImage() $file6->expects($this->any())->method('guessExtension')->willReturn('svg'); $file6->expects($this->any())->method('getClientOriginalExtension')->willReturn('svg'); $v = new Validator($trans, ['x' => $file6], ['x' => 'image']); + $this->assertFalse($v->passes()); + + $file6 = $this->getMockBuilder(UploadedFile::class)->onlyMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); + $file6->expects($this->any())->method('guessExtension')->willReturn('svg'); + $file6->expects($this->any())->method('getClientOriginalExtension')->willReturn('svg'); + $v = new Validator($trans, ['x' => $file6], ['x' => 'image:allow_svg']); $this->assertTrue($v->passes()); $file7 = $this->getMockBuilder(UploadedFile::class)->onlyMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); @@ -5255,6 +5262,29 @@ public function testAlternativeFormat() $this->assertTrue($v->passes()); } + public function testNumericKeys() + { + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['3' => 'aslsdlks'], [3 => 'required']); + $this->assertTrue($v->passes()); + } + + public function testMergeRules() + { + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['x' => 'asl', 'a' => [1, 4]], ['x' => ['alpha', ['min', 3]], 'a.*' => 'integer']); + $v->addRules(['x' => ['required', ['max', 10]], 'a.1' => 'digits:1']); + $this->assertEquals( + [ + 'x' => ['alpha', ['min', 3], 'required', ['max', 10]], + 'a.0' => ['integer'], + 'a.1' => ['integer', 'digits:1'], + ], + $v->getRules() + ); + $this->assertTrue($v->passes()); + } + public function testValidateAlpha() { $trans = $this->getIlluminateArrayTranslator(); @@ -8522,6 +8552,14 @@ public function testValidateWithInvalidUuid($uuid) $this->assertFalse($v->passes()); } + #[DataProvider('uuidVersionList')] + public function testValidateWithUuidWithVersionConstraint($uuid, $rule, $passes) + { + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => $uuid], ['foo' => $rule]); + $this->assertSame($v->passes(), $passes); + } + public static function validUuidList() { return [ @@ -8554,6 +8592,56 @@ public static function invalidUuidList() ]; } + public static function uuidVersionList() + { + return [ + ['00000000-0000-0000-0000-000000000000', 'uuid', true], + ['00000000-0000-0000-0000-000000000000', 'uuid:0', true], + ['00000000-0000-0000-0000-000000000000', 'uuid:1', false], + ['00000000-0000-0000-0000-000000000000', 'uuid:42', false], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 'uuid', true], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 'uuid:1', true], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 'uuid:4', false], + ['145a1e72-d11d-11e8-a8d5-f2801f1b9fd1', 'uuid:42', false], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 'uuid', true], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 'uuid:1', false], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 'uuid:2', true], + ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66', 'uuid:42', false], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 'uuid', true], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 'uuid:1', false], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 'uuid:3', true], + ['76a4ba72-cc4e-3e1d-b52d-856382f408c3', 'uuid:42', false], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 'uuid', true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 'uuid:1', false], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 'uuid:4', true], + ['a0a2a2d2-0b87-4a18-83f2-2529882be2de', 'uuid:42', false], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 'uuid', true], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 'uuid:1', false], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 'uuid:5', true], + ['d3b2b5a9-d433-5c58-b038-4fa13696e357', 'uuid:42', false], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 'uuid', true], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 'uuid:1', false], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 'uuid:6', true], + ['1ef97d97-b5ab-67d8-9f12-5600051f1387', 'uuid:42', false], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 'uuid', true], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 'uuid:1', false], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 'uuid:7', true], + ['0192e4b9-92eb-7aec-8707-1becfb1e3eb7', 'uuid:42', false], + ['07e80a1f-1629-831f-811f-c595103c91b5', 'uuid', true], + ['07e80a1f-1629-831f-811f-c595103c91b5', 'uuid:1', false], + ['07e80a1f-1629-831f-811f-c595103c91b5', 'uuid:8', true], + ['07e80a1f-1629-831f-811f-c595103c91b5', 'uuid:42', false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'uuid', true], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'uuid:1', false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'uuid:42', false], + ['FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'uuid:max', true], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 'uuid', false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 'uuid:1', false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 'uuid:4', false], + ['zf6f8cb0-c57d-11e1-9b21-0800200c9a66', 'uuid:42', false], + ]; + } + public function testValidateWithValidAscii() { $trans = $this->getIlluminateArrayTranslator(); diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 5ebfbb104648..46d7a1c08f2d 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Pagination\AbstractPaginator; use Illuminate\View\Compilers\BladeCompiler; use Illuminate\View\Compilers\ComponentTagCompiler; use Illuminate\View\Component; @@ -696,7 +697,7 @@ public function testClasslessComponentsWithAnonymousComponentPath() $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { - return $arg === md5('test-directory').'::panel.index'; + return $arg === hash('xxh128', 'test-directory').'::panel.index'; }); Container::setInstance($container); @@ -704,14 +705,14 @@ public function testClasslessComponentsWithAnonymousComponentPath() $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ - ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')], + ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => hash('xxh128', 'test-directory')], ]); $compiler = $this->compiler([], [], $blade); $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".hash('xxh128', 'test-directory')."::panel.index','data' => []]) except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> @@ -762,7 +763,7 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $app->shouldReceive('getNamespace')->once()->andReturn('App\\'); $factory->shouldReceive('exists')->andReturnUsing(function ($arg) { - return $arg === md5('test-directory').'::panel'; + return $arg === hash('xxh128', 'test-directory').'::panel'; }); Container::setInstance($container); @@ -770,14 +771,14 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $blade = m::mock(BladeCompiler::class)->makePartial(); $blade->shouldReceive('getAnonymousComponentPaths')->once()->andReturn([ - ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => md5('test-directory')], + ['path' => 'test-directory', 'prefix' => null, 'prefixHash' => hash('xxh128', 'test-directory')], ]); $compiler = $this->compiler([], [], $blade); $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".hash('xxh128', 'test-directory')."::panel','data' => []]) except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> @@ -796,13 +797,18 @@ public function __toString() } }; - $model = new class extends Model {}; + $model = new class extends Model { + }; + + $paginator = new class extends AbstractPaginator { + }; $this->assertEquals(e(''), BladeCompiler::sanitizeComponentAttribute('')); $this->assertEquals(e('1'), BladeCompiler::sanitizeComponentAttribute('1')); $this->assertEquals(1, BladeCompiler::sanitizeComponentAttribute(1)); $this->assertEquals(e(''), BladeCompiler::sanitizeComponentAttribute($class)); $this->assertSame($model, BladeCompiler::sanitizeComponentAttribute($model)); + $this->assertSame($paginator, BladeCompiler::sanitizeComponentAttribute($paginator)); } public function testItThrowsAnExceptionForNonExistingAliases() diff --git a/tests/View/Blade/BladeExtendsTest.php b/tests/View/Blade/BladeExtendsTest.php index a4dcf8c64e38..d09d996c6950 100644 --- a/tests/View/Blade/BladeExtendsTest.php +++ b/tests/View/Blade/BladeExtendsTest.php @@ -6,8 +6,7 @@ class BladeExtendsTest extends AbstractBladeTestCase { public function testExtendsAreCompiled() { - $string = '@extends(\'foo\') -test'; + $string = "@extends('foo')\ntest"; $expected = "test\n".'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); @@ -18,8 +17,7 @@ public function testExtendsAreCompiled() public function testSequentialCompileStringCalls() { - $string = '@extends(\'foo\') -test'; + $string = "@extends('foo')\ntest"; $expected = "test\n".'make(\'foo\', array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); @@ -31,8 +29,7 @@ public function testSequentialCompileStringCalls() public function testExtendsFirstAreCompiled() { - $string = '@extendsFirst([\'foo\', \'milwad\']) -test'; + $string = "@extendsFirst(['foo', 'milwad'])\ntest"; $expected = "test\n".'first([\'foo\', \'milwad\'], array_diff_key(get_defined_vars(), [\'__data\' => 1, \'__path\' => 1]))->render(); ?>'; $this->assertEquals($expected, $this->compiler->compileString($string)); diff --git a/tests/View/Blade/BladeUseTest.php b/tests/View/Blade/BladeUseTest.php index 8e72c321540d..28072b64559c 100644 --- a/tests/View/Blade/BladeUseTest.php +++ b/tests/View/Blade/BladeUseTest.php @@ -6,29 +6,133 @@ class BladeUseTest extends AbstractBladeTestCase { public function testUseStatementsAreCompiled() { - $string = "Foo @use('SomeNamespace\SomeClass', 'Foo') bar"; $expected = "Foo bar"; + + $string = "Foo @use('SomeNamespace\SomeClass', 'Foo') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(SomeNamespace\SomeClass, Foo) bar"; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithoutAsAreCompiled() { - $string = "Foo @use('SomeNamespace\SomeClass') bar"; $expected = "Foo bar"; + + $string = "Foo @use('SomeNamespace\SomeClass') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(SomeNamespace\SomeClass) bar"; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithBackslashAtBeginningAreCompiled() { - $string = "Foo @use('\SomeNamespace\SomeClass') bar"; $expected = "Foo bar"; + + $string = "Foo @use('\SomeNamespace\SomeClass') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(\SomeNamespace\SomeClass) bar"; $this->assertEquals($expected, $this->compiler->compileString($string)); } public function testUseStatementsWithBackslashAtBeginningAndAliasedAreCompiled() { - $string = "Foo @use('\SomeNamespace\SomeClass', 'Foo') bar"; $expected = "Foo bar"; + + $string = "Foo @use('\SomeNamespace\SomeClass', 'Foo') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(\SomeNamespace\SomeClass, Foo) bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithBracesAreCompiledCorrectly() + { + $expected = "Foo bar"; + + $string = "Foo @use('SomeNamespace\{Foo, Bar}') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(SomeNamespace\{Foo, Bar}) bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementWithBracesAndBackslashAreCompiledCorrectly() + { + $expected = "Foo bar"; + + $string = "Foo @use('\SomeNamespace\{Foo, Bar}') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = "Foo @use(\SomeNamespace\{Foo, Bar}) bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithModifiersAreCompiled() + { + $expected = 'Foo bar'; + + $string = "Foo @use('function SomeNamespace\SomeFunction', 'Foo') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(function SomeNamespace\SomeFunction, Foo) bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithModifiersWithoutAliasAreCompiled() + { + $expected = 'Foo bar'; + + $string = "Foo @use('const SomeNamespace\SOME_CONST') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(const SomeNamespace\SOME_CONST) bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithModifiersAndBackslashAtBeginningAreCompiled() + { + $expected = 'Foo bar'; + + $string = "Foo @use('function \SomeNamespace\SomeFunction') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(function \SomeNamespace\SomeFunction) bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithModifiersBackslashAtBeginningAndAliasedAreCompiled() + { + $expected = 'Foo bar'; + + $string = "Foo @use('const \SomeNamespace\SOME_CONST', 'Foo') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(const \SomeNamespace\SOME_CONST, Foo) bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithModifiersWithBracesAreCompiledCorrectly() + { + $expected = 'Foo bar'; + + $string = "Foo @use('function SomeNamespace\{Foo, Bar}') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(function SomeNamespace\{Foo, Bar}) bar'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseFunctionStatementWithBracesAndBackslashAreCompiledCorrectly() + { + $expected = 'Foo bar'; + + $string = "Foo @use('const \SomeNamespace\{FOO, BAR}') bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = 'Foo @use(const \SomeNamespace\{FOO, BAR}) bar'; $this->assertEquals($expected, $this->compiler->compileString($string)); } } diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php index c3955945e8ff..b26693489c81 100644 --- a/tests/View/ViewBladeCompilerTest.php +++ b/tests/View/ViewBladeCompilerTest.php @@ -51,10 +51,17 @@ public function testIsExpiredReturnsFalseWhenUseCacheIsTrueAndNoFileModification public function testIsExpiredReturnsTrueWhenUseCacheIsFalse() { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__, $basePath = '', $useCache = false); + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__, shouldCache: false); $this->assertTrue($compiler->isExpired('foo')); } + public function testIsExpiredReturnsFalseWhenIgnoreCacheTimestampsIsTrue() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__, shouldCheckTimestamps: false); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(true); + $this->assertFalse($compiler->isExpired('foo')); + } + public function testCompilePathIsProperlyCreated() { $compiler = new BladeCompiler($this->getFiles(), __DIR__); @@ -66,17 +73,42 @@ public function testCompileCompilesFileAndReturnsContents() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); } public function testCompileCompilesFileAndReturnsContentsCreatingDirectory() { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $compiler->compile('foo'); + } + + public function testCompileUpdatesCacheIfChanged() + { + $compiledPath = __DIR__.'/'.hash('xxh128', 'v2foo').'.php'; + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with($compiledPath)->andReturn(true); + $files->shouldReceive('hash')->once()->with($compiledPath, 'xxh128')->andReturn(hash('xxh128', 'outdated content')); + $files->shouldReceive('put')->once()->with($compiledPath, 'Hello World'); + $compiler->compile('foo'); + } + + public function testCompileKeepsCacheIfUnchanged() + { + $compiledPath = __DIR__.'/'.hash('xxh128', 'v2foo').'.php'; $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false); $files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); + $files->shouldReceive('exists')->once()->with($compiledPath)->andReturn(true); + $files->shouldReceive('hash')->once()->with($compiledPath, 'xxh128')->andReturn(hash('xxh128', 'Hello World')); $compiler->compile('foo'); } @@ -85,6 +117,7 @@ public function testCompileCompilesAndGetThePath() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); $compiler->compile('foo'); $this->assertSame('foo', $compiler->getPath()); @@ -102,6 +135,7 @@ public function testCompileWithPathSetBefore() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World'); // set path before compilation $compiler->setPath('foo'); @@ -132,6 +166,7 @@ public function testIncludePathToTemplate($content, $compiled) $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('foo')->andReturn($content); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiled); $compiler->compile('foo'); @@ -187,6 +222,7 @@ public function testDontIncludeEmptyPath() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with('')->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); $compiler->setPath(''); $compiler->compile(); @@ -197,6 +233,7 @@ public function testDontIncludeNullPath() $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World'); $files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php')->andReturn(false); $files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World'); $compiler->setPath(null); $compiler->compile(); diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php index 82fbd8687fa1..511181024b2c 100644 --- a/tests/View/ViewComponentAttributeBagTest.php +++ b/tests/View/ViewComponentAttributeBagTest.php @@ -153,4 +153,15 @@ public function testAttributeIsNotEmpty() $this->assertTrue((bool) $bag->isNotEmpty()); } + + public function testAttributeIsArray() + { + $bag = new ComponentAttributeBag([ + 'name' => 'test', + 'class' => 'font-bold', + ]); + + $this->assertIsArray($bag->toArray()); + $this->assertEquals(['name' => 'test', 'class' => 'font-bold'], $bag->toArray()); + } } diff --git a/types/Contracts/Container/Container.php b/types/Contracts/Container/Container.php index 0c2fc9b26820..1812914a28e6 100644 --- a/types/Contracts/Container/Container.php +++ b/types/Contracts/Container/Container.php @@ -2,6 +2,7 @@ use Illuminate\Config\Repository; use Illuminate\Contracts\Container\Container; +use Illuminate\Http\Request; use function PHPStan\Testing\assertType; @@ -17,3 +18,5 @@ assertType('mixed', $container->make('foo')); assertType('Illuminate\Config\Repository', $container->make(Repository::class)); + +assertType('Illuminate\Http\Request', $container->instance('request', Request::capture())); diff --git a/types/Database/Eloquent/Builder.php b/types/Database/Eloquent/Builder.php index 61cea1d75508..24f03fc71f99 100644 --- a/types/Database/Eloquent/Builder.php +++ b/types/Database/Eloquent/Builder.php @@ -63,6 +63,7 @@ function test( assertType('Illuminate\Types\Builder\User', $query->forceCreate(['name' => 'John'])); assertType('Illuminate\Types\Builder\User', $query->updateOrCreate(['id' => 1], ['name' => 'John'])); assertType('Illuminate\Types\Builder\User', $query->firstOrFail()); + assertType('Illuminate\Types\Builder\User', $query->findSole(1)); assertType('Illuminate\Types\Builder\User', $query->sole()); assertType('Illuminate\Support\LazyCollection', $query->cursor()); assertType('Illuminate\Support\LazyCollection', $query->cursor()); @@ -222,6 +223,12 @@ function test( assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->where('foo', 'bar')); assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->foo()); assertType('Illuminate\Types\Builder\Comment', $comment->newQuery()->create(['name' => 'John'])); + assertType('Illuminate\Database\Eloquent\Builder', $query->pipe(function () { + // + })); + assertType('Illuminate\Database\Eloquent\Builder', $query->pipe(fn () => null)); + assertType('Illuminate\Database\Eloquent\Builder', $query->pipe(fn ($query) => $query)); + assertType('5', $query->pipe(fn ($query) => 5)); } class User extends Model diff --git a/types/Database/Eloquent/Relations.php b/types/Database/Eloquent/Relations.php index 123b22d5b359..a9d305707c43 100644 --- a/types/Database/Eloquent/Relations.php +++ b/types/Database/Eloquent/Relations.php @@ -41,43 +41,43 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Types\Relations\Post|false', $user->posts()->save(new Post())); assertType('Illuminate\Types\Relations\Post|false', $user->posts()->saveQuietly(new Post())); - assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $user->roles()); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->getResults()); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->find([1])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findMany([1, 2, 3])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrNew([1])); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrFail([1])); - assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], fn () => 42)); - assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role', $user->roles()->findOrNew(1)); - assertType('Illuminate\Types\Relations\Role', $user->roles()->findOrFail(1)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->find(1)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->findOr(1, fn () => 42)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->findOr(1, callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->first()); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->firstOr(fn () => 42)); - assertType('42|Illuminate\Types\Relations\Role', $user->roles()->firstOr(callback: fn () => 42)); - assertType('Illuminate\Types\Relations\Role|null', $user->roles()->firstWhere('foo')); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrNew()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrFail()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->firstOrCreate()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->create()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->createOrFirst()); - assertType('Illuminate\Types\Relations\Role', $user->roles()->updateOrCreate([])); - assertType('Illuminate\Types\Relations\Role', $user->roles()->save(new Role())); - assertType('Illuminate\Types\Relations\Role', $user->roles()->saveQuietly(new Role())); + assertType("Illuminate\Database\Eloquent\Relations\BelongsToMany", $user->roles()); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->getResults()); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->find([1])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findMany([1, 2, 3])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrNew([1])); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->findOrFail([1])); + assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], fn () => 42)); + assertType('42|Illuminate\Database\Eloquent\Collection', $user->roles()->findOr([1], callback: fn () => 42)); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->findOrNew(1)); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->findOrFail(1)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->find(1)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->findOr(1, fn () => 42)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->findOr(1, callback: fn () => 42)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->first()); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->firstOr(fn () => 42)); + assertType('42|(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})', $user->roles()->firstOr(callback: fn () => 42)); + assertType('(Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot})|null', $user->roles()->firstWhere('foo')); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrNew()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrFail()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->firstOrCreate()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->create()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->createOrFirst()); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->updateOrCreate([])); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->save(new Role())); + assertType('Illuminate\Types\Relations\Role&object{pivot: Illuminate\Database\Eloquent\Relations\Pivot}', $user->roles()->saveQuietly(new Role())); $roles = $user->roles()->getResults(); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveMany($roles)); - assertType('array', $user->roles()->saveMany($roles->all())); - assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveManyQuietly($roles)); - assertType('array', $user->roles()->saveManyQuietly($roles->all())); - assertType('array', $user->roles()->createMany($roles)); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveMany($roles)); + assertType('array', $user->roles()->saveMany($roles->all())); + assertType('Illuminate\Database\Eloquent\Collection', $user->roles()->saveManyQuietly($roles)); + assertType('array', $user->roles()->saveManyQuietly($roles->all())); + assertType('array', $user->roles()->createMany($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->sync($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->syncWithoutDetaching($roles)); assertType('array{attached: array, detached: array, updated: array}', $user->roles()->syncWithPivotValues($roles, [])); - assertType('Illuminate\Support\LazyCollection', $user->roles()->lazy()); - assertType('Illuminate\Support\LazyCollection', $user->roles()->lazyById()); - assertType('Illuminate\Support\LazyCollection', $user->roles()->cursor()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->lazy()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->lazyById()); + assertType('Illuminate\Support\LazyCollection', $user->roles()->cursor()); assertType('Illuminate\Database\Eloquent\Relations\HasOneThrough', $user->car()); assertType('Illuminate\Types\Relations\Car|null', $user->car()->getResults()); @@ -122,8 +122,8 @@ function test(User $user, Post $post, Comment $comment, ChildUser $child): void assertType('Illuminate\Types\Relations\Comment', $comment->commentable()->associate(new Post())); assertType('Illuminate\Types\Relations\Comment', $comment->commentable()->dissociate()); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $post->tags()); - assertType('Illuminate\Database\Eloquent\Collection', $post->tags()->getResults()); + assertType("Illuminate\Database\Eloquent\Relations\MorphToMany", $post->tags()); + assertType('Illuminate\Database\Eloquent\Collection', $post->tags()->getResults()); assertType('42', Relation::noConstraints(fn () => 42)); } @@ -161,7 +161,7 @@ public function latestPost(): HasOne public function roles(): BelongsToMany { $belongsToMany = $this->belongsToMany(Role::class); - assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $belongsToMany); + assertType('Illuminate\Database\Eloquent\Relations\BelongsToMany', $belongsToMany); return $belongsToMany; } @@ -298,7 +298,7 @@ public function latestComment(): MorphOne public function tags(): MorphToMany { $morphToMany = $this->morphedByMany(Tag::class, 'taggable'); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); return $morphToMany; } @@ -322,7 +322,7 @@ class Tag extends Model public function posts(): MorphToMany { $morphToMany = $this->morphToMany(Post::class, 'taggable'); - assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); + assertType('Illuminate\Database\Eloquent\Relations\MorphToMany', $morphToMany); return $morphToMany; } diff --git a/types/Database/Query/Builder.php b/types/Database/Query/Builder.php index c5917edf6b20..780630bdd39c 100644 --- a/types/Database/Query/Builder.php +++ b/types/Database/Query/Builder.php @@ -60,4 +60,10 @@ function test(Builder $query, EloquentBuilder $userQuery): void assertType('object', $users); assertType('int', $page); }); + assertType('Illuminate\Database\Query\Builder', $query->pipe(function () { + // + })); + assertType('Illuminate\Database\Query\Builder', $query->pipe(fn () => null)); + assertType('Illuminate\Database\Query\Builder', $query->pipe(fn ($query) => $query)); + assertType('5', $query->pipe(fn ($query) => 5)); } diff --git a/types/Managers/CacheManager.php b/types/Managers/CacheManager.php new file mode 100644 index 000000000000..47521e110349 --- /dev/null +++ b/types/Managers/CacheManager.php @@ -0,0 +1,13 @@ +extend('redis', function (): void { + assertType('Illuminate\Cache\CacheManager', $this); +}); diff --git a/types/Managers/ConcurrencyManager.php b/types/Managers/ConcurrencyManager.php new file mode 100644 index 000000000000..d745b77ca0f7 --- /dev/null +++ b/types/Managers/ConcurrencyManager.php @@ -0,0 +1,13 @@ +extend('custom', function (): void { + assertType('Illuminate\Concurrency\ConcurrencyManager', $this); +}); diff --git a/types/Managers/LogManager.php b/types/Managers/LogManager.php new file mode 100644 index 000000000000..45da40dfa3dc --- /dev/null +++ b/types/Managers/LogManager.php @@ -0,0 +1,13 @@ +extend('emergency', function (): void { + assertType('Illuminate\Log\LogManager', $this); +}); diff --git a/types/Managers/RedisManager.php b/types/Managers/RedisManager.php new file mode 100644 index 000000000000..8b38200762c0 --- /dev/null +++ b/types/Managers/RedisManager.php @@ -0,0 +1,13 @@ +extend('custom', function (): void { + assertType('Illuminate\Redis\RedisManager', $this); +}); diff --git a/types/Queue/Events/JobQueued.php b/types/Queue/Events/JobQueued.php new file mode 100644 index 000000000000..5733c57bf078 --- /dev/null +++ b/types/Queue/Events/JobQueued.php @@ -0,0 +1,19 @@ + null, + payload: '{}', + delay: null, +); + +/** + * @see testQueueMayBeNullForJobQueueingAndJobQueuedEvent + */ +assertType('string|null', $instance->queue); diff --git a/types/Queue/Events/JobQueueing.php b/types/Queue/Events/JobQueueing.php new file mode 100644 index 000000000000..989cb8432b9f --- /dev/null +++ b/types/Queue/Events/JobQueueing.php @@ -0,0 +1,18 @@ + null, + payload: '{}', + delay: null, +); + +/** + * @see testQueueMayBeNullForJobQueueingAndJobQueuedEvent + */ +assertType('string|null', $instance->queue); diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 701735e732f2..c6733fa5d331 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -10,7 +10,7 @@ class Users implements Arrayable { public function toArray(): array { - return [new User()]; + return [new User]; } } @@ -21,6 +21,8 @@ public function toArray(): array /** @var Traversable $traversable */ $traversable = new ArrayIterator(['string']); +$associativeCollection = collect(['John' => new User]); + class Invokable { public function __invoke(): string @@ -28,7 +30,7 @@ public function __invoke(): string return 'Taylor'; } } -$invokable = new Invokable(); +$invokable = new Invokable; assertType('Illuminate\Support\Collection', $collection); @@ -806,6 +808,8 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection>', $collection::make(['string'])->chunk(1)); assertType('Illuminate\Support\Collection>', $collection->chunk(2)); +assertType('Illuminate\Support\Collection>', $associativeCollection->chunk(2)); +assertType('Illuminate\Support\Collection>', $associativeCollection->chunk(2, false)); assertType('Illuminate\Support\Collection>', $collection->chunkWhile(function ($user, $int, $collection) { assertType('User', $user); @@ -869,10 +873,10 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection->make(['string' => 'string'])->sortKeysDesc(1)); assertType('mixed', $collection->make([1])->sum('string')); -assertType('mixed', $collection->make(['string'])->sum(function ($string) { +assertType('int<1, 2>', $collection->make(['string'])->sum(function ($string) { assertType('string', $string); - return 1; + return rand(1, 2); })); assertType('Illuminate\Support\Collection', $collection->make([1])->take(1)); diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index 287eb69a2127..4e96ba431f70 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -11,20 +11,22 @@ class Users implements Arrayable { public function toArray(): array { - return [new User()]; + return [new User]; } } $collection = new LazyCollection([new User]); -$arrayable = new Users(); +$arrayable = new Users; /** @var iterable $iterable */ $iterable = [1]; /** @var Traversable $traversable */ $traversable = new ArrayIterator(['string']); $generator = function () { - yield new User(); + yield new User; }; +$associativeCollection = new LazyCollection(['Sam' => new User]); + assertType('Illuminate\Support\LazyCollection', $collection); assertType("Illuminate\Support\LazyCollection", new LazyCollection(['string'])); @@ -665,6 +667,8 @@ public function toArray(): array assertType('Illuminate\Support\LazyCollection>', $collection::make(['string'])->chunk(1)); assertType('Illuminate\Support\LazyCollection>', $collection->chunk(2)); +assertType('Illuminate\Support\LazyCollection>', $associativeCollection->chunk(2)); +assertType('Illuminate\Support\LazyCollection>', $associativeCollection->chunk(2, false)); assertType('Illuminate\Support\LazyCollection>', $collection->chunkWhile(function ($user, $int, $collection) { assertType('User', $user); @@ -728,10 +732,10 @@ public function toArray(): array assertType('Illuminate\Support\LazyCollection', $collection->make(['string' => 'string'])->sortKeysDesc(1)); assertType('mixed', $collection->make([1])->sum('string')); -assertType('mixed', $collection->make(['string'])->sum(function ($string) { +assertType('int<1, 2>', $collection->make(['string'])->sum(function ($string) { assertType('string', $string); - return 1; + return rand(1, 2); })); assertType('Illuminate\Support\LazyCollection', $collection->make([1])->take(1));