diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index da9500c98394..d2a186904407 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,9 +8,13 @@ on: jobs: linux_tests: - runs-on: ubuntu-latest + services: + memcached: + image: memcached:1.6-alpine + ports: + - 11211:11211 mysql: image: mysql:5.7 env: @@ -27,7 +31,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.2, 7.3, 7.4] + php: [7.2, 7.3, 7.4, 8.0] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} @@ -40,15 +44,27 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached tools: composer:v2 coverage: none - - name: Setup Memcached - uses: niden/actions-memcached@v7 + - name: Setup problem matchers + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set Minimum Guzzle Version + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require guzzlehttp/guzzle:^7.2 --no-interaction --no-update + if: matrix.php >= 8 - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Execute tests run: vendor/bin/phpunit --verbose @@ -57,19 +73,13 @@ jobs: DB_USERNAME: root windows_tests: - runs-on: windows-latest + strategy: fail-fast: true matrix: - php: [7.2, 7.3, 7.4] - include: - - php: 7.2 - stability: prefer-lowest - - php: 7.3 - stability: prefer-stable - - php: 7.4 - stability: prefer-stable + php: [7.2, 7.3, 7.4, 8.0] + stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows @@ -86,13 +96,27 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, gd, pdo_mysql, fileinfo, ftp + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, gd, pdo_mysql, fileinfo, ftp, redis, memcached tools: composer:v2 coverage: none - ini-values: memory_limit=512M + + - name: Setup problem matchers + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set Minimum Guzzle Version + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require guzzlehttp/guzzle:^7.2 --no-interaction --no-update + if: matrix.php >= 8 - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Execute tests run: vendor/bin/phpunit --verbose diff --git a/CHANGELOG-6.x.md b/CHANGELOG-6.x.md index 8becbee52ef0..42350000b97c 100644 --- a/CHANGELOG-6.x.md +++ b/CHANGELOG-6.x.md @@ -1,6 +1,240 @@ # Release Notes for 6.x -## [Unreleased](https://github.com/laravel/framework/compare/v6.18.25...6.x) +## [Unreleased](https://github.com/laravel/framework/compare/v6.20.13...6.x) + + +## [v6.20.13 (2021-01-19)](https://github.com/laravel/framework/compare/v6.20.12...v6.20.13) + +### Fixed +- Fixed empty html mail ([#35941](https://github.com/laravel/framework/pull/35941)) + + +## [v6.20.12 (2021-01-13)](https://github.com/laravel/framework/compare/v6.20.11...v6.20.12) + + +## [v6.20.11 (2021-01-13)](https://github.com/laravel/framework/compare/v6.20.10...v6.20.11) + +### Fixed +- Limit expected bindings ([#35865](https://github.com/laravel/framework/pull/35865)) + + +## [v6.20.10 (2021-01-12)](https://github.com/laravel/framework/compare/v6.20.9...v6.20.10) + +### Added +- Added new line to `DetectsLostConnections` ([#35790](https://github.com/laravel/framework/pull/35790)) + +### Fixed +- Fixed error from missing null check on PHP 8 in `Illuminate\Validation\Concerns\ValidatesAttributes::validateJson()` ([#35797](https://github.com/laravel/framework/pull/35797)) + + +## [v6.20.9 (2021-01-05)](https://github.com/laravel/framework/compare/v6.20.8...v6.20.9) + +### Added +- [Updated Illuminate\Database\DetectsLostConnections with new strings](https://github.com/laravel/framework/compare/v6.20.8...v6.20.9) + + +## [v6.20.8 (2020-12-22)](https://github.com/laravel/framework/compare/v6.20.7...v6.20.8) + +### Fixed +- Fixed `Illuminate\Validation\Concerns\ValidatesAttributes::validateJson()` for PHP8 ([#35646](https://github.com/laravel/framework/pull/35646)) +- Catch DecryptException with invalid X-XSRF-TOKEN in `Illuminate\Foundation\Http\Middleware\VerifyCsrfToken` ([#35671](https://github.com/laravel/framework/pull/35671)) + + +## [v6.20.7 (2020-12-08)](https://github.com/laravel/framework/compare/v6.20.6...v6.20.7) + +### Fixed +- Backport for fix issue with polymorphic morphMaps with literal 0 ([#35487](https://github.com/laravel/framework/pull/35487)) +- Fixed mime validation for jpeg files ([#35518](https://github.com/laravel/framework/pull/35518)) + + +## [v6.20.6 (2020-12-01)](https://github.com/laravel/framework/compare/v6.20.5...v6.20.6) + +### Fixed +- Backport Redis context option ([#35370](https://github.com/laravel/framework/pull/35370)) +- Fixed validating image/jpeg images after Symfony/Mime update ([#35419](https://github.com/laravel/framework/pull/35419)) + + +## [v6.20.5 (2020-11-24)](https://github.com/laravel/framework/compare/v6.20.4...v6.20.5) + +### Fixed +- Fixing BroadcastException message in PusherBroadcaster@broadcast ([#35290](https://github.com/laravel/framework/pull/35290)) +- Fixed generic DetectsLostConnection string ([#35323](https://github.com/laravel/framework/pull/35323)) + +### Changed +- Updated `aws/aws-sdk-php` suggest to `^3.155` ([#35267](https://github.com/laravel/framework/pull/35267)) + + +## [v6.20.4 (2020-11-17)](https://github.com/laravel/framework/compare/v6.20.3...v6.20.4) + +### Fixed +- Fixed pivot restoration ([#35218](https://github.com/laravel/framework/pull/35218)) + + +## [v6.20.3 (2020-11-10)](https://github.com/laravel/framework/compare/v6.20.2...v6.20.3) + +### Fixed +- Turn the eloquent collection into a base collection if mapWithKeys loses models ([#35129](https://github.com/laravel/framework/pull/35129)) + + +## [v6.20.2 (2020-10-29)](https://github.com/laravel/framework/compare/v6.20.1...v6.20.2) + +### Fixed +- [Add some fixes](https://github.com/laravel/framework/compare/v6.20.1...v6.20.2) + + +## [v6.20.1 (2020-10-29)](https://github.com/laravel/framework/compare/v6.20.0...v6.20.1) + +### Fixed +- Fixed alias usage in `Eloquent` ([6091048](https://github.com/laravel/framework/commit/609104806b8b639710268c75c22f43034c2b72db)) +- Fixed `Illuminate\Support\Reflector::isCallable()` ([a90f344](https://github.com/laravel/framework/commit/a90f344c66f0a5bb1d718f8bbd20c257d4de9e02)) + + +## [v6.20.0 (2020-10-28)](https://github.com/laravel/framework/compare/v6.19.1...v6.20.0) + +### Added +- Full PHP 8.0 Support ([#33388](https://github.com/laravel/framework/pull/33388)) +- Added `Illuminate\Support\Reflector::isCallable()` ([#34994](https://github.com/laravel/framework/pull/34994), [8c16891](https://github.com/laravel/framework/commit/8c16891c6e7a4738d63788f4447614056ab5136e), [31917ab](https://github.com/laravel/framework/commit/31917abcfa0db6ec6221bb07fc91b6e768ff5ec8), [11cfa4d](https://github.com/laravel/framework/commit/11cfa4d4c92bf2f023544d58d51b35c5d31dece0), [#34999](https://github.com/laravel/framework/pull/34999)) + +### Changed +- Bump minimum PHP version to v7.2.5 ([#34928](https://github.com/laravel/framework/pull/34928)) + +### Fixed +- Fixed ambigious column on many to many with select load ([5007986](https://github.com/laravel/framework/commit/500798623d100a9746b2931ae6191cb756521f05)) + + +## [v6.19.1 (2020-10-20)](https://github.com/laravel/framework/compare/v6.19.0...v6.19.1) + +### Fixed +- Fixed `bound()` method ([a7759d7](https://github.com/laravel/framework/commit/a7759d70e15b0be946569b8299ac694c08a35d7e)) + + +## [v6.19.0 (2020-10-20)](https://github.com/laravel/framework/compare/v6.18.43...v6.19.0) + +### Added +- Provisional support for PHP 8.0 ([#34884](https://github.com/laravel/framework/pull/34884), [28bb76e](https://github.com/laravel/framework/commit/28bb76efbcfc5fee57307ffa062b67ff709240dc)) + + +## [v6.18.43 (2020-10-13)](https://github.com/laravel/framework/compare/v6.18.42...v6.18.43) + +### Fixed +- Matched `symfony/debug` version with other symfony reqs ([6ce02a2](https://github.com/laravel/framework/commit/6ce02a21cf736f28beda2529d1e28849e86b0944)) + + +## [v6.18.42 (2020-10-06)](https://github.com/laravel/framework/compare/v6.18.41...v6.18.42) + +### Fixed +- Added missed RESET_THROTTLED constant to Password Facade ([#34641](https://github.com/laravel/framework/pull/34641)) + + +## [v6.18.41 (2020-09-29)](https://github.com/laravel/framework/compare/v6.18.40...v6.18.41) + +### Fixed +- Added support for stream reads in FileManager for S3 driver ([#34480](https://github.com/laravel/framework/pull/34480)) + + +## [v6.18.40 (2020-09-09)](https://github.com/laravel/framework/compare/v6.18.39...v6.18.40) + +### Revert +- Revert of ["Fixed for empty fallback_locale in `Illuminate\Translation\Translator`"](https://github.com/laravel/framework/pull/34136) ([7c54eb6](https://github.com/laravel/framework/commit/7c54eb678d58fb9ee7f532a5a5842e6f0e1fe4c9)) + + +## [v6.18.39 (2020-09-08)](https://github.com/laravel/framework/compare/v6.18.38...v6.18.39) + +### Fixed +- Fixed for empty fallback_locale in `Illuminate\Translation\Translator` ([#34136](https://github.com/laravel/framework/pull/34136)) + + +## [v6.18.38 (2020-09-01)](https://github.com/laravel/framework/compare/v6.18.37...v6.18.38) + +### Changed +- Changed postgres processor ([#34055](https://github.com/laravel/framework/pull/34055)) + + +## [v6.18.37 (2020-08-27)](https://github.com/laravel/framework/compare/v6.18.36...v6.18.37) + +### Fixed +- Fixed offset error on invalid remember token ([#34020](https://github.com/laravel/framework/pull/34020)) +- Only prepend scheme to PhpRedis host when necessary ([#34017](https://github.com/laravel/framework/pull/34017)) +- Fixed `whereKey` and `whereKeyNot` in `Illuminate\Database\Eloquent\Builder` ([#34031](https://github.com/laravel/framework/pull/34031)) + + +## [v6.18.36 (2020-08-25)](https://github.com/laravel/framework/compare/v6.18.35...v6.18.36) + +### Fixed +- Fix dimension ratio calculation in `Illuminate\Validation\Concerns\ValidatesAttributes::failsRatioCheck()` ([#34003](https://github.com/laravel/framework/pull/34003)) + +### Changed +- Normalize scheme in Redis connections ([#33892](https://github.com/laravel/framework/pull/33892)) +- Check no-interaction flag exists and is true for Artisan commands ([#33950](https://github.com/laravel/framework/pull/33950)) + + +## [v6.18.35 (2020-08-07)](https://github.com/laravel/framework/compare/v6.18.34...v6.18.35) + +### Changed +- Verify column names are actual columns when using guarded ([#33777](https://github.com/laravel/framework/pull/33777)) + + +## [v6.18.34 (2020-08-06)](https://github.com/laravel/framework/compare/v6.18.33...v6.18.34) + +### Fixed +- Fixed `Illuminate\Support\Arr::query()` ([c6f9ae2](https://github.com/laravel/framework/commit/c6f9ae2b6fdc3c1716938223de731b97f6a5a255)) +- Don't allow mass filling with table names ([9240404](https://github.com/laravel/framework/commit/9240404b22ef6f9e827577b3753e4713ddce7471), [f5fa6e3](https://github.com/laravel/framework/commit/f5fa6e3a0fbf9a93eab45b9ae73265b4dbfc3ad7)) + + +## [v6.18.33 (2020-08-06)](https://github.com/laravel/framework/compare/v6.18.32...v6.18.33) + +### Fixed +- Fixed `Illuminate\Database\Eloquent\Concerns\GuardsAttributes::isGuarded()` ([1b70bef](https://github.com/laravel/framework/commit/1b70bef5fd7cc5da74abcdf79e283f830fa3b0a4), [624d873](https://github.com/laravel/framework/commit/624d873733388aa2246553a3b465e38554953180), [b70876a](https://github.com/laravel/framework/commit/b70876ac80759fbf168c91cdffd7a2b2305e27cb)) +- Fixed escaping quotes ([687df01](https://github.com/laravel/framework/commit/687df01fa19c99546c1ae1dd53c2a465459b50dc)) + + +## [v6.18.32 (2020-08-04)](https://github.com/laravel/framework/compare/v6.18.31...v6.18.32) + +### Changed +- Ignore numeric field names in validators ([#33712](https://github.com/laravel/framework/pull/33712)) +- Fixed validation rule 'required_unless' when other field value is boolean. ([#33715](https://github.com/laravel/framework/pull/33715)) + + +## [v6.18.31 (2020-07-27)](https://github.com/laravel/framework/compare/v6.18.30...v6.18.31) + +### Update +- Update cookies encryption ([release](https://github.com/laravel/framework/compare/v6.18.30...v6.18.31)) + + +## [v6.18.30 (2020-07-27)](https://github.com/laravel/framework/compare/v6.18.29...v6.18.30) + +### Update +- Update cookies encryption ([release](https://github.com/laravel/framework/compare/v6.18.29...v6.18.30)) + + +## [v6.18.29 (2020-07-27)](https://github.com/laravel/framework/compare/v6.18.28...v6.18.29) + +### Fixed +- Fixed cookie issues encryption ([c9ce261](https://github.com/laravel/framework/commit/c9ce261a9f7b8e07c9ebc8a7d45651ee1cf86215), [5786aa4](https://github.com/laravel/framework/commit/5786aa4a388adfcc62862573275bd37d49aa07d7)) + + +## [v6.18.28 (2020-07-27)](https://github.com/laravel/framework/compare/v6.18.27...v6.18.28) + +### Fixed +- Fixed cookie issues ([bb9db21](https://github.com/laravel/framework/commit/bb9db21af137344feffa192fcabe4e439c8b0f60)) + + +## [v6.18.27 (2020-07-27)](https://github.com/laravel/framework/compare/v6.18.26...v6.18.27) + +### Fixed +- Don't decrement transaction below 0 in `Illuminate\Database\Concerns\ManagesTransactions::handleCommitTransactionException()` ([7681795](https://github.com/laravel/framework/commit/768179578e5492b5f80c391bd43b233938e16e27)) +- Fixed transaction problems on closure transaction ([c4cdfc7](https://github.com/laravel/framework/commit/c4cdfc7c54127b772ef10f37cfc9ef8e9d6b3227)) +- Prevent to serialize uninitialized properties ([#33644](https://github.com/laravel/framework/pull/33644)) +- Fixed missing statement preventing deletion in `Illuminate\Database\Eloquent\Relations\MorphPivot::delete()` ([#33648](https://github.com/laravel/framework/pull/33648)) + +### Changed +- Improve cookie encryption ([#33662](https://github.com/laravel/framework/pull/33662)) + + +## [v6.18.26 (2020-07-21)](https://github.com/laravel/framework/compare/v6.18.25...v6.18.26) + +### Fixed +- Align (fix) nested arrays support for `assertViewHas` & `assertViewMissing` in `Illuminate\Testing\TestResponse` ([#33566](https://github.com/laravel/framework/pull/33566)) ## [v6.18.25 (2020-07-10)](https://github.com/laravel/framework/compare/v6.18.24...v6.18.25) @@ -337,7 +571,7 @@ ### Changed - Use SKIP LOCKED for mysql 8.1 and pgsql 9.5 queue workers ([#31287](https://github.com/laravel/framework/pull/31287)) -- Dont merge middleware from method and property in `Illuminate\Bus\Queueable::middleware()` ([#31301](https://github.com/laravel/framework/pull/31301)) +- Don't merge middleware from method and property in `Illuminate\Bus\Queueable::middleware()` ([#31301](https://github.com/laravel/framework/pull/31301)) - Split `specifyParameter()` from `Illuminate\Console\Command` to `HasParameters` trait ([#31254](https://github.com/laravel/framework/pull/31254)) - Make sure changing a database field to json does not include charset ([#31343](https://github.com/laravel/framework/pull/31343)) @@ -476,7 +710,7 @@ - Fixed `Builder::withCount()` binding error when a scope is added into related model with binding in a sub-select ([#30869](https://github.com/laravel/framework/pull/30869)) ### Changed -- Dont throw exception when session is not set in `AuthenticateSession` middleware ([4de1d24](https://github.com/laravel/framework/commit/4de1d24cf390f07d4f503973e5556f73060fbb31)) +- Don't throw exception when session is not set in `AuthenticateSession` middleware ([4de1d24](https://github.com/laravel/framework/commit/4de1d24cf390f07d4f503973e5556f73060fbb31)) ## [v6.8.0 (2019-12-17)](https://github.com/laravel/framework/compare/v6.7.0...v6.8.0) diff --git a/CHANGELOG-7.x.md b/CHANGELOG-7.x.md index a1d1839a4ce1..36d7fe5e06fc 100644 --- a/CHANGELOG-7.x.md +++ b/CHANGELOG-7.x.md @@ -1,6 +1,295 @@ # Release Notes for 7.x -## [Unreleased](https://github.com/laravel/framework/compare/v7.19.1...7.x) +## [Unreleased](https://github.com/laravel/framework/compare/v7.30.3...7.x) + + +## [v7.30.3 (2021-01-15)](https://github.com/laravel/framework/compare/v7.30.2...v7.30.3) + + +## [v7.30.2 (2021-01-13)](https://github.com/laravel/framework/compare/v7.30.1...v7.30.2) + +### Added +- Added strings to `DetectsLostConnections` ([#35752](https://github.com/laravel/framework/pull/35752)) + +### Fixed +- Fixed error from missing null check on PHP 8 ([#35797](https://github.com/laravel/framework/pull/35797)) +- Limit expected bindings ([#35865](https://github.com/laravel/framework/pull/35865)) + +### Changed +- Retry connection if DNS lookup fails ([#35790](https://github.com/laravel/framework/pull/35790)) + + +## [v7.30.1 (2020-12-22)](https://github.com/laravel/framework/compare/v7.30.0...v7.30.1) + +### Fixed +- Backport for fix issue with polymorphic morphMaps with literal 0 ([#35487](https://github.com/laravel/framework/pull/35487)) +- Fixed mime validation for jpeg files ([#35518](https://github.com/laravel/framework/pull/35518)) +- Fixed `Illuminate\Validation\Concerns\ValidatesAttributes::validateJson()` for PHP8 ([#35646](https://github.com/laravel/framework/pull/35646)) +- Catch DecryptException with invalid X-XSRF-TOKEN in `Illuminate\Foundation\Http\Middleware\VerifyCsrfToken` ([#35671](https://github.com/laravel/framework/pull/35671)) + + +## [v7.30.0 (2020-12-01)](https://github.com/laravel/framework/compare/v7.29.3...v7.30.0) + +### Fixed +- Turn the eloquent collection into a base collection if mapWithKeys loses models ([#35129](https://github.com/laravel/framework/pull/35129)) +- Fixed pivot restoration ([#35218](https://github.com/laravel/framework/pull/35218)) +- Fixing BroadcastException message in PusherBroadcaster@broadcast ([#35290](https://github.com/laravel/framework/pull/35290)) +- Fixed generic DetectsLostConnection string ([#35323](https://github.com/laravel/framework/pull/35323)) +- Backport Redis context option ([#35370](https://github.com/laravel/framework/pull/35370)) +- Fixed validating image/jpeg images after Symfony/Mime update ([#35419](https://github.com/laravel/framework/pull/35419)) + +### Changed +- Updated `aws/aws-sdk-php` suggest to `^3.155` ([#35267](https://github.com/laravel/framework/pull/35267)) + + +## [v7.29.3 (2020-11-03)](https://github.com/laravel/framework/compare/v7.29.2...v7.29.3) + +### Fixed +- Added php 8 support for Illuminate Testing 7.x ([#35045](https://github.com/laravel/framework/pull/35045)) + + +## [v7.29.2 (2020-10-29)](https://github.com/laravel/framework/compare/v7.29.1...v7.29.2) + +### Fixed +- [Add some fixes](https://github.com/laravel/framework/compare/v7.29.1...v7.29.2) + + +## [v7.29.1 (2020-10-29)](https://github.com/laravel/framework/compare/v7.29.0...v7.29.1) + +### Fixed +- Fixed alias usage in `Eloquent` ([6091048](https://github.com/laravel/framework/commit/609104806b8b639710268c75c22f43034c2b72db)) +- Fixed `Illuminate\Support\Reflector::isCallable()` ([a90f344](https://github.com/laravel/framework/commit/a90f344c66f0a5bb1d718f8bbd20c257d4de9e02)) + + +## [v7.29.0 (2020-10-29)](https://github.com/laravel/framework/compare/v7.28.4...v7.29.0) + +### Added +- Full PHP 8.0 Support ([#34884](https://github.com/laravel/framework/pull/34884), [28bb76e](https://github.com/laravel/framework/commit/28bb76efbcfc5fee57307ffa062b67ff709240dc), [#33388](https://github.com/laravel/framework/pull/33388)) +- Added `Illuminate\Support\Reflector::isCallable()` ([#34994](https://github.com/laravel/framework/pull/34994), [8c16891](https://github.com/laravel/framework/commit/8c16891c6e7a4738d63788f4447614056ab5136e), [31917ab](https://github.com/laravel/framework/commit/31917abcfa0db6ec6221bb07fc91b6e768ff5ec8), [11cfa4d](https://github.com/laravel/framework/commit/11cfa4d4c92bf2f023544d58d51b35c5d31dece0), [#34999](https://github.com/laravel/framework/pull/34999)) + +### Changed +- Bump minimum PHP version to v7.2.5 ([#34928](https://github.com/laravel/framework/pull/34928)) + +### Fixed +- Fixed ambigious column on many to many with select load ([5007986](https://github.com/laravel/framework/commit/500798623d100a9746b2931ae6191cb756521f05)) + + +## [v7.28.4 (2020-10-06)](https://github.com/laravel/framework/compare/v7.28.3...v7.28.4) + +### Fixed +- Added missed RESET_THROTTLED constant to Password Facade ([#34641](https://github.com/laravel/framework/pull/34641)) + + +## [v7.28.3 (2020-09-17)](https://github.com/laravel/framework/compare/v7.28.2...v7.28.3) + +### Fixed +- Fixed problems with dots in validator ([#34355](https://github.com/laravel/framework/pull/34355)) + + +## [v7.28.2 (2020-09-15)](https://github.com/laravel/framework/compare/v7.28.1...v7.28.2) + +### Fixed +- Do not used `now` helper in `Illuminate/Cache/DatabaseLock::expiresAt()` ([#34262](https://github.com/laravel/framework/pull/34262)) +- Fixed `Illuminate\View\ComponentAttributeBag::whereDoesntStartWith()` ([#34329](https://github.com/laravel/framework/pull/34329)) + + +## [v7.28.1 (2020-09-09)](https://github.com/laravel/framework/compare/v7.28.0...v7.28.1) + +### Revert +- Revert of ["Fixed for empty fallback_locale in `Illuminate\Translation\Translator`"](https://github.com/laravel/framework/pull/34136) ([7c54eb6](https://github.com/laravel/framework/commit/7c54eb678d58fb9ee7f532a5a5842e6f0e1fe4c9)) + + +## [v7.28.0 (2020-09-08)](https://github.com/laravel/framework/compare/v7.27.0...v7.28.0) + +### Added +- Added expectsTable console assertion ([74e1fca](https://github.com/laravel/framework/commit/74e1fca5fa333e32e24a7aa24049d5303a1bf281), [c6cf381](https://github.com/laravel/framework/commit/c6cf38139d2524a7c3accb606e3fb1b035c98d6a)) + +### Fixed +- Use `getTouchedRelations` when touching owners ([#34100](https://github.com/laravel/framework/pull/34100)) +- Fixed for empty fallback_locale in `Illuminate\Translation\Translator` ([#34136](https://github.com/laravel/framework/pull/34136)) +- Fixed `Illuminate\Database\Schema\Grammars\SqlServerGrammar::compileColumnListing()` for tables with schema ([#34076](https://github.com/laravel/framework/pull/34076)) +- Fixed Significant performance issue in Eloquent Collection loadCount() method ([#34177](https://github.com/laravel/framework/pull/34177)) + + +## [v7.27.0 (2020-09-01)](https://github.com/laravel/framework/compare/v7.26.1...v7.27.0) + +### Added +- Allow to use alias of morphed model ([#34032](https://github.com/laravel/framework/pull/34032)) +- Introduced basic padding (both, left, right) methods to Str and Stringable ([#34053](https://github.com/laravel/framework/pull/34053)) + +### Refactoring +- RefreshDatabase migration commands parameters moved to methods ([#34007](https://github.com/laravel/framework/pull/34007), [8b35c8e](https://github.com/laravel/framework/commit/8b35c8e6ba5879e71fd81fd03b5687ee2b46c55a), [256f71c](https://github.com/laravel/framework/commit/256f71c1f81da2d4bb3e327b18389ac43fa97a72)) + +### Changed +- Allow to reset forced scheme and root-url in UrlGenerator ([#34039](https://github.com/laravel/framework/pull/34039)) +- Updating the make commands to use a custom views path ([#34060](https://github.com/laravel/framework/pull/34060), [b593c62](https://github.com/laravel/framework/commit/b593c6242942623fcc12638d0390da7c58dbbb11)) +- Using "public static property" in View Component causes an error ([#34058](https://github.com/laravel/framework/pull/34058)) +- Changed postgres processor ([#34055](https://github.com/laravel/framework/pull/34055)) + + +## [v7.26.1 (2020-08-27)](https://github.com/laravel/framework/compare/v7.26.0...v7.26.1) + +### Fixed +- Fixed offset error on invalid remember token ([#34020](https://github.com/laravel/framework/pull/34020)) +- Only prepend scheme to PhpRedis host when necessary ([#34017](https://github.com/laravel/framework/pull/34017)) +- Fixed `whereKey` and `whereKeyNot` in `Illuminate\Database\Eloquent\Builder` ([#34031](https://github.com/laravel/framework/pull/34031)) + + +## [v7.26.0 (2020-08-25)](https://github.com/laravel/framework/compare/v7.25.0...v7.26.0) + +### Added +- Added `whenHas` and `whenFilled` methods to `Illuminate\Http\Concerns\InteractsWithInput` class ([#33829](https://github.com/laravel/framework/pull/33829)) +- Added email validating with custom class ([#33835](https://github.com/laravel/framework/pull/33835)) +- Added `Illuminate\View\ComponentAttributeBag::whereDoesntStartWith()` ([#33851](https://github.com/laravel/framework/pull/33851)) +- Allow setting synchronous_commit for Postgres ([#33897](https://github.com/laravel/framework/pull/33897)) +- Allow nested errors in `Illuminate\Testing\TestResponse::assertJsonValidationErrors()` ([#33989](https://github.com/laravel/framework/pull/33989)) +- Added support for stream reads to `FilesystemManager` ([#34001](https://github.com/laravel/framework/pull/34001)) + +### Fixed +- Fix defaultTimezone not respected in scheduled Events ([#33834](https://github.com/laravel/framework/pull/33834)) +- Fixed usage of Support `Collection#countBy($key)` ([#33852](https://github.com/laravel/framework/pull/33852)) +- Fixed route registerar bug ([42ba0ef](https://github.com/laravel/framework/commit/42ba0ef3e379cb1e0fa38c3d3297109ff1234a1d)) +- Fixed key composition for attribute with dot at validation error messages ([#33932](https://github.com/laravel/framework/pull/33932)) +- Fixed the `dump` method for `LazyCollection` ([#33944](https://github.com/laravel/framework/pull/33944)) +- Fixed dimension ratio calculation in `Illuminate\Validation\Concerns\ValidatesAttributes::failsRatioCheck()` ([#34003](https://github.com/laravel/framework/pull/34003)) + +### Changed +- Implement LockProvider on DatabaseStore ([#33844](https://github.com/laravel/framework/pull/33844)) +- Publish resources.stub in stub:publish command ([#33862](https://github.com/laravel/framework/pull/33862)) +- Handle argon failures robustly ([#33856](https://github.com/laravel/framework/pull/33856)) +- Normalize scheme in Redis connections ([#33892](https://github.com/laravel/framework/pull/33892)) +- Cast primary key to string when $keyType is string ([#33930](https://github.com/laravel/framework/pull/33930)) +- Load anonymous components from packages ([#33954](https://github.com/laravel/framework/pull/33954)) +- Check no-interaction flag exists and is true for Artisan commands ([#33950](https://github.com/laravel/framework/pull/33950)) + +### Deprecated +- Deprecate `Illuminate\Database\Eloquent\Model::removeTableFromKey()` ([#33859](https://github.com/laravel/framework/pull/33859)) + + +## [v7.25.0 (2020-08-11)](https://github.com/laravel/framework/compare/v7.24.0...v7.25.0) + +### Added +- Added support to use `where` in `apiResource` method ([#33790](https://github.com/laravel/framework/pull/33790), [3dcc4a6](https://github.com/laravel/framework/commit/3dcc4a6bc6640b3d577c6740d63b6ef3df42e124)) +- Support `tls://` scheme when using `url` in Redis config ([#33800](https://github.com/laravel/framework/pull/33800)) +- Scoped resource routes ([#33752](https://github.com/laravel/framework/pull/33752)) +- Added Once blade Blocks ([#33812](https://github.com/laravel/framework/pull/33812)) +- Let mailables accept a simple array of email addresses as cc or bcc ([#33810](https://github.com/laravel/framework/pull/33810)) +- Added support for PhpRedis 5.3 options parameter ([#33799](https://github.com/laravel/framework/pull/33799)) + +### Changed +- Removed quotes when setting isolation level for mysql connections ([#33805](https://github.com/laravel/framework/pull/33805)) +- Make LazyCollection#countBy be lazy ([#33801](https://github.com/laravel/framework/pull/33801)) + +### Fixed +- Revert changes to MailMessage ([#33816](https://github.com/laravel/framework/pull/33816)) + + +## [v7.24.0 (2020-08-07)](https://github.com/laravel/framework/compare/v7.23.2...v7.24.0) + +### Added +- Added possibility to configure isolation level for mysql connections ([#33783](https://github.com/laravel/framework/pull/33783), [c6a3174](https://github.com/laravel/framework/commit/c6a317405e5e9075206a019246a8a79d0c68def4)) +- Added plain text only notifications ([#33781](https://github.com/laravel/framework/pull/33781)) + +### Changed +- Verify column names are actual columns when using guarded ([#33777](https://github.com/laravel/framework/pull/33777)) + + +## [v7.23.2 (2020-08-06)](https://github.com/laravel/framework/compare/v7.23.1...v7.23.2) + +### Fixed +- Fixed `Illuminate\Support\Arr::query()` ([c6f9ae2](https://github.com/laravel/framework/commit/c6f9ae2b6fdc3c1716938223de731b97f6a5a255)) +- Don't allow mass filling with table names ([9240404](https://github.com/laravel/framework/commit/9240404b22ef6f9e827577b3753e4713ddce7471), [f5fa6e3](https://github.com/laravel/framework/commit/f5fa6e3a0fbf9a93eab45b9ae73265b4dbfc3ad7)) + + +## [v7.23.1 (2020-08-06)](https://github.com/laravel/framework/compare/v7.23.0...v7.23.1) + +### Added +- Added isNotFilled() method to Request ([#33732](https://github.com/laravel/framework/pull/33732)) + +### Fixed +- Fixed `Illuminate\Database\Eloquent\Concerns\GuardsAttributes::isGuarded()` ([1b70bef](https://github.com/laravel/framework/commit/1b70bef5fd7cc5da74abcdf79e283f830fa3b0a4), [624d873](https://github.com/laravel/framework/commit/624d873733388aa2246553a3b465e38554953180), [b70876a](https://github.com/laravel/framework/commit/b70876ac80759fbf168c91cdffd7a2b2305e27cb)) +- Fixed escaping quotes ([687df01](https://github.com/laravel/framework/commit/687df01fa19c99546c1ae1dd53c2a465459b50dc)) + + +## [v7.23.0 (2020-08-04)](https://github.com/laravel/framework/compare/v7.22.4...v7.23.0) + +### Added +- Added dynamic slot (directive) name support ([#33724](https://github.com/laravel/framework/pull/33724)) +- Added plain mail to notifications ([#33725](https://github.com/laravel/framework/pull/33725)) +- Support the `sink` option when using Http::fake() ([#33720](https://github.com/laravel/framework/pull/33720), [fba984b](https://github.com/laravel/framework/commit/fba984b05081f8aee19447caa0d92624bcf04312)) +- Added whereBetweenColumns | orWhereBetweenColumns | whereNotBetweenColumns | orWhereNotBetweenColumns methods to `Illuminate\Database\Query\Builder` ([#33728](https://github.com/laravel/framework/pull/33728)) + +### Changed +- Ignore numeric field names in validators ([#33712](https://github.com/laravel/framework/pull/33712)) +- Fixed validation rule 'required_unless' when other field value is boolean. ([#33715](https://github.com/laravel/framework/pull/33715)) + + +## [v7.22.4 (2020-07-27)](https://github.com/laravel/framework/compare/v7.22.3...v7.22.4) + +### Update +- Update cookies encryption ([release](https://github.com/laravel/framework/compare/v7.22.3...v7.22.4)) + + +## [v7.22.3 (2020-07-27)](https://github.com/laravel/framework/compare/v7.22.2...v7.22.3) + +### Update +- Update cookies encryption ([release](https://github.com/laravel/framework/compare/v7.22.2...v7.22.3)) + + +## [v7.22.2 (2020-07-27)](https://github.com/laravel/framework/compare/v7.22.1...v7.22.2) + +### Fixed +- Fixed cookie issues encryption ([c9ce261](https://github.com/laravel/framework/commit/c9ce261a9f7b8e07c9ebc8a7d45651ee1cf86215), [5786aa4](https://github.com/laravel/framework/commit/5786aa4a388adfcc62862573275bd37d49aa07d7)) + + +## [v7.22.1 (2020-07-27)](https://github.com/laravel/framework/compare/v7.22.0...v7.22.1) + +### Fixed +- Fixed cookie issues ([bb9db21](https://github.com/laravel/framework/commit/bb9db21af137344feffa192fcabe4e439c8b0f60)) + + +## [v7.22.0 (2020-07-27)](https://github.com/laravel/framework/compare/v7.21.0...v7.22.0) + +### Added +- Added `sectionMissing` Blade Directive ([#33614](https://github.com/laravel/framework/pull/33614)) +- Added range option to queue:retry command ([#33627](https://github.com/laravel/framework/pull/33627)) + +### Fixed +- Prevent usage of get*AtColumn() when model has no timestamps ([#33634](https://github.com/laravel/framework/pull/33634)) +- Don't decrement transaction below 0 in `Illuminate\Database\Concerns\ManagesTransactions::handleCommitTransactionException()` ([7681795](https://github.com/laravel/framework/commit/768179578e5492b5f80c391bd43b233938e16e27)) +- Fixed transaction problems on closure transaction ([c4cdfc7](https://github.com/laravel/framework/commit/c4cdfc7c54127b772ef10f37cfc9ef8e9d6b3227)) +- Prevent to serialize uninitialized properties ([#33644](https://github.com/laravel/framework/pull/33644)) +- Fixed missing statement preventing deletion in `Illuminate\Database\Eloquent\Relations\MorphPivot::delete()` ([#33648](https://github.com/laravel/framework/pull/33648)) + +### Changed +- Throw a TypeError if concrete is not a string or closure in `Illuminate\Container\Container::bind()` ([#33539](https://github.com/laravel/framework/pull/33539)) +- Add HTML comment block around inline inspiring quote for consistency with blade template version ([#33625](https://github.com/laravel/framework/pull/33625)) +- Improve cookie encryption ([#33662](https://github.com/laravel/framework/pull/33662)) + + +## [v7.21.0 (2020-07-21)](https://github.com/laravel/framework/compare/v7.20.0...v7.21.0) + +### Added +- Added `Illuminate\Database\Schema\ForeignKeyDefinition::nullOnDelete()` ([#33551](https://github.com/laravel/framework/pull/33551)) +- Added `getFallbackLocale()` and `setFallbackLocale()` methods to `Illuminate\Foundation\Application` ([#33595](https://github.com/laravel/framework/pull/33595)) + +### Fixed +- Fixed `Illuminate/Redis/Connections/PhpRedisConnection::*scan()` returns ([d3d36f0](https://github.com/laravel/framework/commit/d3d36f059ef1c56e17d8e434e9fd3dfd6cbe6e53)) +- Align (fix) nested arrays support for `assertViewHas` & `assertViewMissing` in `Illuminate\Testing\TestResponse` ([#33566](https://github.com/laravel/framework/pull/33566)) +- Fixed issue where Storage::path breaks when using cache due to missing method in CachedAdapter ([#33602](https://github.com/laravel/framework/pull/33602)) + +### Changed +- Added a base exception for Http Client exceptions ([#33581](https://github.com/laravel/framework/pull/33581)) + + +## [v7.20.0 (2020-07-14)](https://github.com/laravel/framework/compare/v7.19.1...v7.20.0) + +### Added +- Added `Illuminate\Database\Schema\ForeignKeyDefinition::cascadeOnUpdate()` ([#33522](https://github.com/laravel/framework/pull/33522)) + +### Changed +- Apply model connection name to Database validation rules ([#33525](https://github.com/laravel/framework/pull/33525)) +- Allow calling invokable classes using FQN in `Illuminate\Container\BoundMethod.php::call()` ([#33535](https://github.com/laravel/framework/pull/33535)) ## [v7.19.1 (2020-07-10)](https://github.com/laravel/framework/compare/v7.19.0...v7.19.1) @@ -182,7 +471,7 @@ - Fixed `Illuminate\Cache\ArrayStore::increment()` bug that changes expiration to forever ([#32875](https://github.com/laravel/framework/pull/32875)) ### Changed -- Dont cache non objects in `Illuminate/Database/Eloquent/Concerns/HasAttributes::getClassCastableAttributeValue()` ([894fe22](https://github.com/laravel/framework/commit/894fe22c6c111b224de5bada24dcbba4c93f0305)) +- Don't cache non objects in `Illuminate/Database/Eloquent/Concerns/HasAttributes::getClassCastableAttributeValue()` ([894fe22](https://github.com/laravel/framework/commit/894fe22c6c111b224de5bada24dcbba4c93f0305)) - Added explicit `symfony/polyfill-php73` dependency ([5796b1e](https://github.com/laravel/framework/commit/5796b1e43dfe14914050a7e5dd24ddf803ec99b8)) - Set `Cache\FileStore` file permissions only once ([#32845](https://github.com/laravel/framework/pull/32845), [11c533b](https://github.com/laravel/framework/commit/11c533b9aa062f4cba1dd0fe3673bf33d275480f)) - Added alias as key of package's view components ([#32863](https://github.com/laravel/framework/pull/32863)) @@ -673,7 +962,7 @@ - Fixed `trim` of the prefix in the `CompiledRouteCollection::newRoute()` ([ce0355c](https://github.com/laravel/framework/commit/ce0355c72bf4defb93ae80c7bf7812bd6532031a), [b842c65](https://github.com/laravel/framework/commit/b842c65ecfe1ea7839d61a46b177b6b5887fd4d2)) ### Changed -- remove comments before compiling components in the `BladeCompiler` ([2964d2d](https://github.com/laravel/framework/commit/2964d2dfd3cc50f7a709effee0af671c86587915)) +- Remove comments before compiling components in the `BladeCompiler` ([2964d2d](https://github.com/laravel/framework/commit/2964d2dfd3cc50f7a709effee0af671c86587915)) ## [v7.0.1 (2020-03-03)](https://github.com/laravel/framework/compare/v7.0.0...v7.0.1) diff --git a/README.md b/README.md index 7b54d65ca9c5..ef4bc184428e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

Build Status diff --git a/composer.json b/composer.json index b1b7e6eb759e..dea51b9ab5f1 100644 --- a/composer.json +++ b/composer.json @@ -15,18 +15,18 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^2.0", + "dragonmantank/cron-expression": "^2.3.1", "egulias/email-validator": "^2.1.10", "league/commonmark": "^1.3", - "league/flysystem": "^1.0.34", + "league/flysystem": "^1.1", "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.17", - "opis/closure": "^3.1", + "nesbot/carbon": "^2.31", + "opis/closure": "^3.6", "psr/container": "^1.0", "psr/simple-cache": "^1.0", "ramsey/uuid": "^3.7|^4.0", @@ -77,16 +77,16 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.0", + "aws/aws-sdk-php": "^3.155", "doctrine/dbal": "^2.6", - "filp/whoops": "^2.4", - "guzzlehttp/guzzle": "^6.3.1|^7.0", + "filp/whoops": "^2.8", + "guzzlehttp/guzzle": "^6.3.1|^7.0.1", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.3.1", + "mockery/mockery": "~1.3.3|^1.4.2", "moontoast/math": "^1.1", - "orchestra/testbench-core": "^5.0", + "orchestra/testbench-core": "^5.8", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.4|^9.0", + "phpunit/phpunit": "^8.4|^9.3.3", "predis/predis": "^1.1.1", "symfony/cache": "^5.0" }, @@ -125,20 +125,21 @@ "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", - "filp/whoops": "Required for friendly error pages in development (^2.4).", - "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0).", + "filp/whoops": "Required for friendly error pages in development (^2.8).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0.1).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.3.1).", + "mockery/mockery": "Required to use mocking (~1.3.3|^1.4.2).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.3.3).", + "predis/predis": "Required to use the predis connector (^1.1.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).", "symfony/cache": "Required to PSR-6 cache bridge (^5.0).", diff --git a/docker-compose.yml b/docker-compose.yml index dc02296a48b4..4b129f911cfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: memcached: - image: memcached:1.5-alpine + image: memcached:1.6-alpine ports: - "11211:11211" restart: always diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b310cbd3a4e4..bb20f5f6ded1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,18 +17,11 @@ ./tests - - - ./src - - ./src/ - - - + diff --git a/src/Illuminate/Auth/composer.json b/src/Illuminate/Auth/composer.json index 82316dd1008f..7ef7fccbc08b 100644 --- a/src/Illuminate/Auth/composer.json +++ b/src/Illuminate/Auth/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/http": "^7.0", "illuminate/queue": "^7.0", diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index b5deebc75ab7..3be0500d9122 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -95,7 +95,7 @@ public function socket($request = null) * Begin broadcasting an event. * * @param mixed|null $event - * @return \Illuminate\Broadcasting\PendingBroadcast|void + * @return \Illuminate\Broadcasting\PendingBroadcast */ public function event($event = null) { diff --git a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php index 68daf9da4b26..c39abbd6f61b 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php @@ -120,7 +120,7 @@ public function broadcast(array $channels, $event, array $payload = []) throw new BroadcastException( ! empty($response['body']) - ? sprintf('Pusher error: %s.', $response['status']) + ? sprintf('Pusher error: %s.', $response['body']) : 'Failed to connect to Pusher.' ); } diff --git a/src/Illuminate/Broadcasting/composer.json b/src/Illuminate/Broadcasting/composer.json index 6abaeb3c21a5..ecfc18c32c23 100644 --- a/src/Illuminate/Broadcasting/composer.json +++ b/src/Illuminate/Broadcasting/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "psr/log": "^1.0", "illuminate/bus": "^7.0", diff --git a/src/Illuminate/Bus/composer.json b/src/Illuminate/Bus/composer.json index 7d03aea85107..7d4a72d3dc34 100644 --- a/src/Illuminate/Bus/composer.json +++ b/src/Illuminate/Bus/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/pipeline": "^7.0", "illuminate/support": "^7.0" diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php index b2f9f566a893..296f973bd2e2 100644 --- a/src/Illuminate/Cache/DatabaseLock.php +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -4,6 +4,7 @@ use Illuminate\Database\Connection; use Illuminate\Database\QueryException; +use Illuminate\Support\Carbon; class DatabaseLock extends Lock { @@ -92,7 +93,7 @@ public function acquire() */ protected function expiresAt() { - return $this->seconds > 0 ? time() + $this->seconds : now()->addDays(1)->getTimestamp(); + return $this->seconds > 0 ? time() + $this->seconds : Carbon::now()->addDays(1)->getTimestamp(); } /** diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index 41976f60a3b0..c868b145eed4 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -4,6 +4,7 @@ use Closure; use Exception; +use Illuminate\Contracts\Cache\LockProvider; use Illuminate\Contracts\Cache\Store; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\PostgresConnection; @@ -11,7 +12,7 @@ use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; -class DatabaseStore implements Store +class DatabaseStore implements LockProvider, Store { use InteractsWithTime, RetrievesMultipleKeys; diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 208ae94661be..ad50ce9adb41 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -179,7 +179,7 @@ protected function deleteValues($referenceKey) if (count($values) > 0) { foreach (array_chunk($values, 1000) as $valuesChunk) { - call_user_func_array([$this->store->connection(), 'del'], $valuesChunk); + $this->store->connection()->del(...$valuesChunk); } } } diff --git a/src/Illuminate/Cache/composer.json b/src/Illuminate/Cache/composer.json index a3565dca0c9a..adce54e6a7a6 100755 --- a/src/Illuminate/Cache/composer.json +++ b/src/Illuminate/Cache/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" }, diff --git a/src/Illuminate/Config/composer.json b/src/Illuminate/Config/composer.json index a76331305c87..3446109f2996 100755 --- a/src/Illuminate/Config/composer.json +++ b/src/Illuminate/Config/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" }, diff --git a/src/Illuminate/Console/Concerns/CallsCommands.php b/src/Illuminate/Console/Concerns/CallsCommands.php index 11da1d1fb303..e060c5562606 100644 --- a/src/Illuminate/Console/Concerns/CallsCommands.php +++ b/src/Illuminate/Console/Concerns/CallsCommands.php @@ -66,7 +66,7 @@ protected function runCommand($command, array $arguments, OutputInterface $outpu protected function createInputFromArguments(array $arguments) { return tap(new ArrayInput(array_merge($this->context(), $arguments)), function ($input) { - if ($input->hasParameterOption(['--no-interaction'], true)) { + if ($input->getParameterOption('--no-interaction')) { $input->setInteractive(false); } }); diff --git a/src/Illuminate/Console/Concerns/HasParameters.php b/src/Illuminate/Console/Concerns/HasParameters.php index 3f6f9c7642cf..e860ec2a2ec5 100644 --- a/src/Illuminate/Console/Concerns/HasParameters.php +++ b/src/Illuminate/Console/Concerns/HasParameters.php @@ -21,7 +21,7 @@ protected function specifyParameters() if ($arguments instanceof InputArgument) { $this->getDefinition()->addArgument($arguments); } else { - call_user_func_array([$this, 'addArgument'], $arguments); + $this->addArgument(...array_values($arguments)); } } @@ -29,7 +29,7 @@ protected function specifyParameters() if ($options instanceof InputOption) { $this->getDefinition()->addOption($options); } else { - call_user_func_array([$this, 'addOption'], $options); + $this->addOption(...array_values($options)); } } } diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index 1a6e333f2cb1..8af456b1fc08 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -366,6 +366,19 @@ protected function isReservedName($name) return in_array($name, $this->reservedNames); } + /** + * Get the first view directory path from the application configuration. + * + * @param string $path + * @return string + */ + protected function viewPath($path = '') + { + $views = $this->laravel['config']['view.paths'][0] ?? resource_path('views'); + + return $views.($path ? DIRECTORY_SEPARATOR.$path : $path); + } + /** * Get the console command arguments. * diff --git a/src/Illuminate/Console/Scheduling/CallbackEvent.php b/src/Illuminate/Console/Scheduling/CallbackEvent.php index 799c4afce7d3..6af680d990c0 100644 --- a/src/Illuminate/Console/Scheduling/CallbackEvent.php +++ b/src/Illuminate/Console/Scheduling/CallbackEvent.php @@ -3,6 +3,7 @@ namespace Illuminate\Console\Scheduling; use Illuminate\Contracts\Container\Container; +use Illuminate\Support\Reflector; use InvalidArgumentException; use LogicException; @@ -28,13 +29,14 @@ class CallbackEvent extends Event * @param \Illuminate\Console\Scheduling\EventMutex $mutex * @param string $callback * @param array $parameters + * @param \DateTimeZone|string|null $timezone * @return void * * @throws \InvalidArgumentException */ - public function __construct(EventMutex $mutex, $callback, array $parameters = []) + public function __construct(EventMutex $mutex, $callback, array $parameters = [], $timezone = null) { - if (! is_string($callback) && ! is_callable($callback)) { + if (! is_string($callback) && ! Reflector::isCallable($callback)) { throw new InvalidArgumentException( 'Invalid scheduled callback event. Must be a string or callable.' ); @@ -43,6 +45,7 @@ public function __construct(EventMutex $mutex, $callback, array $parameters = [] $this->mutex = $mutex; $this->callback = $callback; $this->parameters = $parameters; + $this->timezone = $timezone; } /** @@ -161,6 +164,6 @@ public function getSummaryForDisplay() return $this->description; } - return is_string($this->callback) ? $this->callback : 'Closure'; + return is_string($this->callback) ? $this->callback : 'Callback'; } } diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index ccff80adbc6c..dfb505045ed2 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -12,6 +12,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Reflector; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\ReflectsClosures; use Psr\Http\Client\ClientExceptionInterface; @@ -673,7 +674,7 @@ public function onOneServer() */ public function when($callback) { - $this->filters[] = is_callable($callback) ? $callback : function () use ($callback) { + $this->filters[] = Reflector::isCallable($callback) ? $callback : function () use ($callback) { return $callback; }; @@ -688,7 +689,7 @@ public function when($callback) */ public function skip($callback) { - $this->rejects[] = is_callable($callback) ? $callback : function () use ($callback) { + $this->rejects[] = Reflector::isCallable($callback) ? $callback : function () use ($callback) { return $callback; }; diff --git a/src/Illuminate/Console/Scheduling/Schedule.php b/src/Illuminate/Console/Scheduling/Schedule.php index 344971be7618..89ad97209ae3 100644 --- a/src/Illuminate/Console/Scheduling/Schedule.php +++ b/src/Illuminate/Console/Scheduling/Schedule.php @@ -91,7 +91,7 @@ public function __construct($timezone = null) public function call($callback, array $parameters = []) { $this->events[] = $event = new CallbackEvent( - $this->eventMutex, $callback, $parameters + $this->eventMutex, $callback, $parameters, $this->timezone ); return $event; diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 9967756728d0..41e0bad132d6 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0", "symfony/console": "^5.0", @@ -31,8 +31,8 @@ } }, "suggest": { - "dragonmantank/cron-expression": "Required to use scheduler (^2.0).", - "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.3.1|^7.0).", + "dragonmantank/cron-expression": "Required to use scheduler (^2.3.1).", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.3.1|^7.0.1).", "illuminate/bus": "Required to use the scheduled job dispatcher (^7.0).", "illuminate/container": "Required to use the scheduler (^7.0).", "illuminate/filesystem": "Required to use the generator command (^7.0).", diff --git a/src/Illuminate/Container/BoundMethod.php b/src/Illuminate/Container/BoundMethod.php index 5da3d7655372..c617bf79795f 100644 --- a/src/Illuminate/Container/BoundMethod.php +++ b/src/Illuminate/Container/BoundMethod.php @@ -33,9 +33,7 @@ public static function call($container, $callback, array $parameters = [], $defa } return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) { - return call_user_func_array( - $callback, static::getMethodDependencies($container, $callback, $parameters) - ); + return $callback(...array_values(static::getMethodDependencies($container, $callback, $parameters))); }); } @@ -126,7 +124,7 @@ protected static function getMethodDependencies($container, $callback, array $pa static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies); } - return array_merge($dependencies, $parameters); + return array_merge($dependencies, array_values($parameters)); } /** diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 205b1beb32bc..1eb489a4f06f 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -234,6 +234,10 @@ public function bind($abstract, $concrete = null, $shared = false) // bound into this container to the abstract type and we will just wrap it // up inside its own Closure to give us more convenience when extending. if (! $concrete instanceof Closure) { + if (! is_string($concrete)) { + throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null'); + } + $concrete = $this->getClosure($abstract, $concrete); } @@ -858,7 +862,7 @@ protected function resolveDependencies(array $dependencies) $results = []; foreach ($dependencies as $dependency) { - // If this dependency has a override for this particular build we will use + // If the dependency has an override for this particular build we will use // that instead as the value. Otherwise, we will continue with this run // of resolutions and let reflection attempt to determine the result. if ($this->hasParameterOverride($dependency)) { diff --git a/src/Illuminate/Container/composer.json b/src/Illuminate/Container/composer.json index 73e5d289711a..e65b8b84d2d5 100755 --- a/src/Illuminate/Container/composer.json +++ b/src/Illuminate/Container/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "psr/container": "^1.0" }, diff --git a/src/Illuminate/Contracts/Auth/Access/Authorizable.php b/src/Illuminate/Contracts/Auth/Access/Authorizable.php index 2f9657c57492..cedeb6ea3440 100644 --- a/src/Illuminate/Contracts/Auth/Access/Authorizable.php +++ b/src/Illuminate/Contracts/Auth/Access/Authorizable.php @@ -7,9 +7,9 @@ interface Authorizable /** * Determine if the entity has a given ability. * - * @param string $ability + * @param iterable|string $abilities * @param array|mixed $arguments * @return bool */ - public function can($ability, $arguments = []); + public function can($abilities, $arguments = []); } diff --git a/src/Illuminate/Contracts/Cache/Lock.php b/src/Illuminate/Contracts/Cache/Lock.php index 4f98d68d9301..7f01b1be3f33 100644 --- a/src/Illuminate/Contracts/Cache/Lock.php +++ b/src/Illuminate/Contracts/Cache/Lock.php @@ -24,7 +24,7 @@ public function block($seconds, $callback = null); /** * Release the lock. * - * @return void + * @return bool */ public function release(); diff --git a/src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php b/src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php index ea482326a514..808d005f5c1d 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php +++ b/src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php @@ -22,7 +22,7 @@ public function get($model, string $key, $value, array $attributes); * @param string $key * @param mixed $value * @param array $attributes - * @return array|string + * @return mixed */ public function set($model, string $key, $value, array $attributes); } diff --git a/src/Illuminate/Contracts/Database/Eloquent/CastsInboundAttributes.php b/src/Illuminate/Contracts/Database/Eloquent/CastsInboundAttributes.php index b9e3f922e689..4c7801b583f1 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/CastsInboundAttributes.php +++ b/src/Illuminate/Contracts/Database/Eloquent/CastsInboundAttributes.php @@ -11,7 +11,7 @@ interface CastsInboundAttributes * @param string $key * @param mixed $value * @param array $attributes - * @return array + * @return mixed */ public function set($model, string $key, $value, array $attributes); } diff --git a/src/Illuminate/Contracts/Debug/ExceptionHandler.php b/src/Illuminate/Contracts/Debug/ExceptionHandler.php index 05efd7021917..54381a179af2 100644 --- a/src/Illuminate/Contracts/Debug/ExceptionHandler.php +++ b/src/Illuminate/Contracts/Debug/ExceptionHandler.php @@ -12,7 +12,7 @@ interface ExceptionHandler * @param \Throwable $e * @return void * - * @throws \Exception + * @throws \Throwable */ public function report(Throwable $e); diff --git a/src/Illuminate/Contracts/Pagination/Paginator.php b/src/Illuminate/Contracts/Pagination/Paginator.php index f6bd6c0ee19f..49bafaa77156 100644 --- a/src/Illuminate/Contracts/Pagination/Paginator.php +++ b/src/Illuminate/Contracts/Pagination/Paginator.php @@ -86,7 +86,7 @@ public function currentPage(); public function hasPages(); /** - * Determine if there is more items in the data store. + * Determine if there are more items in the data store. * * @return bool */ diff --git a/src/Illuminate/Contracts/Session/Session.php b/src/Illuminate/Contracts/Session/Session.php index 0b429537e7ba..6a6e0a154702 100644 --- a/src/Illuminate/Contracts/Session/Session.php +++ b/src/Illuminate/Contracts/Session/Session.php @@ -56,7 +56,7 @@ public function all(); public function exists($key); /** - * Checks if an a key is present and not null. + * Checks if a key is present and not null. * * @param string|array $key * @return bool diff --git a/src/Illuminate/Contracts/Validation/Validator.php b/src/Illuminate/Contracts/Validation/Validator.php index f389d03e5080..f68498df8f52 100644 --- a/src/Illuminate/Contracts/Validation/Validator.php +++ b/src/Illuminate/Contracts/Validation/Validator.php @@ -10,6 +10,8 @@ interface Validator extends MessageProvider * Run the validator's rules against its data. * * @return array + * + * @throws \Illuminate\Validation\ValidationException */ public function validate(); @@ -17,6 +19,8 @@ public function validate(); * Get the attributes and values that were validated. * * @return array + * + * @throws \Illuminate\Validation\ValidationException */ public function validated(); diff --git a/src/Illuminate/Contracts/composer.json b/src/Illuminate/Contracts/composer.json index 4424cf6baaf6..5409fbc03c6f 100644 --- a/src/Illuminate/Contracts/composer.json +++ b/src/Illuminate/Contracts/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "psr/container": "^1.0", "psr/simple-cache": "^1.0" }, diff --git a/src/Illuminate/Cookie/CookieJar.php b/src/Illuminate/Cookie/CookieJar.php index 172eecd7a16d..fe0cb9ad7f18 100755 --- a/src/Illuminate/Cookie/CookieJar.php +++ b/src/Illuminate/Cookie/CookieJar.php @@ -143,7 +143,7 @@ public function queue(...$parameters) if (isset($parameters[0]) && $parameters[0] instanceof Cookie) { $cookie = $parameters[0]; } else { - $cookie = $this->make(...$parameters); + $cookie = $this->make(...array_values($parameters)); } if (! isset($this->queued[$cookie->getName()])) { diff --git a/src/Illuminate/Cookie/CookieValuePrefix.php b/src/Illuminate/Cookie/CookieValuePrefix.php new file mode 100644 index 000000000000..e39cb69fa6aa --- /dev/null +++ b/src/Illuminate/Cookie/CookieValuePrefix.php @@ -0,0 +1,29 @@ +cookies->set($key, $this->decryptCookie($key, $cookie)); + $value = $this->decryptCookie($key, $cookie); + + $hasValidPrefix = strpos($value, CookieValuePrefix::create($key, $this->encrypter->getKey())) === 0; + + $request->cookies->set( + $key, $hasValidPrefix ? CookieValuePrefix::remove($value) : null + ); } catch (DecryptException $e) { $request->cookies->set($key, null); } @@ -136,7 +143,11 @@ protected function encrypt(Response $response) } $response->headers->setCookie($this->duplicate( - $cookie, $this->encrypter->encrypt($cookie->getValue(), static::serialized($cookie->getName())) + $cookie, + $this->encrypter->encrypt( + CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey()).$cookie->getValue(), + static::serialized($cookie->getName()) + ) )); } diff --git a/src/Illuminate/Cookie/composer.json b/src/Illuminate/Cookie/composer.json index 03bb50bc7768..6e3cb7208132 100755 --- a/src/Illuminate/Cookie/composer.json +++ b/src/Illuminate/Cookie/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0", "symfony/http-foundation": "^5.0", diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index aa4357b3e3da..1dd4475290d6 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -40,7 +40,11 @@ public function transaction(Closure $callback, $attempts = 1) } try { - $this->commit(); + if ($this->transactions == 1) { + $this->getPdo()->commit(); + } + + $this->transactions = max(0, $this->transactions - 1); } catch (Throwable $e) { $this->handleCommitTransactionException( $e, $currentAttempt, $attempts @@ -49,6 +53,8 @@ public function transaction(Closure $callback, $attempts = 1) continue; } + $this->fireConnectionEvent('committed'); + return $callbackResult; } } @@ -189,7 +195,7 @@ public function commit() */ protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts) { - $this->transactions--; + $this->transactions = max(0, $this->transactions - 1); if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { diff --git a/src/Illuminate/Database/Connectors/MySqlConnector.php b/src/Illuminate/Database/Connectors/MySqlConnector.php index 4bd58551db6e..a7640859d77c 100755 --- a/src/Illuminate/Database/Connectors/MySqlConnector.php +++ b/src/Illuminate/Database/Connectors/MySqlConnector.php @@ -27,6 +27,8 @@ public function connect(array $config) $connection->exec("use `{$config['database']}`;"); } + $this->configureIsolationLevel($connection, $config); + $this->configureEncoding($connection, $config); // Next, we will check to see if a timezone has been specified in this config @@ -40,12 +42,30 @@ public function connect(array $config) } /** - * Set the connection character set and collation. + * Set the connection transaction isolation level. * * @param \PDO $connection * @param array $config * @return void */ + protected function configureIsolationLevel($connection, array $config) + { + if (! isset($config['isolation_level'])) { + return; + } + + $connection->prepare( + "SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}" + )->execute(); + } + + /** + * Set the connection character set and collation. + * + * @param \PDO $connection + * @param array $config + * @return void|\PDO + */ protected function configureEncoding($connection, array $config) { if (! isset($config['charset'])) { diff --git a/src/Illuminate/Database/Connectors/PostgresConnector.php b/src/Illuminate/Database/Connectors/PostgresConnector.php index c40369d75f94..a3ca25e96323 100755 --- a/src/Illuminate/Database/Connectors/PostgresConnector.php +++ b/src/Illuminate/Database/Connectors/PostgresConnector.php @@ -47,6 +47,8 @@ public function connect(array $config) // determine if the option has been specified and run a statement if so. $this->configureApplicationName($connection, $config); + $this->configureSynchronousCommit($connection, $config); + return $connection; } @@ -173,4 +175,20 @@ protected function addSslOptions($dsn, array $config) return $dsn; } + + /** + * Configure the synchronous_commit setting. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureSynchronousCommit($connection, array $config) + { + if (! isset($config['synchronous_commit'])) { + return; + } + + $connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute(); + } } diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 72132c164df7..07630c590d5c 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -40,11 +40,15 @@ protected function causedByLostConnection(Throwable $e) 'Communication link failure', 'connection is no longer usable', 'Login timeout expired', - 'Connection refused', + 'SQLSTATE[HY000] [2002] Connection refused', 'running with the --read-only option so it cannot execute this statement', 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.', 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known', 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected', + 'SQLSTATE[HY000] [2002] Connection timed out', + 'SSL: Connection timed out', + 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', ]); } } diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 4765479c5e97..66cd2ded455c 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -7,6 +7,7 @@ use Exception; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Concerns\BuildsQueries; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Pagination\Paginator; @@ -193,6 +194,10 @@ public function whereKey($id) return $this; } + if ($id !== null && $this->model->getKeyType() === 'string') { + $id = (string) $id; + } + return $this->where($this->model->getQualifiedKeyName(), '=', $id); } @@ -210,13 +215,17 @@ public function whereKeyNot($id) return $this; } + if ($id !== null && $this->model->getKeyType() === 'string') { + $id = (string) $id; + } + return $this->where($this->model->getQualifiedKeyName(), '!=', $id); } /** * Add a basic where clause to the query. * - * @param \Closure|string|array $column + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean @@ -238,7 +247,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' /** * Add a basic where clause to the query, and return the first result. * - * @param \Closure|string|array $column + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean @@ -252,7 +261,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = /** * Add an "or where" clause to the query. * - * @param \Closure|array|string $column + * @param \Closure|array|string|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this @@ -269,7 +278,7 @@ public function orWhere($column, $operator = null, $value = null) /** * Add an "order by" clause for a timestamp to the query. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return $this */ public function latest($column = null) @@ -286,7 +295,7 @@ public function latest($column = null) /** * Add an "order by" clause for a timestamp to the query. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return $this */ public function oldest($column = null) @@ -497,13 +506,13 @@ public function firstOr($columns = ['*'], Closure $callback = null) /** * Get a single column's value from the first result of a query. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return mixed */ public function value($column) { if ($result = $this->first([$column])) { - return $result->{$column}; + return $result->{Str::afterLast($column, '.')}; } } @@ -680,7 +689,7 @@ protected function enforceOrderBy() /** * Get an array with the values of a given column. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @param string|null $key * @return \Illuminate\Support\Collection */ @@ -795,7 +804,7 @@ public function update(array $values) /** * Increment a column's value by a given amount. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @param float|int $amount * @param array $extra * @return int @@ -810,7 +819,7 @@ public function increment($column, $amount = 1, array $extra = []) /** * Decrement a column's value by a given amount. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @param float|int $amount * @param array $extra * @return int @@ -1159,7 +1168,15 @@ protected function parseWithRelations(array $relations) protected function createSelectWithConstraint($name) { return [explode(':', $name)[0], static function ($query) use ($name) { - $query->select(explode(',', explode(':', $name)[1])); + $query->select(array_map(static function ($column) use ($query) { + if (Str::contains($column, '.')) { + return $column; + } + + return $query instanceof BelongsToMany + ? $query->getRelated()->getTable().'.'.$column + : $column; + }, explode(',', explode(':', $name)[1]))); }]; } @@ -1297,7 +1314,7 @@ public function setModel(Model $model) /** * Qualify the given column name by the model's table. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function qualifyColumn($column) @@ -1388,11 +1405,13 @@ public function __call($method, $parameters) } if (static::hasGlobalMacro($method)) { - if (static::$macros[$method] instanceof Closure) { - return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters); + $callable = static::$macros[$method]; + + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this, static::class); } - return call_user_func_array(static::$macros[$method], $parameters); + return $callable(...$parameters); } if ($this->hasNamedScope($method)) { @@ -1433,11 +1452,13 @@ public static function __callStatic($method, $parameters) static::throwBadMethodCallException($method); } - if (static::$macros[$method] instanceof Closure) { - return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters); + $callable = static::$macros[$method]; + + if ($callable instanceof Closure) { + $callable = $callable->bindTo(null, static::class); } - return call_user_func_array(static::$macros[$method], $parameters); + return $callable(...$parameters); } /** diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index d88a958b6ebe..17199e8b3d7e 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -79,19 +79,18 @@ public function loadCount($relations) ->whereKey($this->modelKeys()) ->select($this->first()->getKeyName()) ->withCount(...func_get_args()) - ->get(); + ->get() + ->keyBy($this->first()->getKeyName()); $attributes = Arr::except( array_keys($models->first()->getAttributes()), $models->first()->getKeyName() ); - $models->each(function ($model) use ($attributes) { - $this->where($this->first()->getKeyName(), $model->getKey()) - ->each - ->forceFill(Arr::only($model->getAttributes(), $attributes)) - ->each - ->syncOriginalAttributes($attributes); + $this->each(function ($model) use ($models, $attributes) { + $extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes); + + $model->forceFill($extraAttributes)->syncOriginalAttributes($attributes); }); return $this; @@ -281,6 +280,23 @@ public function map(callable $callback) }) ? $result->toBase() : $result; } + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key / value pair. + * + * @param callable $callback + * @return \Illuminate\Support\Collection|static + */ + public function mapWithKeys(callable $callback) + { + $result = parent::mapWithKeys($callback); + + return $result->contains(function ($item) { + return ! $item instanceof Model; + }) ? $result->toBase() : $result; + } + /** * Reload a fresh model instance from the database for all the entities. * @@ -358,7 +374,7 @@ public function intersect($items) * * @param string|callable|null $key * @param bool $strict - * @return static|\Illuminate\Support\Collection + * @return static */ public function unique($key = null, $strict = false) { @@ -485,7 +501,7 @@ public function keys() */ public function zip($items) { - return call_user_func_array([$this->toBase(), 'zip'], func_get_args()); + return $this->toBase()->zip(...func_get_args()); } /** @@ -633,7 +649,7 @@ public function getQueueableConnection() /** * Get the Eloquent query builder from the collection. * - * @return Illuminate\Database\Eloquen\Builder + * @return \Illuminate\Database\Eloquent\Builder * * @throws \LogicException */ diff --git a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php index 5c44abb1fb8b..d7e4ac8a42cb 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -27,6 +27,13 @@ trait GuardsAttributes */ protected static $unguarded = false; + /** + * The actual columns that exist on the database and can be guarded. + * + * @var array + */ + protected static $guardableColumns = []; + /** * Get the fillable attributes for the model. * @@ -178,6 +185,7 @@ public function isFillable($key) } return empty($this->getFillable()) && + strpos($key, '.') === false && ! Str::startsWith($key, '_'); } @@ -189,7 +197,30 @@ public function isFillable($key) */ public function isGuarded($key) { - return in_array($key, $this->getGuarded()) || $this->getGuarded() == ['*']; + if (empty($this->getGuarded())) { + return false; + } + + return $this->getGuarded() == ['*'] || + ! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) || + ! $this->isGuardableColumn($key); + } + + /** + * Determine if the given column is a valid, guardable column. + * + * @param string $key + * @return bool + */ + protected function isGuardableColumn($key) + { + if (! isset(static::$guardableColumns[get_class($this)])) { + static::$guardableColumns[get_class($this)] = $this->getConnection() + ->getSchemaBuilder() + ->getColumnListing($this->getTable()); + } + + return in_array($key, static::$guardableColumns[get_class($this)]); } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 8322da150b4d..498e87195adc 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -972,14 +972,16 @@ protected function serializeDate(DateTimeInterface $date) */ public function getDates() { + if (! $this->usesTimestamps()) { + return $this->dates; + } + $defaults = [ $this->getCreatedAtColumn(), $this->getUpdatedAtColumn(), ]; - return $this->usesTimestamps() - ? array_unique(array_merge($this->dates, $defaults)) - : $this->dates; + return array_unique(array_merge($this->dates, $defaults)); } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index a914cf9c35e3..3406fe51e4f7 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -256,7 +256,7 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null // If the type value is null it is probably safe to assume we're eager loading // 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 empty($class = $this->{$type}) + return is_null($class = $this->{$type}) || $class === '' ? $this->morphEagerTo($name, $type, $id, $ownerKey) : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey); } @@ -682,7 +682,7 @@ public function joiningTableSegment() */ public function touches($relation) { - return in_array($relation, $this->touches); + return in_array($relation, $this->getTouchedRelations()); } /** @@ -692,7 +692,7 @@ public function touches($relation) */ public function touchOwners() { - foreach ($this->touches as $relation) { + foreach ($this->getTouchedRelations() as $relation) { $this->$relation()->touch(); if ($this->$relation instanceof self) { diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php b/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php index 8de099c998cd..b9c049b36482 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php @@ -110,7 +110,7 @@ public function usesTimestamps() /** * Get the name of the "created at" column. * - * @return string + * @return string|null */ public function getCreatedAtColumn() { @@ -120,7 +120,7 @@ public function getCreatedAtColumn() /** * Get the name of the "updated at" column. * - * @return string + * @return string|null */ public function getUpdatedAtColumn() { diff --git a/src/Illuminate/Database/Eloquent/Concerns/HidesAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HidesAttributes.php index 9ca6f88e3bc2..7f6ebfdbc55f 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HidesAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HidesAttributes.php @@ -117,7 +117,7 @@ public function makeHidden($attributes) /** * Make the given, typically visible, attributes hidden if the given truth test passes. * - * @param bool|Closure $truthTest + * @param bool|Closure $condition * @param array|string|null $attributes * @return $this */ diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 4a02fec5d2e8..c97c4033082e 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -205,10 +205,10 @@ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boole if ($types === ['*']) { $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all(); + } - foreach ($types as &$type) { - $type = Relation::getMorphedModel($type) ?? $type; - } + foreach ($types as &$type) { + $type = Relation::getMorphedModel($type) ?? $type; } return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) { diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 90999c106a0c..9b9d72642af0 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -146,14 +146,14 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab /** * The name of the "created at" column. * - * @var string + * @var string|null */ const CREATED_AT = 'created_at'; /** * The name of the "updated at" column. * - * @var string + * @var string|null */ const UPDATED_AT = 'updated_at'; @@ -396,10 +396,12 @@ public function qualifyColumn($column) * * @param string $key * @return string + * + * @deprecated This method is deprecated and will be removed in a future Laravel version. */ protected function removeTableFromKey($key) { - return Str::contains($key, '.') ? last(explode('.', $key)) : $key; + return $key; } /** @@ -1581,6 +1583,8 @@ public function resolveChildRouteBinding($childType, $value, $field) { $relationship = $this->{Str::plural(Str::camel($childType))}(); + $field = $field ?: $relationship->getRelated()->getRouteKeyName(); + if ($relationship instanceof HasManyThrough || $relationship instanceof BelongsToMany) { return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first(); diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index b4f703d6009f..45bfbc2de0f5 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -554,7 +554,6 @@ public function orWherePivotNull($column, $not = false) * Set a "or where not null" clause for a pivot table column. * * @param string $column - * @param bool $not * @return $this */ public function orWherePivotNotNull($column) diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php index a6fdd5af80b9..d7de34e2c348 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php @@ -286,6 +286,8 @@ public function newQueryForRestoration($ids) */ protected function newQueryForCollectionRestoration(array $ids) { + $ids = array_values($ids); + if (! Str::contains($ids[0], ':')) { return parent::newQueryForRestoration($ids); } diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php index 0d13eefc1469..df39c1169803 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -541,15 +541,15 @@ public function newPivotQuery() $query = $this->newPivotStatement(); foreach ($this->pivotWheres as $arguments) { - call_user_func_array([$query, 'where'], $arguments); + $query->where(...$arguments); } foreach ($this->pivotWhereIns as $arguments) { - call_user_func_array([$query, 'whereIn'], $arguments); + $query->whereIn(...$arguments); } foreach ($this->pivotWhereNulls as $arguments) { - call_user_func_array([$query, 'whereNull'], $arguments); + $query->whereNull(...$arguments); } return $query->where($this->foreignPivotKey, $this->parent->{$this->parentKey}); diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index 0db82ba101bc..68489265f838 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -139,6 +139,8 @@ public function newQueryForRestoration($ids) */ protected function newQueryForCollectionRestoration(array $ids) { + $ids = array_values($ids); + if (! Str::contains($ids[0], ':')) { return parent::newQueryForRestoration($ids); } diff --git a/src/Illuminate/Database/MigrationServiceProvider.php b/src/Illuminate/Database/MigrationServiceProvider.php index 3ccc4517eafd..9b1f355d1f68 100755 --- a/src/Illuminate/Database/MigrationServiceProvider.php +++ b/src/Illuminate/Database/MigrationServiceProvider.php @@ -102,7 +102,7 @@ protected function registerCreator() protected function registerCommands(array $commands) { foreach (array_keys($commands) as $command) { - call_user_func_array([$this, "register{$command}Command"], []); + $this->{"register{$command}Command"}(); } $this->commands(array_values($commands)); diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 763da6afc754..adb8a8d5bc1e 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -709,7 +709,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' ); if (! $value instanceof Expression) { - $this->addBinding($value, 'where'); + $this->addBinding($this->flattenValue($value), 'where'); } return $this; @@ -1078,7 +1078,25 @@ public function whereBetween($column, array $values, $boolean = 'and', $not = fa $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); - $this->addBinding($this->cleanBindings($values), 'where'); + $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'where'); + + return $this; + } + + /** + * Add a where between statement using columns to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereBetweenColumns($column, array $values, $boolean = 'and', $not = false) + { + $type = 'betweenColumns'; + + $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); return $this; } @@ -1095,6 +1113,18 @@ public function orWhereBetween($column, array $values) return $this->whereBetween($column, $values, 'or'); } + /** + * Add an or where between statement using columns to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereBetweenColumns($column, array $values) + { + return $this->whereBetweenColumns($column, $values, 'or'); + } + /** * Add a where not between statement to the query. * @@ -1108,6 +1138,19 @@ public function whereNotBetween($column, array $values, $boolean = 'and') return $this->whereBetween($column, $values, $boolean, true); } + /** + * Add a where not between statement using columns to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return $this + */ + public function whereNotBetweenColumns($column, array $values, $boolean = 'and') + { + return $this->whereBetweenColumns($column, $values, $boolean, true); + } + /** * Add an or where not between statement to the query. * @@ -1120,6 +1163,18 @@ public function orWhereNotBetween($column, array $values) return $this->whereNotBetween($column, $values, 'or'); } + /** + * Add an or where not between statement using columns to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereNotBetweenColumns($column, array $values) + { + return $this->whereNotBetweenColumns($column, $values, 'or'); + } + /** * Add an "or where not null" clause to the query. * @@ -1146,6 +1201,8 @@ public function whereDate($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + $value = $this->flattenValue($value); + if ($value instanceof DateTimeInterface) { $value = $value->format('Y-m-d'); } @@ -1185,6 +1242,8 @@ public function whereTime($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + $value = $this->flattenValue($value); + if ($value instanceof DateTimeInterface) { $value = $value->format('H:i:s'); } @@ -1224,6 +1283,8 @@ public function whereDay($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + $value = $this->flattenValue($value); + if ($value instanceof DateTimeInterface) { $value = $value->format('d'); } @@ -1267,6 +1328,8 @@ public function whereMonth($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + $value = $this->flattenValue($value); + if ($value instanceof DateTimeInterface) { $value = $value->format('m'); } @@ -1310,6 +1373,8 @@ public function whereYear($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + $value = $this->flattenValue($value); + if ($value instanceof DateTimeInterface) { $value = $value->format('Y'); } @@ -1382,7 +1447,7 @@ public function forNestedWhere() /** * Add another query builder as a nested where to the query builder. * - * @param $this $query + * @param \Illuminate\Database\Query\Builder $query * @param string $boolean * @return $this */ @@ -1527,7 +1592,7 @@ public function whereRowValues($columns, $operator, $values, $boolean = 'and') } /** - * Adds a or where condition using row values. + * Adds an or where condition using row values. * * @param array $columns * @param string $operator @@ -1562,7 +1627,7 @@ public function whereJsonContains($column, $value, $boolean = 'and', $not = fals } /** - * Add a "or where JSON contains" clause to the query. + * Add an "or where JSON contains" clause to the query. * * @param string $column * @param mixed $value @@ -1587,7 +1652,7 @@ public function whereJsonDoesntContain($column, $value, $boolean = 'and') } /** - * Add a "or where JSON not contains" clause to the query. + * Add an "or where JSON not contains" clause to the query. * * @param string $column * @param mixed $value @@ -1618,14 +1683,14 @@ public function whereJsonLength($column, $operator, $value = null, $boolean = 'a $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); if (! $value instanceof Expression) { - $this->addBinding($value); + $this->addBinding((int) $this->flattenValue($value)); } return $this; } /** - * Add a "or where JSON length" clause to the query. + * Add an "or where JSON length" clause to the query. * * @param string $column * @param mixed $operator @@ -1767,14 +1832,14 @@ public function having($column, $operator = null, $value = null, $boolean = 'and $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean'); if (! $value instanceof Expression) { - $this->addBinding($value, 'having'); + $this->addBinding($this->flattenValue($value), 'having'); } return $this; } /** - * Add a "or having" clause to the query. + * Add an "or having" clause to the query. * * @param string $column * @param string|null $operator @@ -1805,7 +1870,7 @@ public function havingBetween($column, array $values, $boolean = 'and', $not = f $this->havings[] = compact('type', 'column', 'values', 'boolean', 'not'); - $this->addBinding($this->cleanBindings($values), 'having'); + $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'having'); return $this; } @@ -2045,6 +2110,8 @@ public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id') /** * Remove all existing orders and optionally add a new order. * + * @param string|null $column + * @param string $direction * @return $this */ public function reorder($column = null, $direction = 'asc') @@ -3025,6 +3092,17 @@ protected function cleanBindings(array $bindings) })); } + /** + * Get a scalar type value from an unknown type of input. + * + * @param mixed $value + * @return mixed + */ + protected function flattenValue($value) + { + return is_array($value) ? head(Arr::flatten($value)) : $value; + } + /** * Get the default key name of the table. * diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index b0d1972fa32b..fa9e962d1e28 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -363,6 +363,24 @@ protected function whereBetween(Builder $query, $where) return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; } + /** + * Compile a "between" where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBetweenColumns(Builder $query, $where) + { + $between = $where['not'] ? 'not between' : 'between'; + + $min = $this->wrap(reset($where['values'])); + + $max = $this->wrap(end($where['values'])); + + return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; + } + /** * Compile a "where date" clause. * @@ -1199,7 +1217,7 @@ protected function wrapJsonFieldAndPath($column) */ protected function wrapJsonPath($value, $delimiter = '->') { - $value = preg_replace("/([\\\\]+)?\\'/", "\\'", $value); + $value = preg_replace("/([\\\\]+)?\\'/", "''", $value); return '\'$."'.str_replace($delimiter, '"."', $value).'"\''; } diff --git a/src/Illuminate/Database/Query/JoinClause.php b/src/Illuminate/Database/Query/JoinClause.php index 4d84e59de58f..800da42ef3fb 100755 --- a/src/Illuminate/Database/Query/JoinClause.php +++ b/src/Illuminate/Database/Query/JoinClause.php @@ -84,7 +84,7 @@ public function __construct(Builder $parentQuery, $type, $table) * * @param \Closure|string $first * @param string|null $operator - * @param string|null $second + * @param \Illuminate\Database\Query\Expression|string|null $second * @param string $boolean * @return $this * diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 7503dfe92811..c13e69ea9589 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -22,6 +22,7 @@ * @method $this primary() Add a primary index * @method $this spatialIndex() Add a spatial index * @method $this storedAs(string $expression) Create a stored generated column (MySQL) + * @method $this type(string $type) Specify a type for the column * @method $this unique(string $indexName = null) Add a unique index * @method $this unsigned() Set the INTEGER column as UNSIGNED (MySQL) * @method $this useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value diff --git a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php index 33aad5ba45bd..6fe970608fca 100644 --- a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php @@ -33,4 +33,14 @@ public function cascadeOnDelete() { return $this->onDelete('cascade'); } + + /** + * Indicate that deletes should set the foreign key value to null. + * + * @return $this + */ + public function nullOnDelete() + { + return $this->onDelete('set null'); + } } diff --git a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php index 777c9d897b66..0db0c507e404 100644 --- a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php +++ b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php @@ -63,9 +63,22 @@ protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) { $tableDiff->renamedColumns = [ - $command->from => new Column($command->to, $column->getType(), $column->toArray()), + $command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column)), ]; return $tableDiff; } + + /** + * Get the writable column options. + * + * @param \Doctrine\DBAL\Schema\Column $column + * @return array + */ + private static function getWritableColumnOptions(Column $column) + { + return array_filter($column->toArray(), function (string $name) use ($column) { + return method_exists($column, 'set'.$name); + }, ARRAY_FILTER_USE_KEY); + } } diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index f30be675939f..43d3b7d0568d 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -48,7 +48,7 @@ public function compileColumnListing($table) { return "select col.name from sys.columns as col join sys.objects as obj on col.object_id = obj.object_id - where obj.type = 'U' and obj.name = '$table'"; + where obj.type = 'U' and obj.object_id = object_id('$table')"; } /** diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index cad46df9d7a7..38d7d371adae 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/container": "^7.0", "illuminate/contracts": "^7.0", @@ -34,7 +34,7 @@ }, "suggest": { "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", - "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "illuminate/console": "Required to use the database commands (^7.0).", "illuminate/events": "Required to use the observers with Eloquent (^7.0).", "illuminate/filesystem": "Required to use the migrations (^7.0).", diff --git a/src/Illuminate/Encryption/composer.json b/src/Illuminate/Encryption/composer.json index 7a3598d820a9..3ea301e727a1 100644 --- a/src/Illuminate/Encryption/composer.json +++ b/src/Illuminate/Encryption/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", diff --git a/src/Illuminate/Events/CallQueuedListener.php b/src/Illuminate/Events/CallQueuedListener.php index b3904f86e233..b389b40ef39b 100644 --- a/src/Illuminate/Events/CallQueuedListener.php +++ b/src/Illuminate/Events/CallQueuedListener.php @@ -89,17 +89,15 @@ public function handle(Container $container) $this->job, $container->make($this->class) ); - call_user_func_array( - [$handler, $this->method], $this->data - ); + $handler->{$this->method}(...array_values($this->data)); } /** * Set the job instance of the given class if necessary. * * @param \Illuminate\Contracts\Queue\Job $job - * @param mixed $instance - * @return mixed + * @param object $instance + * @return object */ protected function setJobInstanceIfNecessary(Job $job, $instance) { @@ -124,10 +122,10 @@ public function failed($e) $handler = Container::getInstance()->make($this->class); - $parameters = array_merge($this->data, [$e]); + $parameters = array_merge(array_values($this->data), [$e]); if (method_exists($handler, 'failed')) { - call_user_func_array([$handler, 'failed'], $parameters); + $handler->failed(...$parameters); } } diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 10e3b5666004..b81275855d8c 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -396,9 +396,9 @@ public function createClassListener($listener, $wildcard = false) return call_user_func($this->createClassCallable($listener), $event, $payload); } - return call_user_func_array( - $this->createClassCallable($listener), $payload - ); + $callable = $this->createClassCallable($listener); + + return $callable(...array_values($payload)); }; } diff --git a/src/Illuminate/Events/composer.json b/src/Illuminate/Events/composer.json index 03225a9743b7..c93a0320ca59 100755 --- a/src/Illuminate/Events/composer.json +++ b/src/Illuminate/Events/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/container": "^7.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" diff --git a/src/Illuminate/Filesystem/FilesystemAdapter.php b/src/Illuminate/Filesystem/FilesystemAdapter.php index 31d6727d9888..fb3ffdc39255 100644 --- a/src/Illuminate/Filesystem/FilesystemAdapter.php +++ b/src/Illuminate/Filesystem/FilesystemAdapter.php @@ -116,7 +116,13 @@ public function missing($path) */ public function path($path) { - return $this->driver->getAdapter()->getPathPrefix().$path; + $adapter = $this->driver->getAdapter(); + + if ($adapter instanceof CachedAdapter) { + $adapter = $adapter->getAdapter(); + } + + return $adapter->getPathPrefix().$path; } /** @@ -132,7 +138,7 @@ public function get($path) try { return $this->driver->read($path); } catch (FileNotFoundException $e) { - throw new ContractFileNotFoundException($path, $e->getCode(), $e); + throw new ContractFileNotFoundException($e->getMessage(), $e->getCode(), $e); } } @@ -230,7 +236,7 @@ public function put($path, $contents, $options = []) * * @param string $path * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file - * @param array $options + * @param mixed $options * @return string|false */ public function putFile($path, $file, $options = []) @@ -246,7 +252,7 @@ public function putFile($path, $file, $options = []) * @param string $path * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file * @param string $name - * @param array $options + * @param mixed $options * @return string|false */ public function putFileAs($path, $file, $name, $options = []) @@ -739,6 +745,6 @@ protected function parseVisibility($visibility) */ public function __call($method, array $parameters) { - return call_user_func_array([$this->driver, $method], $parameters); + return $this->driver->{$method}(...array_values($parameters)); } } diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index 3dacc2b10aa4..6003ac6b9634 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -208,8 +208,10 @@ public function createS3Driver(array $config) $options = $config['options'] ?? []; + $streamReads = $config['stream_reads'] ?? false; + return $this->adapt($this->createFlysystem( - new S3Adapter(new S3Client($s3Config), $s3Config['bucket'], $root, $options), $config + new S3Adapter(new S3Client($s3Config), $s3Config['bucket'], $root, $options, $streamReads), $config )); } diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index fd661962fa09..f1a48239e0d9 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0", "symfony/finder": "^5.0" @@ -32,7 +32,7 @@ "suggest": { "ext-ftp": "Required to use the Flysystem FTP driver.", "illuminate/http": "Required for handling uploaded files (^7.0).", - "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0.34).", + "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.1).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index d5b999337de1..142bc4b2d4bd 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '7.20.0'; + const VERSION = '7.30.4'; /** * The base path for the Laravel installation. @@ -1177,6 +1177,16 @@ public function getLocale() return $this['config']->get('app.locale'); } + /** + * Get the current application fallback locale. + * + * @return string + */ + public function getFallbackLocale() + { + return $this['config']->get('app.fallback_locale'); + } + /** * Set the current application locale. * @@ -1192,6 +1202,19 @@ public function setLocale($locale) $this['events']->dispatch(new LocaleUpdated($locale)); } + /** + * Set the current application fallback locale. + * + * @param string $fallbackLocale + * @return void + */ + public function setFallbackLocale($fallbackLocale) + { + $this['config']->set('app.fallback_locale', $fallbackLocale); + + $this['translator']->setFallback($fallbackLocale); + } + /** * Determine if application locale is the given locale. * diff --git a/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php b/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php index 1bf3f8b8442e..60dd3707f761 100644 --- a/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php +++ b/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php @@ -105,6 +105,6 @@ protected function writeErrorAndDie(InvalidFileException $e) $output->writeln('The environment file is invalid!'); $output->writeln($e->getMessage()); - die(1); + exit(1); } } diff --git a/src/Illuminate/Foundation/Console/ComponentMakeCommand.php b/src/Illuminate/Foundation/Console/ComponentMakeCommand.php index dbe24d208e32..8ab3e8eeebea 100644 --- a/src/Illuminate/Foundation/Console/ComponentMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ComponentMakeCommand.php @@ -53,9 +53,9 @@ public function handle() */ protected function writeView() { - $view = $this->getView(); - - $path = resource_path('views').'/'.str_replace('.', '/', 'components.'.$view); + $path = $this->viewPath( + str_replace('.', '/', 'components.'.$this->getView()) + ); if (! $this->files->isDirectory(dirname($path))) { $this->files->makeDirectory(dirname($path), 0777, true, true); @@ -80,7 +80,7 @@ protected function buildClass($name) if ($this->option('inline')) { return str_replace( 'DummyView', - "<<<'blade'\n

\n ".Inspiring::quote()."\n
\nblade", + "<<<'blade'\n
\n \n
\nblade", parent::buildClass($name) ); } diff --git a/src/Illuminate/Foundation/Console/MailMakeCommand.php b/src/Illuminate/Foundation/Console/MailMakeCommand.php index d401a9ec45be..19bef8db3c33 100644 --- a/src/Illuminate/Foundation/Console/MailMakeCommand.php +++ b/src/Illuminate/Foundation/Console/MailMakeCommand.php @@ -51,7 +51,9 @@ public function handle() */ protected function writeMarkdownTemplate() { - $path = resource_path('views/'.str_replace('.', '/', $this->option('markdown'))).'.blade.php'; + $path = $this->viewPath( + str_replace('.', '/', $this->option('markdown')).'.blade.php' + ); if (! $this->files->isDirectory(dirname($path))) { $this->files->makeDirectory(dirname($path), 0755, true); diff --git a/src/Illuminate/Foundation/Console/NotificationMakeCommand.php b/src/Illuminate/Foundation/Console/NotificationMakeCommand.php index 40e9d849f3ad..6eb66e282e3c 100644 --- a/src/Illuminate/Foundation/Console/NotificationMakeCommand.php +++ b/src/Illuminate/Foundation/Console/NotificationMakeCommand.php @@ -51,7 +51,9 @@ public function handle() */ protected function writeMarkdownTemplate() { - $path = resource_path('views/'.str_replace('.', '/', $this->option('markdown'))).'.blade.php'; + $path = $this->viewPath( + str_replace('.', '/', $this->option('markdown')).'.blade.php' + ); if (! $this->files->isDirectory(dirname($path))) { $this->files->makeDirectory(dirname($path), 0755, true); diff --git a/src/Illuminate/Foundation/Console/QueuedCommand.php b/src/Illuminate/Foundation/Console/QueuedCommand.php index 67749ee938dd..fb3d027b4b0a 100644 --- a/src/Illuminate/Foundation/Console/QueuedCommand.php +++ b/src/Illuminate/Foundation/Console/QueuedCommand.php @@ -37,6 +37,6 @@ public function __construct($data) */ public function handle(KernelContract $kernel) { - call_user_func_array([$kernel, 'call'], $this->data); + $kernel->call(...array_values($this->data)); } } diff --git a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php index 1fd28d26e3a1..abaf6f04a35f 100644 --- a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php @@ -51,8 +51,8 @@ public function handle() protected function getStub() { return $this->collection() - ? __DIR__.'/stubs/resource-collection.stub' - : __DIR__.'/stubs/resource.stub'; + ? $this->resolveStubPath('/stubs/resource-collection.stub') + : $this->resolveStubPath('/stubs/resource.stub'); } /** @@ -66,6 +66,19 @@ protected function collection() Str::endsWith($this->argument('name'), 'Collection'); } + /** + * Resolve the fully-qualified path to the stub. + * + * @param string $stub + * @return string + */ + protected function resolveStubPath($stub) + { + return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) + ? $customPath + : __DIR__.$stub; + } + /** * Get the default namespace for the class. * diff --git a/src/Illuminate/Foundation/Console/StubPublishCommand.php b/src/Illuminate/Foundation/Console/StubPublishCommand.php index 8eab4b812d4f..55e86a958423 100644 --- a/src/Illuminate/Foundation/Console/StubPublishCommand.php +++ b/src/Illuminate/Foundation/Console/StubPublishCommand.php @@ -38,6 +38,8 @@ public function handle() __DIR__.'/stubs/model.pivot.stub' => $stubsPath.'/model.pivot.stub', __DIR__.'/stubs/model.stub' => $stubsPath.'/model.stub', __DIR__.'/stubs/request.stub' => $stubsPath.'/request.stub', + __DIR__.'/stubs/resource.stub' => $stubsPath.'/resource.stub', + __DIR__.'/stubs/resource-collection.stub' => $stubsPath.'/resource-collection.stub', __DIR__.'/stubs/test.stub' => $stubsPath.'/test.stub', __DIR__.'/stubs/test.unit.stub' => $stubsPath.'/test.unit.stub', realpath(__DIR__.'/../../Database/Console/Factories/stubs/factory.stub') => $stubsPath.'/factory.stub', diff --git a/src/Illuminate/Foundation/Console/stubs/job.stub b/src/Illuminate/Foundation/Console/stubs/job.stub index 2e0acd1d8e46..89dc9d727cee 100644 --- a/src/Illuminate/Foundation/Console/stubs/job.stub +++ b/src/Illuminate/Foundation/Console/stubs/job.stub @@ -2,12 +2,11 @@ namespace {{ namespace }}; -use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; class {{ class }} { - use Dispatchable, Queueable; + use Dispatchable; /** * Create a new job instance. diff --git a/src/Illuminate/Foundation/Console/stubs/resource-collection.stub b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub index 037432ca4916..05390d553144 100644 --- a/src/Illuminate/Foundation/Console/stubs/resource-collection.stub +++ b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub @@ -1,10 +1,10 @@ container->call($reportCallable); return; @@ -322,7 +323,7 @@ protected function prepareResponse($request, Throwable $e) */ protected function convertExceptionToResponse(Throwable $e) { - return SymfonyResponse::create( + return new SymfonyResponse( $this->renderExceptionContent($e), $this->isHttpException($e) ? $e->getStatusCode() : 500, $this->isHttpException($e) ? $e->getHeaders() : [] diff --git a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php index 0f24357e20fc..59483200e4d0 100644 --- a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php +++ b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -3,9 +3,11 @@ namespace Illuminate\Foundation\Http\Middleware; use Closure; +use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Support\Responsable; +use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Session\TokenMismatchException; use Illuminate\Support\InteractsWithTime; @@ -151,7 +153,11 @@ protected function getTokenFromRequest($request) $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN'); if (! $token && $header = $request->header('X-XSRF-TOKEN')) { - $token = $this->encrypter->decrypt($header, static::serialized()); + try { + $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized())); + } catch (DecryptException $e) { + $token = ''; + } } return $token; diff --git a/src/Illuminate/Foundation/Inspiring.php b/src/Illuminate/Foundation/Inspiring.php index f360ec90a944..6023f5635029 100644 --- a/src/Illuminate/Foundation/Inspiring.php +++ b/src/Illuminate/Foundation/Inspiring.php @@ -20,31 +20,31 @@ class Inspiring public static function quote() { return Collection::make([ - 'When there is no desire, all things are at peace. - Laozi', - 'Simplicity is the ultimate sophistication. - Leonardo da Vinci', - 'Simplicity is the essence of happiness. - Cedric Bledsoe', - 'Smile, breathe, and go slowly. - Thich Nhat Hanh', - 'Simplicity is an acquired taste. - Katharine Gerould', - 'Well begun is half done. - Aristotle', - 'He who is contented is rich. - Laozi', - 'Very little is needed to make a happy life. - Marcus Antoninus', - 'It is quality rather than quantity that matters. - Lucius Annaeus Seneca', 'Act only according to that maxim whereby you can, at the same time, will that it should become a universal law. - Immanuel Kant', - 'Knowing is not enough; we must apply. Being willing is not enough; we must do. - Leonardo da Vinci', 'An unexamined life is not worth living. - Socrates', + 'Be present above all else. - Naval Ravikant', 'Happiness is not something readymade. It comes from your own actions. - Dalai Lama', - 'The only way to do great work is to love what you do. - Steve Jobs', - 'The whole future lies in uncertainty: live immediately. - Seneca', - 'Waste no more time arguing what a good man should be, be one. - Marcus Aurelius', - 'It is not the man who has too little, but the man who craves more, that is poor. - Seneca', + 'He who is contented is rich. - Laozi', 'I begin to speak only when I am certain what I will say is not better left unsaid - Cato the Younger', - 'Order your soul. Reduce your wants. - Augustine', - 'Be present above all else. - Naval Ravikant', - 'Let all your things have their places; let each part of your business have its time. - Benjamin Franklin', 'If you do not have a consistent goal in life, you can not live it in a consistent way. - Marcus Aurelius', + 'It is not the man who has too little, but the man who craves more, that is poor. - Seneca', + 'It is quality rather than quantity that matters. - Lucius Annaeus Seneca', + 'Knowing is not enough; we must apply. Being willing is not enough; we must do. - Leonardo da Vinci', + 'Let all your things have their places; let each part of your business have its time. - Benjamin Franklin', 'No surplus words or unnecessary actions. - Marcus Aurelius', + 'Order your soul. Reduce your wants. - Augustine', 'People find pleasure in different ways. I find it in keeping my mind clear. - Marcus Aurelius', + 'Simplicity is an acquired taste. - Katharine Gerould', 'Simplicity is the consequence of refined emotions. - Jean D\'Alembert', + 'Simplicity is the essence of happiness. - Cedric Bledsoe', + 'Simplicity is the ultimate sophistication. - Leonardo da Vinci', + 'Smile, breathe, and go slowly. - Thich Nhat Hanh', + 'The only way to do great work is to love what you do. - Steve Jobs', + 'The whole future lies in uncertainty: live immediately. - Seneca', + 'Very little is needed to make a happy life. - Marcus Antoninus', + 'Waste no more time arguing what a good man should be, be one. - Marcus Aurelius', + 'Well begun is half done. - Aristotle', + 'When there is no desire, all things are at peace. - Laozi', ])->random(); } } diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index 1cdb3e69fa44..c762517390e7 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -172,7 +172,7 @@ public function register() protected function registerCommands(array $commands) { foreach (array_keys($commands) as $command) { - call_user_func_array([$this, "register{$command}Command"], []); + $this->{"register{$command}Command"}(); } $this->commands(array_values($commands)); diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php index c4ec0d5c3619..b34777910ea7 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php @@ -22,6 +22,13 @@ trait InteractsWithConsole */ public $expectedOutput = []; + /** + * All of the expected ouput tables. + * + * @var array + */ + public $expectedTables = []; + /** * All of the expected questions. * diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index fd3391b51311..10e55aab1358 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -3,6 +3,7 @@ namespace Illuminate\Foundation\Testing\Concerns; use Illuminate\Contracts\Http\Kernel as HttpKernel; +use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Http\Request; use Illuminate\Support\Str; use Illuminate\Testing\TestResponse; @@ -599,8 +600,8 @@ protected function prepareCookiesForRequest() return array_merge($this->defaultCookies, $this->unencryptedCookies); } - return collect($this->defaultCookies)->map(function ($value) { - return encrypt($value, false); + return collect($this->defaultCookies)->map(function ($value, $key) { + return encrypt(CookieValuePrefix::create($key, app('encrypter')->getKey()).$value, false); })->merge($this->unencryptedCookies)->all(); } diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index e968ad90c9ac..0cbeea12d071 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -37,11 +37,21 @@ protected function usingInMemoryDatabase() */ protected function refreshInMemoryDatabase() { - $this->artisan('migrate'); + $this->artisan('migrate', $this->migrateUsing()); $this->app[Kernel::class]->setArtisan(null); } + /** + * The parameters that should be used when running "migrate". + * + * @return array + */ + protected function migrateUsing() + { + return []; + } + /** * Refresh a conventional test database. * @@ -50,10 +60,7 @@ protected function refreshInMemoryDatabase() protected function refreshTestDatabase() { if (! RefreshDatabaseState::$migrated) { - $this->artisan('migrate:fresh', [ - '--drop-views' => $this->shouldDropViews(), - '--drop-types' => $this->shouldDropTypes(), - ]); + $this->artisan('migrate:fresh', $this->migrateFreshUsing()); $this->app[Kernel::class]->setArtisan(null); @@ -63,6 +70,19 @@ protected function refreshTestDatabase() $this->beginDatabaseTransaction(); } + /** + * The parameters that should be used when running "migrate:fresh". + * + * @return array + */ + protected function migrateFreshUsing() + { + return [ + '--drop-views' => $this->shouldDropViews(), + '--drop-types' => $this->shouldDropTypes(), + ]; + } + /** * Begin a database transaction on the testing database. * diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index 8905ec74e967..5fbc613acaa0 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -50,7 +50,7 @@ function abort($code, $message = '', array $headers = []) * Throw an HttpException with the given data if the given condition is true. * * @param bool $boolean - * @param int $code + * @param \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Support\Responsable|int $code * @param string $message * @param array $headers * @return void @@ -71,7 +71,7 @@ function abort_if($boolean, $code, $message = '', array $headers = []) * Throw an HttpException with the given data unless the given condition is true. * * @param bool $boolean - * @param int $code + * @param \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Support\Responsable|int $code * @param string $message * @param array $headers * @return void diff --git a/src/Illuminate/Hashing/ArgonHasher.php b/src/Illuminate/Hashing/ArgonHasher.php index 51e513a28c94..41109c9b0799 100644 --- a/src/Illuminate/Hashing/ArgonHasher.php +++ b/src/Illuminate/Hashing/ArgonHasher.php @@ -60,13 +60,13 @@ public function __construct(array $options = []) */ public function make($value, array $options = []) { - $hash = password_hash($value, $this->algorithm(), [ + $hash = @password_hash($value, $this->algorithm(), [ 'memory_cost' => $this->memory($options), 'time_cost' => $this->time($options), 'threads' => $this->threads($options), ]); - if ($hash === false) { + if (! is_string($hash)) { throw new RuntimeException('Argon2 hashing not supported.'); } diff --git a/src/Illuminate/Hashing/composer.json b/src/Illuminate/Hashing/composer.json index 4c5269341984..c264eb49370d 100755 --- a/src/Illuminate/Hashing/composer.json +++ b/src/Illuminate/Hashing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" }, diff --git a/src/Illuminate/Http/Client/ConnectionException.php b/src/Illuminate/Http/Client/ConnectionException.php index bd21353efed7..eac85dc71d27 100644 --- a/src/Illuminate/Http/Client/ConnectionException.php +++ b/src/Illuminate/Http/Client/ConnectionException.php @@ -2,9 +2,7 @@ namespace Illuminate\Http\Client; -use Exception; - -class ConnectionException extends Exception +class ConnectionException extends HttpClientException { // } diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 883858d4858f..1749de8a5be9 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -221,7 +221,7 @@ public function assertNothingSent() /** * Assert how many requests have been recorded. * - * @param $count + * @param int $count * @return void */ public function assertSentCount($count) diff --git a/src/Illuminate/Http/Client/HttpClientException.php b/src/Illuminate/Http/Client/HttpClientException.php new file mode 100644 index 000000000000..b15b8d30fe92 --- /dev/null +++ b/src/Illuminate/Http/Client/HttpClientException.php @@ -0,0 +1,10 @@ +options['sink'] = $to; + }); + } + /** * Specify the timeout (in seconds) for the request. * @@ -682,8 +695,14 @@ public function buildStubHandler() if (is_null($response)) { return $handler($request, $options); - } elseif (is_array($response)) { - return Factory::response($response); + } + + $response = is_array($response) ? Factory::response($response) : $response; + + $sink = $options['sink'] ?? null; + + if ($sink) { + $response->then($this->sinkStubHandler($sink)); } return $response; @@ -691,6 +710,28 @@ public function buildStubHandler() }; } + /** + * Get the sink stub handler callback. + * + * @param string $sink + * @return \Closure + */ + protected function sinkStubHandler($sink) + { + return function ($response) use ($sink) { + $body = $response->getBody()->getContents(); + + if (is_string($sink)) { + file_put_contents($sink, $body); + + return; + } + + fwrite($sink, $body); + rewind($sink); + }; + } + /** * Execute the "before sending" callbacks. * diff --git a/src/Illuminate/Http/Client/RequestException.php b/src/Illuminate/Http/Client/RequestException.php index 01714787cc54..09909db7eefc 100644 --- a/src/Illuminate/Http/Client/RequestException.php +++ b/src/Illuminate/Http/Client/RequestException.php @@ -2,9 +2,7 @@ namespace Illuminate\Http\Client; -use Exception; - -class RequestException extends Exception +class RequestException extends HttpClientException { /** * The response instance. diff --git a/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php b/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php index be760a2619d9..25d6ec1e9986 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php +++ b/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php @@ -31,7 +31,7 @@ public static function matchesType($actual, $type) */ public function isJson() { - return Str::contains($this->header('CONTENT_TYPE'), ['/json', '+json']); + return Str::contains($this->header('CONTENT_TYPE') ?? '', ['/json', '+json']); } /** diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index 12025eb0d08d..4550271b0f61 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -106,6 +106,22 @@ public function hasAny($keys) return Arr::hasAny($input, $keys); } + /** + * Apply the callback if the request contains the given input item key. + * + * @param string $key + * @param callable $callback + * @return $this|mixed + */ + public function whenHas($key, callable $callback) + { + if ($this->has($key)) { + return $callback(data_get($this->all(), $key)) ?: $this; + } + + return $this; + } + /** * Determine if the request contains a non-empty value for an input item. * @@ -125,6 +141,25 @@ public function filled($key) return true; } + /** + * Determine if the request contains an empty value for an input item. + * + * @param string|array $key + * @return bool + */ + public function isNotFilled($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! $this->isEmptyString($value)) { + return false; + } + } + + return true; + } + /** * Determine if the request contains a non-empty value for any of the given inputs. * @@ -144,6 +179,22 @@ public function anyFilled($keys) return false; } + /** + * Apply the callback if the request contains a non-empty value for the given input item key. + * + * @param string $key + * @param callable $callback + * @return $this|mixed + */ + public function whenFilled($key, callable $callback) + { + if ($this->filled($key)) { + return $callback(data_get($this->all(), $key)) ?: $this; + } + + return $this; + } + /** * Determine if the request is missing a given input item key. * diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index f71fd0b3fc02..2931fd6463c7 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -35,7 +35,7 @@ class ResourceCollection extends JsonResource implements Countable, IteratorAggr /** * The query parameters that should be added to the pagination links. * - * @var array + * @var array|null */ protected $queryParameters; diff --git a/src/Illuminate/Http/ResponseTrait.php b/src/Illuminate/Http/ResponseTrait.php index b52ddcdbde35..46936fb6cd0d 100644 --- a/src/Illuminate/Http/ResponseTrait.php +++ b/src/Illuminate/Http/ResponseTrait.php @@ -96,7 +96,7 @@ public function withHeaders($headers) */ public function cookie($cookie) { - return call_user_func_array([$this, 'withCookie'], func_get_args()); + return $this->withCookie(...func_get_args()); } /** @@ -108,7 +108,7 @@ public function cookie($cookie) public function withCookie($cookie) { if (is_string($cookie) && function_exists('cookie')) { - $cookie = call_user_func_array('cookie', func_get_args()); + $cookie = cookie(...func_get_args()); } $this->headers->setCookie($cookie); diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 6f6184bd0e4e..c85bcc734cb2 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/session": "^7.0", "illuminate/support": "^7.0", @@ -29,7 +29,7 @@ }, "suggest": { "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", - "guzzlehttp/guzzle": "Required to use the HTTP Client (^6.3.1|^7.0)." + "guzzlehttp/guzzle": "Required to use the HTTP Client (^6.3.1|^7.0.1)." }, "extra": { "branch-alias": { diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index b7d6cc8f8bde..ab9bf51a15a4 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -477,7 +477,7 @@ public function extend($driver, Closure $callback) /** * Unset the given channel instance. * - * @param string|null $name + * @param string|null $driver * @return $this */ public function forgetChannel($driver = null) diff --git a/src/Illuminate/Log/composer.json b/src/Illuminate/Log/composer.json index 6f4548c84120..53ebba890db5 100755 --- a/src/Illuminate/Log/composer.json +++ b/src/Illuminate/Log/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0", "monolog/monolog": "^2.0" diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index a8d1c36fad18..6876ba4878dd 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -641,6 +641,12 @@ protected function addressesToArray($address, $name) protected function normalizeRecipient($recipient) { if (is_array($recipient)) { + if (array_values($recipient) === $recipient) { + return (object) array_map(function ($email) { + return compact('email'); + }, $recipient); + } + return (object) $recipient; } elseif (is_string($recipient)) { return (object) ['email' => $recipient]; diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php index 1a96bbc54ddf..668d68baaf2a 100755 --- a/src/Illuminate/Mail/Mailer.php +++ b/src/Illuminate/Mail/Mailer.php @@ -352,7 +352,7 @@ protected function parseView($view) protected function addContent($message, $view, $plain, $raw, $data) { if (isset($view)) { - $message->setBody($this->renderView($view, $data), 'text/html'); + $message->setBody($this->renderView($view, $data) ?: ' ', 'text/html'); } if (isset($plain)) { @@ -398,7 +398,7 @@ protected function setGlobalToAndRemoveCcAndBcc($message) /** * Queue a new e-mail message for sending. * - * @param \Illuminate\Contracts\Mail\Mailable $view + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view * @param string|null $queue * @return mixed * diff --git a/src/Illuminate/Mail/PendingMail.php b/src/Illuminate/Mail/PendingMail.php index 42a9f009a166..d040f79068ba 100644 --- a/src/Illuminate/Mail/PendingMail.php +++ b/src/Illuminate/Mail/PendingMail.php @@ -114,7 +114,6 @@ public function bcc($users) * Send a new mailable message instance. * * @param \Illuminate\Contracts\Mail\Mailable $mailable - * * @return mixed */ public function send(MailableContract $mailable) diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index 706a8c039bcf..0eb541fac7e3 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/container": "^7.0", "illuminate/contracts": "^7.0", @@ -35,8 +35,8 @@ } }, "suggest": { - "aws/aws-sdk-php": "Required to use the SES mail driver (^3.0).", - "guzzlehttp/guzzle": "Required to use the Mailgun mail driver (^6.3.1|^7.0).", + "aws/aws-sdk-php": "Required to use the SES mail driver (^3.155).", + "guzzlehttp/guzzle": "Required to use the Mailgun mail driver (^6.3.1|^7.0.1).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "config": { diff --git a/src/Illuminate/Notifications/Channels/MailChannel.php b/src/Illuminate/Notifications/Channels/MailChannel.php index 157724acbf7e..2a30bcdacc84 100644 --- a/src/Illuminate/Notifications/Channels/MailChannel.php +++ b/src/Illuminate/Notifications/Channels/MailChannel.php @@ -115,7 +115,8 @@ protected function additionalMessageData($notification) '__laravel_notification_id' => $notification->id, '__laravel_notification' => get_class($notification), '__laravel_notification_queued' => in_array( - ShouldQueue::class, class_implements($notification) + ShouldQueue::class, + class_implements($notification) ), ]; } diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index 1d6c424b1512..15128a15ef1d 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -171,7 +171,7 @@ protected function shouldSendNotification($notifiable, $notification, $channel) * Queue the given notification instances. * * @param mixed $notifiables - * @param array[\Illuminate\Notifications\Channels\Notification] $notification + * @param \Illuminate\Notifications\Notification $notification * @return void */ protected function queueNotification($notifiables, $notification) diff --git a/src/Illuminate/Notifications/composer.json b/src/Illuminate/Notifications/composer.json index b99e6c816a3e..18306477017b 100644 --- a/src/Illuminate/Notifications/composer.json +++ b/src/Illuminate/Notifications/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/broadcasting": "^7.0", "illuminate/bus": "^7.0", "illuminate/container": "^7.0", diff --git a/src/Illuminate/Pagination/composer.json b/src/Illuminate/Pagination/composer.json index b780c348fb5f..36de9820a7aa 100755 --- a/src/Illuminate/Pagination/composer.json +++ b/src/Illuminate/Pagination/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index d2f323d931af..c1ee85894e7f 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" }, diff --git a/src/Illuminate/Queue/CallQueuedClosure.php b/src/Illuminate/Queue/CallQueuedClosure.php index 53359dbe1d10..e653b2555df2 100644 --- a/src/Illuminate/Queue/CallQueuedClosure.php +++ b/src/Illuminate/Queue/CallQueuedClosure.php @@ -41,7 +41,7 @@ public function __construct(SerializableClosure $closure) /** * Create a new job instance. * - * @param \Closure $closure + * @param \Closure $job * @return self */ public static function create(Closure $job) diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index c218ed48f98b..e9120a976962 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -12,7 +12,9 @@ class RetryCommand extends Command * * @var string */ - protected $signature = 'queue:retry {id* : The ID of the failed job or "all" to retry all jobs}'; + protected $signature = 'queue:retry + {id?* : The ID of the failed job or "all" to retry all jobs} + {--range=* : Range of job IDs (numeric) to be retried}'; /** * The console command description. @@ -53,7 +55,30 @@ protected function getJobIds() $ids = (array) $this->argument('id'); if (count($ids) === 1 && $ids[0] === 'all') { - $ids = Arr::pluck($this->laravel['queue.failer']->all(), 'id'); + return Arr::pluck($this->laravel['queue.failer']->all(), 'id'); + } + + if ($ranges = (array) $this->option('range')) { + $ids = array_merge($ids, $this->getJobIdsByRanges($ranges)); + } + + return array_values(array_filter(array_unique($ids))); + } + + /** + * Get the job IDs ranges, if applicable. + * + * @param array $ranges + * @return array + */ + protected function getJobIdsByRanges(array $ranges) + { + $ids = []; + + foreach ($ranges as $range) { + if (preg_match('/^[0-9]+\-[0-9]+$/', $range)) { + $ids = array_merge($ids, range(...explode('-', $range))); + } } return $ids; diff --git a/src/Illuminate/Queue/Listener.php b/src/Illuminate/Queue/Listener.php index 89fc74377f69..885d683bd2fe 100755 --- a/src/Illuminate/Queue/Listener.php +++ b/src/Illuminate/Queue/Listener.php @@ -214,7 +214,7 @@ public function memoryExceeded($memoryLimit) */ public function stop() { - die; + exit; } /** diff --git a/src/Illuminate/Queue/SerializesModels.php b/src/Illuminate/Queue/SerializesModels.php index e96111628ff7..52c0f405d831 100644 --- a/src/Illuminate/Queue/SerializesModels.php +++ b/src/Illuminate/Queue/SerializesModels.php @@ -65,6 +65,12 @@ public function __serialize() continue; } + $property->setAccessible(true); + + if (! $property->isInitialized($this)) { + continue; + } + $name = $property->getName(); if ($property->isPrivate()) { diff --git a/src/Illuminate/Queue/SyncQueue.php b/src/Illuminate/Queue/SyncQueue.php index 2871b2d14570..fceb5ddb5174 100755 --- a/src/Illuminate/Queue/SyncQueue.php +++ b/src/Illuminate/Queue/SyncQueue.php @@ -109,7 +109,7 @@ protected function raiseExceptionOccurredJobEvent(Job $job, Throwable $e) * @param \Throwable $e * @return void * - * @throws \Exception + * @throws \Throwable */ protected function handleException(Job $queueJob, Throwable $e) { diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index 5f8e35742a02..a9e16130b15b 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -465,7 +465,6 @@ protected function markJobAsFailedIfWillExceedMaxAttempts($connectionName, $job, * * @param string $connectionName * @param \Illuminate\Contracts\Queue\Job $job - * @param int $maxTries * @param \Throwable $e * @return void */ diff --git a/src/Illuminate/Queue/composer.json b/src/Illuminate/Queue/composer.json index b870f4f50d4a..1132d414583f 100644 --- a/src/Illuminate/Queue/composer.json +++ b/src/Illuminate/Queue/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/console": "^7.0", "illuminate/container": "^7.0", @@ -23,7 +23,7 @@ "illuminate/filesystem": "^7.0", "illuminate/pipeline": "^7.0", "illuminate/support": "^7.0", - "opis/closure": "^3.1", + "opis/closure": "^3.6", "ramsey/uuid": "^3.7|^4.0", "symfony/process": "^5.0" }, @@ -40,7 +40,7 @@ "suggest": { "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.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver and DynamoDb failed job storage (^3.155).", "illuminate/redis": "Required to use the Redis queue driver (^7.0).", "pda/pheanstalk": "Required to use the Beanstalk queue driver (^4.0)." }, diff --git a/src/Illuminate/Redis/Connections/PhpRedisConnection.php b/src/Illuminate/Redis/Connections/PhpRedisConnection.php index 5bb6c4b2710a..7eb11629a58b 100644 --- a/src/Illuminate/Redis/Connections/PhpRedisConnection.php +++ b/src/Illuminate/Redis/Connections/PhpRedisConnection.php @@ -318,7 +318,11 @@ public function scan($cursor, $options = []) $options['count'] ?? 10 ); - return empty($result) ? $result : [$cursor, $result]; + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; } /** @@ -336,7 +340,11 @@ public function zscan($key, $cursor, $options = []) $options['count'] ?? 10 ); - return $result === false ? [0, []] : [$cursor, $result]; + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; } /** @@ -354,7 +362,11 @@ public function hscan($key, $cursor, $options = []) $options['count'] ?? 10 ); - return $result === false ? [0, []] : [$cursor, $result]; + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; } /** @@ -372,7 +384,11 @@ public function sscan($key, $cursor, $options = []) $options['count'] ?? 10 ); - return $result === false ? [0, []] : [$cursor, $result]; + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; } /** diff --git a/src/Illuminate/Redis/Connections/PredisClusterConnection.php b/src/Illuminate/Redis/Connections/PredisClusterConnection.php index 9097aa3a759d..399be1ea73aa 100644 --- a/src/Illuminate/Redis/Connections/PredisClusterConnection.php +++ b/src/Illuminate/Redis/Connections/PredisClusterConnection.php @@ -2,9 +2,6 @@ namespace Illuminate\Redis\Connections; -/** - * @deprecated Predis is no longer maintained by its original author - */ class PredisClusterConnection extends PredisConnection { // diff --git a/src/Illuminate/Redis/Connections/PredisConnection.php b/src/Illuminate/Redis/Connections/PredisConnection.php index 67a9cc1163a3..932982562ba5 100644 --- a/src/Illuminate/Redis/Connections/PredisConnection.php +++ b/src/Illuminate/Redis/Connections/PredisConnection.php @@ -9,7 +9,6 @@ /** * @mixin \Predis\Client - * @deprecated Predis is no longer maintained by its original author */ class PredisConnection extends Connection implements ConnectionContract { @@ -43,7 +42,7 @@ public function createSubscription($channels, Closure $callback, $method = 'subs { $loop = $this->pubSubLoop(); - call_user_func_array([$loop, $method], (array) $channels); + $loop->{$method}(...array_values((array) $channels)); foreach ($loop as $message) { if ($message->kind === 'message' || $message->kind === 'pmessage') { diff --git a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php index ebd6ee11d4ef..ba14ad87a928 100644 --- a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php +++ b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php @@ -7,6 +7,7 @@ use Illuminate\Redis\Connections\PhpRedisConnection; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Redis as RedisFacade; +use Illuminate\Support\Str; use LogicException; use Redis; use RedisCluster; @@ -56,7 +57,7 @@ public function connectToCluster(array $config, array $clusterOptions, array $op */ protected function buildClusterConnectionString(array $server) { - return $server['host'].':'.$server['port'].'?'.Arr::query(Arr::only($server, [ + return $this->formatHost($server).':'.$server['port'].'?'.Arr::query(Arr::only($server, [ 'database', 'password', 'prefix', 'read_timeout', ])); } @@ -74,9 +75,9 @@ protected function createClient(array $config) return tap(new Redis, function ($client) use ($config) { if ($client instanceof RedisFacade) { throw new LogicException( - extension_loaded('redis') - ? 'Please remove or rename the Redis facade alias in your "app" configuration file in order to avoid collision with the PHP Redis extension.' - : 'Please make sure the PHP Redis extension is installed and enabled.' + extension_loaded('redis') + ? 'Please remove or rename the Redis facade alias in your "app" configuration file in order to avoid collision with the PHP Redis extension.' + : 'Please make sure the PHP Redis extension is installed and enabled.' ); } @@ -116,7 +117,7 @@ protected function establishConnection($client, array $config) $persistent = $config['persistent'] ?? false; $parameters = [ - $config['host'], + $this->formatHost($config), $config['port'], Arr::get($config, 'timeout', 0.0), $persistent ? Arr::get($config, 'persistent_id', null) : null, @@ -127,6 +128,12 @@ protected function establishConnection($client, array $config) $parameters[] = Arr::get($config, 'read_timeout', 0.0); } + if (version_compare(phpversion('redis'), '5.3.0', '>=')) { + if (! is_null($context = Arr::get($config, 'context'))) { + $parameters[] = $context; + } + } + $client->{($persistent ? 'pconnect' : 'connect')}(...$parameters); } @@ -151,6 +158,12 @@ protected function createRedisClusterInstance(array $servers, array $options) $parameters[] = $options['password'] ?? null; } + if (version_compare(phpversion('redis'), '5.3.2', '>=')) { + if (! is_null($context = Arr::get($options, 'context'))) { + $parameters[] = $context; + } + } + return tap(new RedisCluster(...$parameters), function ($client) use ($options) { if (! empty($options['prefix'])) { $client->setOption(RedisCluster::OPT_PREFIX, $options['prefix']); @@ -165,4 +178,19 @@ protected function createRedisClusterInstance(array $servers, array $options) } }); } + + /** + * Format the host using the scheme if available. + * + * @param array $options + * @return string + */ + protected function formatHost(array $options) + { + if (isset($options['scheme'])) { + return Str::start($options['host'], "{$options['scheme']}://"); + } + + return $options['host']; + } } diff --git a/src/Illuminate/Redis/Connectors/PredisConnector.php b/src/Illuminate/Redis/Connectors/PredisConnector.php index 91e6f9ac69b8..e91e8956a398 100644 --- a/src/Illuminate/Redis/Connectors/PredisConnector.php +++ b/src/Illuminate/Redis/Connectors/PredisConnector.php @@ -8,9 +8,6 @@ use Illuminate\Support\Arr; use Predis\Client; -/** - * @deprecated Predis is no longer maintained by its original author - */ class PredisConnector implements Connector { /** diff --git a/src/Illuminate/Redis/RedisManager.php b/src/Illuminate/Redis/RedisManager.php index 144bb413f8cb..b5d98203c180 100644 --- a/src/Illuminate/Redis/RedisManager.php +++ b/src/Illuminate/Redis/RedisManager.php @@ -185,6 +185,12 @@ protected function parseConnectionConfiguration($config) { $parsed = (new ConfigurationUrlParser)->parseConfiguration($config); + $driver = strtolower($parsed['driver'] ?? ''); + + if (in_array($driver, ['tcp', 'tls'])) { + $parsed['scheme'] = $driver; + } + return array_filter($parsed, function ($key) { return ! in_array($key, ['driver', 'username'], true); }, ARRAY_FILTER_USE_KEY); diff --git a/src/Illuminate/Redis/composer.json b/src/Illuminate/Redis/composer.json index 100cc4a65d9f..3502c9704ff1 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0" }, @@ -25,7 +25,7 @@ }, "suggest": { "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", - "predis/predis": "Required to use the predis connector (^1.0)." + "predis/predis": "Required to use the predis connector (^1.1.2)." }, "extra": { "branch-alias": { diff --git a/src/Illuminate/Routing/Controller.php b/src/Illuminate/Routing/Controller.php index 50de9712294f..a26a5ee7effa 100644 --- a/src/Illuminate/Routing/Controller.php +++ b/src/Illuminate/Routing/Controller.php @@ -51,7 +51,7 @@ public function getMiddleware() */ public function callAction($method, $parameters) { - return call_user_func_array([$this, $method], $parameters); + return $this->{$method}(...array_values($parameters)); } /** diff --git a/src/Illuminate/Routing/ImplicitRouteBinding.php b/src/Illuminate/Routing/ImplicitRouteBinding.php index 2aa036ce0707..c6d1953462a6 100644 --- a/src/Illuminate/Routing/ImplicitRouteBinding.php +++ b/src/Illuminate/Routing/ImplicitRouteBinding.php @@ -37,7 +37,7 @@ public static function resolveForRoute($container, $route) $parent = $route->parentOfParameter($parameterName); - if ($parent instanceof UrlRoutable && $route->bindingFieldFor($parameterName)) { + if ($parent instanceof UrlRoutable && in_array($parameterName, array_keys($route->bindingFields()))) { if (! $model = $parent->resolveChildRouteBinding( $parameterName, $parameterValue, $route->bindingFieldFor($parameterName) )) { diff --git a/src/Illuminate/Routing/PendingResourceRegistration.php b/src/Illuminate/Routing/PendingResourceRegistration.php index b61c7831dad6..5ab78b3f08da 100644 --- a/src/Illuminate/Routing/PendingResourceRegistration.php +++ b/src/Illuminate/Routing/PendingResourceRegistration.php @@ -169,6 +169,19 @@ public function withoutMiddleware($middleware) return $this; } + /** + * Add "where" constraints to the resource routes. + * + * @param mixed $wheres + * @return \Illuminate\Routing\PendingResourceRegistration + */ + public function where($wheres) + { + $this->options['wheres'] = $wheres; + + return $this; + } + /** * Indicate that the resource routes should have "shallow" nesting. * @@ -182,6 +195,19 @@ public function shallow($shallow = true) return $this; } + /** + * Indicate that the resource routes should be scoped using the given binding fields. + * + * @param array $fields + * @return \Illuminate\Routing\PendingResourceRegistration + */ + public function scoped(array $fields = []) + { + $this->options['bindingFields'] = $fields; + + return $this; + } + /** * Register the resource route. * diff --git a/src/Illuminate/Routing/Pipeline.php b/src/Illuminate/Routing/Pipeline.php index 257c03148fbd..e43d59199026 100644 --- a/src/Illuminate/Routing/Pipeline.php +++ b/src/Illuminate/Routing/Pipeline.php @@ -50,7 +50,7 @@ protected function handleException($passable, Throwable $e) $response = $handler->render($passable, $e); - if (method_exists($response, 'withException')) { + if (is_object($response) && method_exists($response, 'withException')) { $response->withException($e); } diff --git a/src/Illuminate/Routing/ResourceRegistrar.php b/src/Illuminate/Routing/ResourceRegistrar.php index c4230ba1a8b5..e606a1da19b5 100644 --- a/src/Illuminate/Routing/ResourceRegistrar.php +++ b/src/Illuminate/Routing/ResourceRegistrar.php @@ -95,9 +95,15 @@ public function register($name, $controller, array $options = []) $collection = new RouteCollection; foreach ($this->getResourceMethods($defaults, $options) as $m) { - $collection->add($this->{'addResource'.ucfirst($m)}( + $route = $this->{'addResource'.ucfirst($m)}( $name, $base, $controller, $options - )); + ); + + if (isset($options['bindingFields'])) { + $this->setResourceBindingFields($route, $options['bindingFields']); + } + + $collection->add($route); } return $collection; @@ -313,6 +319,24 @@ protected function getShallowName($name, $options) : $name; } + /** + * Set the route's binding fields if the resource is scoped. + * + * @param \Illuminate\Routing\Route $route + * @param array $bindingFields + * @return void + */ + protected function setResourceBindingFields($route, $bindingFields) + { + preg_match_all('/(?<={).*?(?=})/', $route->uri, $matches); + + $fields = array_fill_keys($matches[0], null); + + $route->setBindingFields(array_replace( + $fields, array_intersect_key($bindingFields, $fields) + )); + } + /** * Get the base resource URI for a given resource. * @@ -393,6 +417,10 @@ protected function getResourceAction($resource, $controller, $method, $options) $action['excluded_middleware'] = $options['excluded_middleware']; } + if (isset($options['wheres'])) { + $action['where'] = $options['wheres']; + } + return $action; } diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 944dc3470ed5..3c5da53aeb9f 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -927,9 +927,9 @@ public function gatherMiddleware() $this->computedMiddleware = []; - return $this->computedMiddleware = array_unique(array_merge( + return $this->computedMiddleware = Router::uniqueMiddleware(array_merge( $this->middleware(), $this->controllerMiddleware() - ), SORT_REGULAR); + )); } /** diff --git a/src/Illuminate/Routing/RouteAction.php b/src/Illuminate/Routing/RouteAction.php index a1839758cf9e..9d7eb76a85d8 100644 --- a/src/Illuminate/Routing/RouteAction.php +++ b/src/Illuminate/Routing/RouteAction.php @@ -3,6 +3,7 @@ namespace Illuminate\Routing; use Illuminate\Support\Arr; +use Illuminate\Support\Reflector; use Illuminate\Support\Str; use LogicException; use UnexpectedValueException; @@ -28,7 +29,7 @@ public static function parse($uri, $action) // If the action is already a Closure instance, we will just set that instance // as the "uses" property, because there is nothing else we need to do when // it is available. Otherwise we will need to find it in the action list. - if (is_callable($action, true)) { + if (Reflector::isCallable($action, true)) { return ! is_array($action) ? ['uses' => $action] : [ 'uses' => $action[0].'@'.$action[1], 'controller' => $action[0].'@'.$action[1], @@ -73,7 +74,7 @@ protected static function missingAction($uri) protected static function findCallable(array $action) { return Arr::first($action, function ($value, $key) { - return is_callable($value) && is_numeric($key); + return Reflector::isCallable($value) && is_numeric($key); }); } diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 8da02e65f2fc..d533d8d45dc2 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -5,6 +5,7 @@ use BadMethodCallException; use Closure; use Illuminate\Support\Arr; +use Illuminate\Support\Reflector; use InvalidArgumentException; /** @@ -180,6 +181,15 @@ protected function compileAction($action) $action = ['uses' => $action]; } + if (is_array($action) && + ! Arr::isAssoc($action) && + Reflector::isCallable($action)) { + $action = [ + 'uses' => $action[0].'@'.$action[1], + 'controller' => $action[0].'@'.$action[1], + ]; + } + return array_merge($this->attributes, $action); } diff --git a/src/Illuminate/Routing/RouteSignatureParameters.php b/src/Illuminate/Routing/RouteSignatureParameters.php index 535d5edcbf32..bd7e932fb316 100644 --- a/src/Illuminate/Routing/RouteSignatureParameters.php +++ b/src/Illuminate/Routing/RouteSignatureParameters.php @@ -37,7 +37,7 @@ protected static function fromClassMethodString($uses) { [$class, $method] = Str::parseCallback($uses); - if (! method_exists($class, $method) && is_callable($class, $method)) { + if (! method_exists($class, $method) && Reflector::isCallable($class, $method)) { return []; } diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index f8ee8a16f0fc..1e11f19bbc82 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -701,7 +701,7 @@ public function gatherRouteMiddleware(Route $route) $middleware = collect($route->gatherMiddleware())->map(function ($name) { return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups); - })->flatten()->reject(function ($name) use ($route, $excluded) { + })->flatten()->reject(function ($name) use ($excluded) { return in_array($name, $excluded, true); })->values(); @@ -1226,6 +1226,29 @@ public function setCompiledRoutes(array $routes) $this->container->instance('routes', $this->routes); } + /** + * Remove any duplicate middleware from the given array. + * + * @param array $middleware + * @return array + */ + public static function uniqueMiddleware(array $middleware) + { + $seen = []; + $result = []; + + foreach ($middleware as $value) { + $key = \is_object($value) ? \spl_object_id($value) : $value; + + if (! isset($seen[$key])) { + $seen[$key] = true; + $result[] = $value; + } + } + + return $result; + } + /** * Dynamically handle calls into the router instance. * diff --git a/src/Illuminate/Routing/SortedMiddleware.php b/src/Illuminate/Routing/SortedMiddleware.php index e4381635c60f..853378cf7e92 100644 --- a/src/Illuminate/Routing/SortedMiddleware.php +++ b/src/Illuminate/Routing/SortedMiddleware.php @@ -61,7 +61,7 @@ protected function sortMiddleware($priorityMap, $middlewares) } } - return array_values(array_unique($middlewares, SORT_REGULAR)); + return Router::uniqueMiddleware($middlewares); } /** diff --git a/src/Illuminate/Routing/UrlGenerator.php b/src/Illuminate/Routing/UrlGenerator.php index 1880be1bf957..971ef62ff1dd 100755 --- a/src/Illuminate/Routing/UrlGenerator.php +++ b/src/Illuminate/Routing/UrlGenerator.php @@ -311,7 +311,7 @@ public function formatScheme($secure = null) * Create a signed route URL for a named route. * * @param string $name - * @param array $parameters + * @param mixed $parameters * @param \DateTimeInterface|\DateInterval|int|null $expiration * @param bool $absolute * @return string @@ -616,25 +616,25 @@ public function getDefaultParameters() /** * Force the scheme for URLs. * - * @param string $scheme + * @param string|null $scheme * @return void */ public function forceScheme($scheme) { $this->cachedScheme = null; - $this->forceScheme = $scheme.'://'; + $this->forceScheme = $scheme ? $scheme.'://' : null; } /** * Set the forced root URL. * - * @param string $root + * @param string|null $root * @return void */ public function forceRootUrl($root) { - $this->forcedRoot = rtrim($root, '/'); + $this->forcedRoot = $root ? rtrim($root, '/') : null; $this->cachedRoot = null; } diff --git a/src/Illuminate/Routing/composer.json b/src/Illuminate/Routing/composer.json index 22ba74235148..b25943fc2d29 100644 --- a/src/Illuminate/Routing/composer.json +++ b/src/Illuminate/Routing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/container": "^7.0", "illuminate/contracts": "^7.0", diff --git a/src/Illuminate/Session/Middleware/AuthenticateSession.php b/src/Illuminate/Session/Middleware/AuthenticateSession.php index 85a9b39d84ad..5da389ae3d39 100644 --- a/src/Illuminate/Session/Middleware/AuthenticateSession.php +++ b/src/Illuminate/Session/Middleware/AuthenticateSession.php @@ -40,9 +40,9 @@ public function handle($request, Closure $next) } if ($this->auth->viaRemember()) { - $passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2]; + $passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2] ?? null; - if ($passwordHash != $request->user()->getAuthPassword()) { + if (! $passwordHash || $passwordHash != $request->user()->getAuthPassword()) { $this->logout($request); } } diff --git a/src/Illuminate/Session/Middleware/StartSession.php b/src/Illuminate/Session/Middleware/StartSession.php index 2e73bb8a0753..955486f2f6c3 100644 --- a/src/Illuminate/Session/Middleware/StartSession.php +++ b/src/Illuminate/Session/Middleware/StartSession.php @@ -281,7 +281,7 @@ protected function sessionIsPersistent(array $config = null) /** * Resolve the given cache driver. * - * @param string $cache + * @param string $driver * @return \Illuminate\Cache\Store */ protected function cache($driver) diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index 7251d259d5cd..6eb43ba9e17e 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -222,7 +222,7 @@ public function get($key, $default = null) * Get the value of a given key and then forget it. * * @param string $key - * @param string|null $default + * @param mixed $default * @return mixed */ public function pull($key, $default = null) diff --git a/src/Illuminate/Session/composer.json b/src/Illuminate/Session/composer.json index 56597c288c5c..1e7447adad13 100755 --- a/src/Illuminate/Session/composer.json +++ b/src/Illuminate/Session/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/contracts": "^7.0", "illuminate/filesystem": "^7.0", diff --git a/src/Illuminate/Support/Arr.php b/src/Illuminate/Support/Arr.php index e14ae83acb5c..8681e98a2cef 100755 --- a/src/Illuminate/Support/Arr.php +++ b/src/Illuminate/Support/Arr.php @@ -632,7 +632,7 @@ public static function sortRecursive($array) */ public static function query($array) { - return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + return http_build_query($array, '', '&', PHP_QUERY_RFC3986); } /** diff --git a/src/Illuminate/Support/Collection.php b/src/Illuminate/Support/Collection.php index 87337b53801e..4fe58afe2550 100644 --- a/src/Illuminate/Support/Collection.php +++ b/src/Illuminate/Support/Collection.php @@ -1210,7 +1210,7 @@ public function take($limit) /** * Take items in the collection until the given condition is met. * - * @param mixed $key + * @param mixed $value * @return static */ public function takeUntil($value) @@ -1221,7 +1221,7 @@ public function takeUntil($value) /** * Take items in the collection while the given condition is met. * - * @param mixed $key + * @param mixed $value * @return static */ public function takeWhile($value) @@ -1271,7 +1271,7 @@ public function zip($items) return new static(func_get_args()); }, $this->items], $arrayableItems); - return new static(call_user_func_array('array_map', $params)); + return new static(array_map(...$params)); } /** @@ -1306,6 +1306,17 @@ public function count() return count($this->items); } + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + return new static($this->lazy()->countBy($countBy)->all()); + } + /** * Add an item to the collection. * diff --git a/src/Illuminate/Support/ConfigurationUrlParser.php b/src/Illuminate/Support/ConfigurationUrlParser.php index 17fc5c423750..946252fb6d15 100644 --- a/src/Illuminate/Support/ConfigurationUrlParser.php +++ b/src/Illuminate/Support/ConfigurationUrlParser.php @@ -17,6 +17,8 @@ class ConfigurationUrlParser 'postgres' => 'pgsql', 'postgresql' => 'pgsql', 'sqlite3' => 'sqlite', + 'redis' => 'tcp', + 'rediss' => 'tls', ]; /** diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index cc7accaaf613..f53ec1c2ce53 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -21,6 +21,7 @@ * @method static \Illuminate\Http\Client\PendingRequest stub(callable $callback) * @method static \Illuminate\Http\Client\PendingRequest timeout(int $seconds) * @method static \Illuminate\Http\Client\PendingRequest withBasicAuth(string $username, string $password) + * @method static \Illuminate\Http\Client\PendingRequest withBody(resource|string $content, string $contentType) * @method static \Illuminate\Http\Client\PendingRequest withCookies(array $cookies, string $domain) * @method static \Illuminate\Http\Client\PendingRequest withDigestAuth(string $username, string $password) * @method static \Illuminate\Http\Client\PendingRequest withHeaders(array $headers) diff --git a/src/Illuminate/Support/Facades/Password.php b/src/Illuminate/Support/Facades/Password.php index 5fbd61676979..b62f1908a35b 100755 --- a/src/Illuminate/Support/Facades/Password.php +++ b/src/Illuminate/Support/Facades/Password.php @@ -7,6 +7,11 @@ /** * @method static mixed reset(array $credentials, \Closure $callback) * @method static string sendResetLink(array $credentials) + * @method static \Illuminate\Contracts\Auth\CanResetPassword getUser(array $credentials) + * @method static string createToken(\Illuminate\Contracts\Auth\CanResetPassword $user) + * @method static void deleteToken(\Illuminate\Contracts\Auth\CanResetPassword $user) + * @method static bool tokenExists(\Illuminate\Contracts\Auth\CanResetPassword $user, string $token) + * @method static \Illuminate\Auth\Passwords\TokenRepositoryInterface getRepository() * * @see \Illuminate\Auth\Passwords\PasswordBroker */ @@ -40,6 +45,13 @@ class Password extends Facade */ const INVALID_TOKEN = PasswordBroker::INVALID_TOKEN; + /** + * Constant representing a throttled reset attempt. + * + * @var string + */ + const RESET_THROTTLED = PasswordBroker::RESET_THROTTLED; + /** * Get the registered name of the component. * diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index ca3fcaa6f61e..05496d9ccd5d 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -28,6 +28,8 @@ * @method static array only(array|mixed $keys) * @method static array segments() * @method static array toArray() + * @method static array validate(array $rules, ...$params) + * @method static array validateWithBag(string $errorBag, array $rules, ...$params) * @method static bool accepts(string|array $contentTypes) * @method static bool acceptsAnyContentType() * @method static bool acceptsHtml() @@ -43,6 +45,7 @@ * @method static bool hasCookie(string $key) * @method static bool hasFile(string $key) * @method static bool hasHeader(string $key) + * @method static bool hasValidSignature(bool $absolute = true) * @method static bool is(mixed ...$patterns) * @method static bool isJson() * @method static bool matchesType(string $actual, string $type) diff --git a/src/Illuminate/Support/Facades/Storage.php b/src/Illuminate/Support/Facades/Storage.php index e1404f9622d1..33eb18bd9f94 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -23,6 +23,7 @@ * @method static bool prepend(string $path, string $data) * @method static bool put(string $path, string|resource $contents, mixed $options = []) * @method static string|false putFile(string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file, mixed $options = []) + * @method static string|false putFileAs(string $path, \Illuminate\Http\File|\Illuminate\Http\UploadedFile|string $file, string $name, mixed $options = []) * @method static bool setVisibility(string $path, string $visibility) * @method static bool writeStream(string $path, resource $resource, array $options = []) * @method static int lastModified(string $path) diff --git a/src/Illuminate/Support/LazyCollection.php b/src/Illuminate/Support/LazyCollection.php index 3d166ebe5191..8ddd5a8cabea 100644 --- a/src/Illuminate/Support/LazyCollection.php +++ b/src/Illuminate/Support/LazyCollection.php @@ -240,6 +240,35 @@ public function crossJoin(...$arrays) return $this->passthru('crossJoin', func_get_args()); } + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + $countBy = is_null($countBy) + ? $this->identity() + : $this->valueRetriever($countBy); + + return new static(function () use ($countBy) { + $counts = []; + + foreach ($this as $key => $value) { + $group = $countBy($value, $key); + + if (empty($counts[$group])) { + $counts[$group] = 0; + } + + $counts[$group]++; + } + + yield from $counts; + }); + } + /** * Get the items that are not present in the given items. * @@ -1155,7 +1184,7 @@ public function take($limit) /** * Take items in the collection until the given condition is met. * - * @param mixed $key + * @param mixed $value * @return static */ public function takeUntil($value) @@ -1176,7 +1205,7 @@ public function takeUntil($value) /** * Take items in the collection while the given condition is met. * - * @param mixed $key + * @param mixed $value * @return static */ public function takeWhile($value) diff --git a/src/Illuminate/Support/Reflector.php b/src/Illuminate/Support/Reflector.php index fb597f5141f1..66392ca2f39a 100644 --- a/src/Illuminate/Support/Reflector.php +++ b/src/Illuminate/Support/Reflector.php @@ -3,10 +3,58 @@ namespace Illuminate\Support; use ReflectionClass; +use ReflectionMethod; use ReflectionNamedType; class Reflector { + /** + * This is a PHP 7.4 compatible implementation of is_callable. + * + * @param mixed $var + * @param bool $syntaxOnly + * @return bool + */ + public static function isCallable($var, $syntaxOnly = false) + { + if (! is_array($var)) { + return is_callable($var, $syntaxOnly); + } + + if ((! isset($var[0]) || ! isset($var[1])) || + ! is_string($var[1] ?? null)) { + return false; + } + + if ($syntaxOnly && + (is_string($var[0]) || is_object($var[0])) && + is_string($var[1])) { + return true; + } + + $class = is_object($var[0]) ? get_class($var[0]) : $var[0]; + + $method = $var[1]; + + if (! class_exists($class)) { + return false; + } + + if (method_exists($class, $method)) { + return (new ReflectionMethod($class, $method))->isPublic(); + } + + if (is_object($var[0]) && method_exists($class, '__call')) { + return (new ReflectionMethod($class, '__call'))->isPublic(); + } + + if (! is_object($var[0]) && method_exists($class, '__callStatic')) { + return (new ReflectionMethod($class, '__callStatic'))->isPublic(); + } + + return false; + } + /** * Get the class name of the given parameter's type, if possible. * diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 76dfa9d8852c..c85142c41bcd 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -370,6 +370,45 @@ public static function words($value, $words = 100, $end = '...') return rtrim($matches[0]).$end; } + /** + * Pad both sides of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padBoth($value, $length, $pad = ' ') + { + return str_pad($value, $length, $pad, STR_PAD_BOTH); + } + + /** + * Pad the left side of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padLeft($value, $length, $pad = ' ') + { + return str_pad($value, $length, $pad, STR_PAD_LEFT); + } + + /** + * Pad the right side of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padRight($value, $length, $pad = ' ') + { + return str_pad($value, $length, $pad, STR_PAD_RIGHT); + } + /** * Parse a Class[@]method style callback into class and method. * @@ -485,6 +524,10 @@ public static function replaceFirst($search, $replace, $subject) */ public static function replaceLast($search, $replace, $subject) { + if ($search === '') { + return $subject; + } + $position = strrpos($subject, $search); if ($position !== false) { diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 42423c702ae7..82ecac845c65 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -338,6 +338,42 @@ public function matchAll($pattern) return collect($matches[1] ?? $matches[0]); } + /** + * Pad both sides of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padBoth($length, $pad = ' ') + { + return new static(Str::padBoth($this->value, $length, $pad)); + } + + /** + * Pad the left side of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padLeft($length, $pad = ' ') + { + return new static(Str::padLeft($this->value, $length, $pad)); + } + + /** + * Pad the right side of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padRight($length, $pad = ' ') + { + return new static(Str::padRight($this->value, $length, $pad)); + } + /** * Parse a Class@method style callback into class and method. * @@ -669,7 +705,7 @@ public function dd() { $this->dump(); - die(1); + exit(1); } /** diff --git a/src/Illuminate/Support/Testing/Fakes/MailFake.php b/src/Illuminate/Support/Testing/Fakes/MailFake.php index 8d0680a1e1ee..d299bb1c5a31 100644 --- a/src/Illuminate/Support/Testing/Fakes/MailFake.php +++ b/src/Illuminate/Support/Testing/Fakes/MailFake.php @@ -347,7 +347,7 @@ public function send($view, array $data = [], $callback = null) /** * Queue a new e-mail message for sending. * - * @param string|array $view + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view * @param string|null $queue * @return mixed */ diff --git a/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php b/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php index 64452e634517..7a7da77ffc39 100644 --- a/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php +++ b/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php @@ -21,7 +21,7 @@ public function __construct($mailer) /** * Send a new mailable message instance. * - * @param \Illuminate\Contracts\Mail\Mailable $mailable; + * @param \Illuminate\Contracts\Mail\Mailable $mailable * @return mixed */ public function send(Mailable $mailable) @@ -32,7 +32,7 @@ public function send(Mailable $mailable) /** * Send a mailable message immediately. * - * @param \Illuminate\Contracts\Mail\Mailable $mailable; + * @param \Illuminate\Contracts\Mail\Mailable $mailable * @return mixed * @deprecated Use send() instead. */ @@ -44,7 +44,7 @@ public function sendNow(Mailable $mailable) /** * Push the given mailable onto the queue. * - * @param \Illuminate\Contracts\Mail\Mailable $mailable; + * @param \Illuminate\Contracts\Mail\Mailable $mailable * @return mixed */ public function queue(Mailable $mailable) diff --git a/src/Illuminate/Support/Traits/EnumeratesValues.php b/src/Illuminate/Support/Traits/EnumeratesValues.php index 0b691f678e92..b8210bc1c169 100644 --- a/src/Illuminate/Support/Traits/EnumeratesValues.php +++ b/src/Illuminate/Support/Traits/EnumeratesValues.php @@ -169,9 +169,9 @@ public function containsStrict($key, $value = null) */ public function dd(...$args) { - call_user_func_array([$this, 'dump'], $args); + $this->dump(...$args); - die(1); + exit(1); } /** @@ -181,8 +181,8 @@ public function dd(...$args) */ public function dump() { - (new static(func_get_args())) - ->push($this) + (new Collection(func_get_args())) + ->push($this->all()) ->each(function ($item) { VarDumper::dump($item); }); @@ -412,13 +412,9 @@ public function partition($key, $operator = null, $value = null) */ public function sum($callback = null) { - if (is_null($callback)) { - $callback = function ($value) { - return $value; - }; - } else { - $callback = $this->valueRetriever($callback); - } + $callback = is_null($callback) + ? $this->identity() + : $this->valueRetriever($callback); return $this->reduce(function ($result, $item) use ($callback) { return $result + $callback($item); @@ -732,7 +728,7 @@ public function uniqueStrict($key = null) * * This is an alias to the "takeUntil" method. * - * @param mixed $key + * @param mixed $value * @return static * * @deprecated Use the "takeUntil" method directly. @@ -806,25 +802,6 @@ public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) return new CachingIterator($this->getIterator(), $flags); } - /** - * Count the number of items in the collection using a given truth test. - * - * @param callable|null $callback - * @return static - */ - public function countBy($callback = null) - { - if (is_null($callback)) { - $callback = function ($value) { - return $value; - }; - } - - return new static($this->groupBy($callback)->map(function ($value) { - return $value->count(); - })); - } - /** * Convert the collection to its string representation. * @@ -990,4 +967,16 @@ protected function negate(Closure $callback) return ! $callback(...$params); }; } + + /** + * Make a function that returns what's passed to it. + * + * @return \Closure + */ + protected function identity() + { + return function ($value) { + return $value; + }; + } } diff --git a/src/Illuminate/Support/Traits/Macroable.php b/src/Illuminate/Support/Traits/Macroable.php index 0c2112c7fba5..406f65edc79b 100644 --- a/src/Illuminate/Support/Traits/Macroable.php +++ b/src/Illuminate/Support/Traits/Macroable.php @@ -82,7 +82,7 @@ public static function __callStatic($method, $parameters) $macro = static::$macros[$method]; if ($macro instanceof Closure) { - return call_user_func_array(Closure::bind($macro, null, static::class), $parameters); + $macro = $macro->bindTo(null, static::class); } return $macro(...$parameters); @@ -108,7 +108,7 @@ public function __call($method, $parameters) $macro = static::$macros[$method]; if ($macro instanceof Closure) { - return call_user_func_array($macro->bindTo($this, static::class), $parameters); + $macro = $macro->bindTo($this, static::class); } return $macro(...$parameters); diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index fb85ebcce4d3..e3a04e04fa41 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -14,12 +14,12 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "ext-mbstring": "*", "doctrine/inflector": "^1.4|^2.0", "illuminate/contracts": "^7.0", - "nesbot/carbon": "^2.17", + "nesbot/carbon": "^2.31", "voku/portable-ascii": "^1.4.8" }, "conflict": { diff --git a/src/Illuminate/Testing/PendingCommand.php b/src/Illuminate/Testing/PendingCommand.php index a21f869ac49f..57ddb89e62f3 100644 --- a/src/Illuminate/Testing/PendingCommand.php +++ b/src/Illuminate/Testing/PendingCommand.php @@ -5,10 +5,12 @@ use Illuminate\Console\OutputStyle; use Illuminate\Contracts\Console\Kernel; use Illuminate\Contracts\Container\Container; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Arr; use Mockery; use Mockery\Exception\NoMatchingExpectationException; use PHPUnit\Framework\TestCase as PHPUnitTestCase; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -131,6 +133,27 @@ public function expectsOutput($output) return $this; } + /** + * Specify a table that should be printed when the command runs. + * + * @param array $headers + * @param \Illuminate\Contracts\Support\Arrayable|array $rows + * @param string $tableStyle + * @param array $columnStyles + * @return $this + */ + public function expectsTable($headers, $rows, $tableStyle = 'default', array $columnStyles = []) + { + $this->test->expectedTables[] = [ + 'headers' => (array) $headers, + 'rows' => $rows instanceof Arrayable ? $rows->toArray() : $rows, + 'tableStyle' => $tableStyle, + 'columnStyles' => $columnStyles, + ]; + + return $this; + } + /** * Assert that the command has the given exit code. * @@ -264,6 +287,8 @@ private function createABufferedOutputMock() ->shouldAllowMockingProtectedMethods() ->shouldIgnoreMissing(); + $this->applyTableOutputExpectations($mock); + foreach ($this->test->expectedOutput as $i => $output) { $mock->shouldReceive('doWrite') ->once() @@ -277,6 +302,36 @@ private function createABufferedOutputMock() return $mock; } + /** + * Apply the output table expectations to the mock. + * + * @param \Mockery\MockInterface $mock + * @return void + */ + private function applyTableOutputExpectations($mock) + { + foreach ($this->test->expectedTables as $consoleTable) { + $table = (new Table($output = new BufferedOutput)) + ->setHeaders($consoleTable['headers']) + ->setRows($consoleTable['rows']) + ->setStyle($consoleTable['tableStyle']); + + foreach ($consoleTable['columnStyles'] as $columnIndex => $columnStyle) { + $table->setColumnStyle($columnIndex, $columnStyle); + } + + $table->render(); + + $lines = array_filter( + preg_split("/\n/", $output->fetch()) + ); + + foreach ($lines as $line) { + $this->expectsOutput($line); + } + } + } + /** * Handle the object's destruction. * diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 2d0aed49c053..ba82ca47824a 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -5,6 +5,7 @@ use ArrayAccess; use Closure; use Illuminate\Contracts\View\View; +use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; @@ -299,7 +300,8 @@ public function assertCookie($cookieName, $value = null, $encrypted = true, $uns $cookieValue = $cookie->getValue(); $actual = $encrypted - ? app('encrypter')->decrypt($cookieValue, $unserialize) : $cookieValue; + ? CookieValuePrefix::remove(app('encrypter')->decrypt($cookieValue, $unserialize)) + : $cookieValue; PHPUnit::assertEquals( $value, $actual, @@ -727,7 +729,7 @@ public function assertJsonValidationErrors($errors, $responseKey = 'errors') PHPUnit::assertNotEmpty($errors, 'No validation errors were provided.'); - $jsonErrors = $this->json()[$responseKey] ?? []; + $jsonErrors = Arr::get($this->json(), $responseKey) ?? []; $errorMessage = $jsonErrors ? 'Response has the following JSON validation errors:'. @@ -870,7 +872,7 @@ public function assertViewHas($key, $value = null) $this->ensureResponseHasView(); if (is_null($value)) { - PHPUnit::assertArrayHasKey($key, $this->original->gatherData()); + PHPUnit::assertTrue(Arr::has($this->original->gatherData(), $key)); } elseif ($value instanceof Closure) { PHPUnit::assertTrue($value(Arr::get($this->original->gatherData(), $key))); } elseif ($value instanceof Model) { @@ -924,7 +926,7 @@ public function assertViewMissing($key) { $this->ensureResponseHasView(); - PHPUnit::assertArrayNotHasKey($key, $this->original->gatherData()); + PHPUnit::assertFalse(Arr::has($this->original->gatherData(), $key)); return $this; } diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index 36a8f7f0ce2d..1c18d912e4d2 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "illuminate/contracts": "^7.0", "illuminate/support": "^7.0", "symfony/polyfill-php73": "^1.17" @@ -33,8 +33,8 @@ "illuminate/console": "Required to assert console commands (^7.0).", "illuminate/database": "Required to assert databases (^7.0).", "illuminate/http": "Required to assert responses (^7.0).", - "mockery/mockery": "Required to use mocking (^1.3.1).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.0)." + "mockery/mockery": "Required to use mocking (~1.3.3|^1.4.2).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.3.3)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Translation/composer.json b/src/Illuminate/Translation/composer.json index 454892e49393..61685c3a8fae 100755 --- a/src/Illuminate/Translation/composer.json +++ b/src/Illuminate/Translation/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/contracts": "^7.0", "illuminate/filesystem": "^7.0", diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index 5aee3f1ea782..e380b6e1807a 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -374,7 +374,7 @@ protected function callReplacer($message, $attribute, $rule, $parameters, $valid $callback = $this->replacers[$rule]; if ($callback instanceof Closure) { - return call_user_func_array($callback, func_get_args()); + return $callback(...func_get_args()); } elseif (is_string($callback)) { return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator); } @@ -395,6 +395,6 @@ protected function callClassBasedReplacer($callback, $message, $attribute, $rule { [$class, $method] = Str::parseCallback($callback, 'replace'); - return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1)); + return $this->container->make($class)->{$method}(...array_slice(func_get_args(), 1)); } } diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index a7c1e6665011..eed211c81be5 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -563,7 +563,7 @@ protected function failsRatioCheck($parameters, $width, $height) [1, 1], array_filter(sscanf($parameters['ratio'], '%f/%d')) ); - $precision = 1 / max($width, $height); + $precision = 1 / (max($width, $height) + 1); return abs($numerator / $denominator - $width / $height) > $precision; } @@ -656,6 +656,8 @@ public function validateEmail($attribute, $value, $parameters) return new FilterEmailValidation(); } elseif ($validation === 'filter_unicode') { return FilterEmailValidation::unicode(); + } elseif (is_string($validation) && class_exists($validation)) { + return $this->container->make($validation); } }) ->values() @@ -933,6 +935,10 @@ public function validateGt($attribute, $value, $parameters) return $this->getSize($attribute, $value) > $parameters[0]; } + if (is_numeric($parameters[0])) { + return false; + } + if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) { return $value > $comparedToValue; } @@ -964,6 +970,10 @@ public function validateLt($attribute, $value, $parameters) return $this->getSize($attribute, $value) < $parameters[0]; } + if (is_numeric($parameters[0])) { + return false; + } + if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) { return $value < $comparedToValue; } @@ -995,6 +1005,10 @@ public function validateGte($attribute, $value, $parameters) return $this->getSize($attribute, $value) >= $parameters[0]; } + if (is_numeric($parameters[0])) { + return false; + } + if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) { return $value >= $comparedToValue; } @@ -1026,6 +1040,10 @@ public function validateLte($attribute, $value, $parameters) return $this->getSize($attribute, $value) <= $parameters[0]; } + if (is_numeric($parameters[0])) { + return false; + } + if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) { return $value <= $comparedToValue; } @@ -1046,7 +1064,7 @@ public function validateLte($attribute, $value, $parameters) */ public function validateImage($attribute, $value) { - return $this->validateMimes($attribute, $value, ['jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']); + return $this->validateMimes($attribute, $value, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']); } /** @@ -1152,7 +1170,11 @@ public function validateIpv6($attribute, $value) */ public function validateJson($attribute, $value) { - if (! is_scalar($value) && ! method_exists($value, '__toString')) { + if (is_array($value)) { + return false; + } + + if (! is_scalar($value) && ! is_null($value) && ! method_exists($value, '__toString')) { return false; } @@ -1198,6 +1220,10 @@ public function validateMimes($attribute, $value, $parameters) return false; } + if (in_array('jpg', $parameters) || in_array('jpeg', $parameters)) { + $parameters = array_unique(array_merge($parameters, ['jpg', 'jpeg'])); + } + return $value->getPath() !== '' && in_array($value->guessExtension(), $parameters); } @@ -1516,11 +1542,9 @@ public function validateRequiredUnless($attribute, $value, $parameters) { $this->requireParameterCount(2, $parameters, 'required_unless'); - $data = Arr::get($this->data, $parameters[0]); - - $values = array_slice($parameters, 1); + [$values, $other] = $this->prepareValuesAndOther($parameters); - if (! in_array($data, $values)) { + if (! in_array($other, $values)) { return $this->validateRequired($attribute, $value); } diff --git a/src/Illuminate/Validation/Rules/Dimensions.php b/src/Illuminate/Validation/Rules/Dimensions.php index 354d071c3af4..e2326c7732b1 100644 --- a/src/Illuminate/Validation/Rules/Dimensions.php +++ b/src/Illuminate/Validation/Rules/Dimensions.php @@ -14,7 +14,7 @@ class Dimensions /** * Create a new dimensions rule instance. * - * @param array $constraints; + * @param array $constraints * @return void */ public function __construct(array $constraints = []) diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 854c3cbcaa36..d210d41825e8 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -314,22 +314,29 @@ protected function replacePlaceholders($data) $originalData = []; foreach ($data as $key => $value) { - if (is_array($value)) { - $value = $this->replacePlaceholders($value); - } - - $key = str_replace( - [$this->dotPlaceholder, '__asterisk__'], - ['.', '*'], - $key - ); - - $originalData[$key] = $value; + $originalData[$this->replacePlaceholderInString($key)] = is_array($value) + ? $this->replacePlaceholders($value) + : $value; } return $originalData; } + /** + * Replace the placeholders in the given string. + * + * @param string $value + * @return string + */ + protected function replacePlaceholderInString(string $value) + { + return str_replace( + [$this->dotPlaceholder, '__asterisk__'], + ['.', '*'], + $value + ); + } + /** * Add an after validation callback. * @@ -339,7 +346,7 @@ protected function replacePlaceholders($data) public function after($callback) { $this->after[] = function () use ($callback) { - return call_user_func_array($callback, [$this]); + return $callback($this); }; return $this; @@ -722,6 +729,10 @@ protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute) */ protected function validateUsingCustomRule($attribute, $value, $rule) { + $attribute = $this->replacePlaceholderInString($attribute); + + $value = is_array($value) ? $this->replacePlaceholders($value) : $value; + if (! $rule->passes($attribute, $value)) { $this->failedRules[$attribute][get_class($rule)] = []; @@ -745,12 +756,14 @@ protected function validateUsingCustomRule($attribute, $value, $rule) */ protected function shouldStopValidating($attribute) { + $cleanedAttribute = $this->replacePlaceholderInString($attribute); + if ($this->hasRule($attribute, ['Bail'])) { - return $this->messages->has($attribute); + return $this->messages->has($cleanedAttribute); } - if (isset($this->failedRules[$attribute]) && - array_key_exists('uploaded', $this->failedRules[$attribute])) { + if (isset($this->failedRules[$cleanedAttribute]) && + array_key_exists('uploaded', $this->failedRules[$cleanedAttribute])) { return true; } @@ -758,8 +771,8 @@ protected function shouldStopValidating($attribute) // and that rule already failed then we should stop validation at this point // as now there is no point in calling other rules with this field empty. return $this->hasRule($attribute, $this->implicitRules) && - isset($this->failedRules[$attribute]) && - array_intersect(array_keys($this->failedRules[$attribute]), $this->implicitRules); + isset($this->failedRules[$cleanedAttribute]) && + array_intersect(array_keys($this->failedRules[$cleanedAttribute]), $this->implicitRules); } /** @@ -776,7 +789,11 @@ public function addFailure($attribute, $rule, $parameters = []) $this->passes(); } - $attribute = str_replace('__asterisk__', '*', $attribute); + $attribute = str_replace( + [$this->dotPlaceholder, '__asterisk__'], + ['.', '*'], + $attribute + ); if (in_array($rule, $this->excludeRules)) { return $this->excludeAttribute($attribute); @@ -1338,7 +1355,7 @@ protected function callExtension($rule, $parameters) $callback = $this->extensions[$rule]; if (is_callable($callback)) { - return call_user_func_array($callback, $parameters); + return $callback(...array_values($parameters)); } elseif (is_string($callback)) { return $this->callClassBasedExtension($callback, $parameters); } @@ -1355,7 +1372,7 @@ protected function callClassBasedExtension($callback, $parameters) { [$class, $method] = Str::parseCallback($callback, 'validate'); - return call_user_func_array([$this->container->make($class), $method], $parameters); + return $this->container->make($class)->{$method}(...array_values($parameters)); } /** diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 344311ce437b..32c02c96ea86 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "egulias/email-validator": "^2.1.10", "illuminate/container": "^7.0", diff --git a/src/Illuminate/View/AnonymousComponent.php b/src/Illuminate/View/AnonymousComponent.php index c31221af3138..a7887c5ad83f 100644 --- a/src/Illuminate/View/AnonymousComponent.php +++ b/src/Illuminate/View/AnonymousComponent.php @@ -19,7 +19,7 @@ class AnonymousComponent extends Component protected $data = []; /** - * Create a new class-less component instance. + * Create a new anonymous component instance. * * @param string $view * @param array $data diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index f73fd77c0f4c..7388b1061308 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -8,6 +8,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; use Illuminate\View\AnonymousComponent; +use Illuminate\View\ViewFinderInterface; use InvalidArgumentException; use ReflectionClass; @@ -237,7 +238,7 @@ protected function componentClass(string $component) return $class; } - if ($viewFactory->exists($view = "components.{$component}")) { + if ($viewFactory->exists($view = $this->guessViewName($component))) { return $view; } @@ -265,6 +266,25 @@ public function guessClassName(string $component) return $namespace.'View\\Components\\'.implode('\\', $componentPieces); } + /** + * Guess the view name for the given component. + * + * @param string $name + * @return string + */ + public function guessViewName($name) + { + $prefix = 'components.'; + + $delimiter = ViewFinderInterface::HINT_PATH_DELIMITER; + + if (Str::contains($name, $delimiter)) { + return Str::replaceFirst($delimiter, $delimiter.$prefix, $name); + } + + return $prefix.$name; + } + /** * Partition the data and extra attributes from the given array of attributes. * @@ -289,7 +309,7 @@ protected function partitionDataAndAttributes($class, array $attributes) return collect($attributes)->partition(function ($value, $key) use ($parameterNames) { return in_array(Str::camel($key), $parameterNames); - }); + })->all(); } /** @@ -311,8 +331,14 @@ protected function compileClosingTags(string $value) */ public function compileSlots(string $value) { - $value = preg_replace_callback('/<\s*x[\-\:]slot\s+name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/', function ($matches) { - return " @slot('".$this->stripQuotes($matches['name'])."') "; + $value = preg_replace_callback('/<\s*x[\-\:]slot\s+(:?)name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/', function ($matches) { + $name = $this->stripQuotes($matches['name']); + + if ($matches[1] !== ':') { + $name = "'{$name}'"; + } + + return " @slot({$name}) "; }, $value); return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value); diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php b/src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php index 3abe1c7f1a04..918e776e6ac0 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php @@ -2,6 +2,8 @@ namespace Illuminate\View\Compilers\Concerns; +use Illuminate\Support\Str; + trait CompilesConditionals { /** @@ -50,12 +52,12 @@ protected function compileEndAuth() /** * Compile the env statements into valid PHP. * - * @param string $environment + * @param string $environments * @return string */ - protected function compileEnv($environment) + protected function compileEnv($environments) { - return "environment{$environment}): ?>"; + return "environment{$environments}): ?>"; } /** @@ -135,6 +137,17 @@ protected function compileHasSection($expression) return "yieldContent{$expression}))): ?>"; } + /** + * Compile the section-missing statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSectionMissing($expression) + { + return "yieldContent{$expression}))): ?>"; + } + /** * Compile the if statements into valid PHP. * @@ -268,4 +281,26 @@ protected function compileEndSwitch() { return ''; } + + /** + * Compile an once block into valid PHP. + * + * @return string + */ + protected function compileOnce($id = null) + { + $id = $id ? $this->stripParentheses($id) : "'".(string) Str::uuid()."'"; + + return 'hasRenderedOnce('.$id.')): $__env->markAsRenderedOnce('.$id.'); ?>'; + } + + /** + * Compile an end-once block into valid PHP. + * + * @return string + */ + public function compileEndOnce() + { + return ''; + } } diff --git a/src/Illuminate/View/Component.php b/src/Illuminate/View/Component.php index 865653c4b0e9..c3e37549970c 100644 --- a/src/Illuminate/View/Component.php +++ b/src/Illuminate/View/Component.php @@ -49,14 +49,14 @@ abstract class Component /** * Get the view / view contents that represent the component. * - * @return \Illuminate\View\View|string + * @return \Illuminate\View\View|\Closure|string */ abstract public function render(); /** * Resolve the Blade view or view file that should be used when rendering the component. * - * @return \Illuminate\View\View|string + * @return \Illuminate\View\View|\Closure|string */ public function resolveView() { @@ -133,6 +133,9 @@ protected function extractPublicProperties() $reflection = new ReflectionClass($this); static::$propertyCache[$class] = collect($reflection->getProperties(ReflectionProperty::IS_PUBLIC)) + ->reject(function (ReflectionProperty $property) { + return $property->isStatic(); + }) ->reject(function (ReflectionProperty $property) { return $this->shouldIgnore($property->getName()); }) diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php index ba4bbe6ed463..cc9d628a9557 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -118,6 +118,19 @@ public function whereStartsWith($string) }); } + /** + * Return a bag of attributes with keys that do not start with the given value / pattern. + * + * @param string $string + * @return static + */ + public function whereDoesntStartWith($string) + { + return $this->filter(function ($value, $key) use ($string) { + return ! Str::startsWith($key, $string); + }); + } + /** * Return a bag of attributes that have keys starting with the given value / pattern. * @@ -152,7 +165,7 @@ public function exceptProps($keys) /** * Merge additional attributes / values into the attribute bag. * - * @param array $attributes + * @param array $attributeDefaults * @return static */ public function merge(array $attributeDefaults = []) @@ -206,7 +219,7 @@ public function toHtml() /** * Merge additional attributes / values into the attribute bag. * - * @param array $attributes + * @param array $attributeDefaults * @return \Illuminate\Support\HtmlString */ public function __invoke(array $attributeDefaults = []) diff --git a/src/Illuminate/View/Concerns/ManagesEvents.php b/src/Illuminate/View/Concerns/ManagesEvents.php index 1889a6b6fdaa..39902905de09 100644 --- a/src/Illuminate/View/Concerns/ManagesEvents.php +++ b/src/Illuminate/View/Concerns/ManagesEvents.php @@ -121,9 +121,7 @@ protected function buildClassEventCallback($class, $prefix) // the instance out of the IoC container and call the method on it with the // given arguments that are passed to the Closure as the composer's data. return function () use ($class, $method) { - return call_user_func_array( - [$this->container->make($class), $method], func_get_args() - ); + return $this->container->make($class)->{$method}(...func_get_args()); }; } diff --git a/src/Illuminate/View/Concerns/ManagesLayouts.php b/src/Illuminate/View/Concerns/ManagesLayouts.php index 29d71552a8ae..785b9fa594de 100644 --- a/src/Illuminate/View/Concerns/ManagesLayouts.php +++ b/src/Illuminate/View/Concerns/ManagesLayouts.php @@ -185,6 +185,17 @@ public function hasSection($name) return array_key_exists($name, $this->sections); } + /** + * Check if section does not exist. + * + * @param string $name + * @return bool + */ + public function sectionMissing($name) + { + return ! $this->hasSection($name); + } + /** * Get the contents of a section. * diff --git a/src/Illuminate/View/Factory.php b/src/Illuminate/View/Factory.php index 1fa14adc130d..cdb803f34d9f 100755 --- a/src/Illuminate/View/Factory.php +++ b/src/Illuminate/View/Factory.php @@ -83,6 +83,13 @@ class Factory implements FactoryContract */ protected $renderCount = 0; + /** + * The "once" block IDs that have been rendered. + * + * @var array + */ + protected $renderedOnce = []; + /** * Create a new view factory instance. * @@ -352,6 +359,28 @@ public function doneRendering() return $this->renderCount == 0; } + /** + * Determine if the given once token has been rendered. + * + * @param string $id + * @return bool + */ + public function hasRenderedOnce(string $id) + { + return isset($this->renderedOnce[$id]); + } + + /** + * Mark the given once token as having been rendered. + * + * @param string $id + * @return void + */ + public function markAsRenderedOnce(string $id) + { + $this->renderedOnce[$id] = true; + } + /** * Add a location to the array of view locations. * @@ -434,6 +463,7 @@ public function addExtension($extension, $engine, $resolver = null) public function flushState() { $this->renderCount = 0; + $this->renderedOnce = []; $this->flushSections(); $this->flushStacks(); diff --git a/src/Illuminate/View/composer.json b/src/Illuminate/View/composer.json index 88141f76b56b..bac215568b17 100644 --- a/src/Illuminate/View/composer.json +++ b/src/Illuminate/View/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.2.5", + "php": "^7.2.5|^8.0", "ext-json": "*", "illuminate/container": "^7.0", "illuminate/contracts": "^7.0", diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index cc350edaf874..11c0d65d6c85 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -328,7 +328,7 @@ public function testCurrentUserThatIsOnGateAlwaysInjectedIntoClosureCallbacks() $gate = $this->getBasicGate(); $gate->define('foo', function ($user) { - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); return true; }); @@ -519,7 +519,7 @@ public function testForUserMethodAttachesANewUserToANewGateInstance() // Assert that the callback receives the new user with ID of 2 instead of ID of 1... $gate->define('foo', function ($user) { - $this->assertEquals(2, $user->id); + $this->assertSame(2, $user->id); return true; }); @@ -541,16 +541,16 @@ public function testForUserMethodAttachesANewUserToANewGateInstanceWithGuessCall }; $gate->guessPolicyNamesUsing($guesserCallback); $gate->getPolicyFor('fooClass'); - $this->assertEquals(1, $counter); + $this->assertSame(1, $counter); // now the guesser callback should be present on the new gate as well $newGate = $gate->forUser((object) ['id' => 1]); $newGate->getPolicyFor('fooClass'); - $this->assertEquals(2, $counter); + $this->assertSame(2, $counter); $newGate->getPolicyFor('fooClass'); - $this->assertEquals(3, $counter); + $this->assertSame(3, $counter); } /** diff --git a/tests/Auth/AuthDatabaseUserProviderTest.php b/tests/Auth/AuthDatabaseUserProviderTest.php index 279a90181163..e75cef6d6197 100755 --- a/tests/Auth/AuthDatabaseUserProviderTest.php +++ b/tests/Auth/AuthDatabaseUserProviderTest.php @@ -28,7 +28,7 @@ public function testRetrieveByIDReturnsUserWhenUserIsFound() $user = $provider->retrieveById(1); $this->assertInstanceOf(GenericUser::class, $user); - $this->assertEquals(1, $user->getAuthIdentifier()); + $this->assertSame(1, $user->getAuthIdentifier()); $this->assertSame('Dayle', $user->name); } @@ -98,7 +98,7 @@ public function testRetrieveByCredentialsReturnsUserWhenUserIsFound() $user = $provider->retrieveByCredentials(['username' => 'dayle', 'password' => 'foo', 'group' => ['one', 'two']]); $this->assertInstanceOf(GenericUser::class, $user); - $this->assertEquals(1, $user->getAuthIdentifier()); + $this->assertSame(1, $user->getAuthIdentifier()); $this->assertSame('taylor', $user->name); } diff --git a/tests/Auth/AuthEloquentUserProviderTest.php b/tests/Auth/AuthEloquentUserProviderTest.php index 322283c266d6..1139af745610 100755 --- a/tests/Auth/AuthEloquentUserProviderTest.php +++ b/tests/Auth/AuthEloquentUserProviderTest.php @@ -64,7 +64,6 @@ public function testRetrieveTokenWithBadIdentifierReturnsNull() public function testRetrievingWithOnlyPasswordCredentialReturnsNull() { $provider = $this->getProviderMock(); - $mock = m::mock(stdClass::class); $user = $provider->retrieveByCredentials(['api_password' => 'foo']); $this->assertNull($user); diff --git a/tests/Auth/AuthPasswordBrokerTest.php b/tests/Auth/AuthPasswordBrokerTest.php index 8bc760814a6c..565990be2d1d 100755 --- a/tests/Auth/AuthPasswordBrokerTest.php +++ b/tests/Auth/AuthPasswordBrokerTest.php @@ -26,7 +26,7 @@ public function testIfUserIsNotFoundErrorRedirectIsReturned() $broker = $this->getMockBuilder(PasswordBroker::class)->setMethods(['getUser', 'makeErrorRedirect'])->setConstructorArgs(array_values($mocks))->getMock(); $broker->expects($this->once())->method('getUser')->willReturn(null); - $this->assertEquals(PasswordBrokerContract::INVALID_USER, $broker->sendResetLink(['credentials'])); + $this->assertSame(PasswordBrokerContract::INVALID_USER, $broker->sendResetLink(['credentials'])); } public function testIfTokenIsRecentlyCreated() @@ -37,7 +37,7 @@ public function testIfTokenIsRecentlyCreated() $mocks['tokens']->shouldReceive('recentlyCreatedToken')->once()->with($user)->andReturn(true); $user->shouldReceive('sendPasswordResetNotification')->with('token'); - $this->assertEquals(PasswordBrokerContract::RESET_THROTTLED, $broker->sendResetLink(['foo'])); + $this->assertSame(PasswordBrokerContract::RESET_THROTTLED, $broker->sendResetLink(['foo'])); } public function testGetUserThrowsExceptionIfUserDoesntImplementCanResetPassword() @@ -68,7 +68,7 @@ public function testBrokerCreatesTokenAndRedirectsWithoutError() $mocks['tokens']->shouldReceive('create')->once()->with($user)->andReturn('token'); $user->shouldReceive('sendPasswordResetNotification')->with('token'); - $this->assertEquals(PasswordBrokerContract::RESET_LINK_SENT, $broker->sendResetLink(['foo'])); + $this->assertSame(PasswordBrokerContract::RESET_LINK_SENT, $broker->sendResetLink(['foo'])); } public function testRedirectIsReturnedByResetWhenUserCredentialsInvalid() @@ -76,7 +76,7 @@ public function testRedirectIsReturnedByResetWhenUserCredentialsInvalid() $broker = $this->getBroker($mocks = $this->getMocks()); $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['creds'])->andReturn(null); - $this->assertEquals(PasswordBrokerContract::INVALID_USER, $broker->reset(['creds'], function () { + $this->assertSame(PasswordBrokerContract::INVALID_USER, $broker->reset(['creds'], function () { // })); } @@ -88,7 +88,7 @@ public function testRedirectReturnedByRemindWhenRecordDoesntExistInTable() $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(Arr::except($creds, ['token']))->andReturn($user = m::mock(CanResetPassword::class)); $mocks['tokens']->shouldReceive('exists')->with($user, 'token')->andReturn(false); - $this->assertEquals(PasswordBrokerContract::INVALID_TOKEN, $broker->reset($creds, function () { + $this->assertSame(PasswordBrokerContract::INVALID_TOKEN, $broker->reset($creds, function () { // })); } @@ -105,7 +105,7 @@ public function testResetRemovesRecordOnReminderTableAndCallsCallback() return 'foo'; }; - $this->assertEquals(PasswordBrokerContract::PASSWORD_RESET, $broker->reset(['password' => 'password', 'token' => 'token'], $callback)); + $this->assertSame(PasswordBrokerContract::PASSWORD_RESET, $broker->reset(['password' => 'password', 'token' => 'token'], $callback)); $this->assertEquals(['user' => $user, 'password' => 'password'], $_SERVER['__password.reset.test']); } diff --git a/tests/Auth/AuthTokenGuardTest.php b/tests/Auth/AuthTokenGuardTest.php index 1f0b9c80e9ff..b79c079b8885 100644 --- a/tests/Auth/AuthTokenGuardTest.php +++ b/tests/Auth/AuthTokenGuardTest.php @@ -27,10 +27,10 @@ public function testUserCanBeRetrievedByQueryStringVariable() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); $this->assertTrue($guard->check()); $this->assertFalse($guard->guest()); - $this->assertEquals(1, $guard->id()); + $this->assertSame(1, $guard->id()); } public function testTokenCanBeHashed() @@ -45,10 +45,10 @@ public function testTokenCanBeHashed() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); $this->assertTrue($guard->check()); $this->assertFalse($guard->guest()); - $this->assertEquals(1, $guard->id()); + $this->assertSame(1, $guard->id()); } public function testUserCanBeRetrievedByAuthHeaders() @@ -61,7 +61,7 @@ public function testUserCanBeRetrievedByAuthHeaders() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); } public function testUserCanBeRetrievedByBearerToken() @@ -74,7 +74,7 @@ public function testUserCanBeRetrievedByBearerToken() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); } public function testValidateCanDetermineIfCredentialsAreValid() @@ -124,7 +124,7 @@ public function testItAllowsToPassCustomRequestInSetterAndUseItForValidation() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); } public function testUserCanBeRetrievedByBearerTokenWithCustomKey() @@ -137,7 +137,7 @@ public function testUserCanBeRetrievedByBearerTokenWithCustomKey() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); } public function testUserCanBeRetrievedByQueryStringVariableWithCustomKey() @@ -152,10 +152,10 @@ public function testUserCanBeRetrievedByQueryStringVariableWithCustomKey() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); $this->assertTrue($guard->check()); $this->assertFalse($guard->guest()); - $this->assertEquals(1, $guard->id()); + $this->assertSame(1, $guard->id()); } public function testUserCanBeRetrievedByAuthHeadersWithCustomField() @@ -168,7 +168,7 @@ public function testUserCanBeRetrievedByAuthHeadersWithCustomField() $user = $guard->user(); - $this->assertEquals(1, $user->id); + $this->assertSame(1, $user->id); } public function testValidateCanDetermineIfCredentialsAreValidWithCustomKey() diff --git a/tests/Cache/CacheMemcachedStoreTest.php b/tests/Cache/CacheMemcachedStoreTest.php index edbc66fbc6ba..f65637e967b3 100755 --- a/tests/Cache/CacheMemcachedStoreTest.php +++ b/tests/Cache/CacheMemcachedStoreTest.php @@ -5,11 +5,19 @@ use Illuminate\Cache\MemcachedStore; use Illuminate\Support\Carbon; use Memcached; +use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; class CacheMemcachedStoreTest extends TestCase { + public function tearDown(): void + { + m::close(); + + parent::tearDown(); + } + public function testGetReturnsNullWhenNotFound() { if (! class_exists(Memcached::class)) { @@ -80,9 +88,15 @@ public function testIncrementMethodProperlyCallsMemcache() $this->markTestSkipped('Memcached module not installed'); } - $memcache = $this->getMockBuilder(Memcached::class)->setMethods(['increment'])->getMock(); - $memcache->expects($this->once())->method('increment')->with($this->equalTo('foo'), $this->equalTo(5)); - $store = new MemcachedStore($memcache); + /* @link https://github.com/php-memcached-dev/php-memcached/pull/468 */ + if (version_compare(phpversion(), '8.0.0', '>=')) { + $this->markTestSkipped('Test broken due to parse error in PHP Memcached.'); + } + + $memcached = m::mock(Memcached::class); + $memcached->shouldReceive('increment')->with('foo', 5)->once()->andReturn(5); + + $store = new MemcachedStore($memcached); $store->increment('foo', 5); } @@ -92,9 +106,15 @@ public function testDecrementMethodProperlyCallsMemcache() $this->markTestSkipped('Memcached module not installed'); } - $memcache = $this->getMockBuilder(Memcached::class)->setMethods(['decrement'])->getMock(); - $memcache->expects($this->once())->method('decrement')->with($this->equalTo('foo'), $this->equalTo(5)); - $store = new MemcachedStore($memcache); + /* @link https://github.com/php-memcached-dev/php-memcached/pull/468 */ + if (version_compare(phpversion(), '8.0.0', '>=')) { + $this->markTestSkipped('Test broken due to parse error in PHP Memcached.'); + } + + $memcached = m::mock(Memcached::class); + $memcached->shouldReceive('decrement')->with('foo', 5)->once()->andReturn(0); + + $store = new MemcachedStore($memcached); $store->decrement('foo', 5); } diff --git a/tests/Console/ConsoleEventSchedulerTest.php b/tests/Console/ConsoleEventSchedulerTest.php index 6e366d885210..19c87e6e3249 100644 --- a/tests/Console/ConsoleEventSchedulerTest.php +++ b/tests/Console/ConsoleEventSchedulerTest.php @@ -117,6 +117,19 @@ public function testCreateNewArtisanCommandUsingCommandClass() $binary = $escape.PHP_BINARY.$escape; $this->assertEquals($binary.' artisan foo:bar --force', $events[0]->command); } + + public function testCallCreatesNewJobWithTimezone() + { + $schedule = new Schedule('UTC'); + $schedule->call('path/to/command'); + $events = $schedule->events(); + $this->assertSame('UTC', $events[0]->timezone); + + $schedule = new Schedule('Asia/Tokyo'); + $schedule->call('path/to/command'); + $events = $schedule->events(); + $this->assertSame('Asia/Tokyo', $events[0]->timezone); + } } class FooClassStub diff --git a/tests/Container/ContainerCallTest.php b/tests/Container/ContainerCallTest.php index 67144ce5b73a..d6f5d0d96d29 100644 --- a/tests/Container/ContainerCallTest.php +++ b/tests/Container/ContainerCallTest.php @@ -3,18 +3,18 @@ namespace Illuminate\Tests\Container; use Closure; +use Error; use Illuminate\Container\Container; use Illuminate\Contracts\Container\BindingResolutionException; use PHPUnit\Framework\TestCase; -use ReflectionException; use stdClass; class ContainerCallTest extends TestCase { public function testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException() { - $this->expectException(ReflectionException::class); - $this->expectExceptionMessage('Function ContainerTestCallStub() does not exist'); + $this->expectException(Error::class); + $this->expectExceptionMessage('Call to undefined function ContainerTestCallStub()'); $container = new Container; $container->call('ContainerTestCallStub'); @@ -187,6 +187,15 @@ public function testCallWithoutRequiredParamsThrowsException() $container->call(ContainerTestCallStub::class.'@unresolvable'); } + public function testCallWithUnnamedParametersThrowsException() + { + $this->expectException(BindingResolutionException::class); + $this->expectExceptionMessage('Unable to resolve dependency [Parameter #0 [ $foo ]] in class Illuminate\Tests\Container\ContainerTestCallStub'); + + $container = new Container; + $container->call([new ContainerTestCallStub, 'unresolvable'], ['foo', 'bar']); + } + public function testCallWithoutRequiredParamsOnClosureThrowsException() { $this->expectException(BindingResolutionException::class); diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 7aa77ba1cd49..13361beb1380 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -120,6 +120,15 @@ public function testSharedConcreteResolution() $this->assertSame($var1, $var2); } + public function testBindFailsLoudlyWithInvalidArgument() + { + $this->expectException(\TypeError::class); + $container = new Container; + + $concrete = new ContainerConcreteStub(); + $container->bind(ContainerConcreteStub::class, $concrete); + } + public function testAbstractToConcreteResolution() { $container = new Container; diff --git a/tests/Database/DatabaseConnectionTest.php b/tests/Database/DatabaseConnectionTest.php index d987ce5cf453..4f69821824e3 100755 --- a/tests/Database/DatabaseConnectionTest.php +++ b/tests/Database/DatabaseConnectionTest.php @@ -61,7 +61,10 @@ public function testSelectProperlyCallsPDO() $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->setMethods(['prepare'])->getMock(); $writePdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->setMethods(['prepare'])->getMock(); $writePdo->expects($this->never())->method('prepare'); - $statement = $this->getMockBuilder('PDOStatement')->setMethods(['execute', 'fetchAll', 'bindValue'])->getMock(); + $statement = $this->getMockBuilder('PDOStatement') + ->setMethods(['setFetchMode', 'execute', 'fetchAll', 'bindValue']) + ->getMock(); + $statement->expects($this->once())->method('setFetchMode'); $statement->expects($this->once())->method('bindValue')->with('foo', 'bar', 2); $statement->expects($this->once())->method('execute'); $statement->expects($this->once())->method('fetchAll')->willReturn(['boom']); @@ -151,9 +154,8 @@ public function testTransactionLevelNotIncrementedOnTransactionException() public function testBeginTransactionMethodRetriesOnFailure() { $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class); - $pdo->expects($this->at(0)) - ->method('beginTransaction') - ->will($this->throwException(new ErrorException('server has gone away'))); + $pdo->method('beginTransaction') + ->willReturnOnConsecutiveCalls($this->throwException(new ErrorException('server has gone away'))); $connection = $this->getMockConnection(['reconnect'], $pdo); $connection->expects($this->once())->method('reconnect'); $connection->beginTransaction(); @@ -179,6 +181,7 @@ public function testBeginTransactionMethodNeverRetriesIfWithinTransaction() $pdo->expects($this->once())->method('exec')->will($this->throwException(new Exception)); $connection = $this->getMockConnection(['reconnect'], $pdo); $queryGrammar = $this->createMock(Grammar::class); + $queryGrammar->expects($this->once())->method('compileSavepoint')->willReturn('trans1'); $queryGrammar->expects($this->once())->method('supportsSavepoints')->willReturn(true); $connection->setQueryGrammar($queryGrammar); $connection->expects($this->never())->method('reconnect'); diff --git a/tests/Database/DatabaseConnectorTest.php b/tests/Database/DatabaseConnectorTest.php index 3181e785ee2e..a796db880c8b 100755 --- a/tests/Database/DatabaseConnectorTest.php +++ b/tests/Database/DatabaseConnectorTest.php @@ -52,6 +52,24 @@ public function mySqlConnectProvider() ]; } + public function testMySqlConnectCallsCreateConnectionWithIsolationLevel() + { + $dsn = 'mysql:host=foo;dbname=bar'; + $config = ['host' => 'foo', 'database' => 'bar', 'collation' => 'utf8_unicode_ci', 'charset' => 'utf8', 'isolation_level' => 'REPEATABLE READ']; + + $connector = $this->getMockBuilder(MySqlConnector::class)->setMethods(['createConnection', 'getOptions'])->getMock(); + $connection = m::mock(PDO::class); + $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); + $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); + $connection->shouldReceive('prepare')->once()->with('set names \'utf8\' collate \'utf8_unicode_ci\'')->andReturn($connection); + $connection->shouldReceive('prepare')->once()->with('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ')->andReturn($connection); + $connection->shouldReceive('execute')->zeroOrMoreTimes(); + $connection->shouldReceive('exec')->zeroOrMoreTimes(); + $result = $connector->connect($config); + + $this->assertSame($result, $connection); + } + public function testPostgresConnectCallsCreateConnectionWithProperArguments() { $dsn = 'pgsql:host=foo;dbname=bar;port=111'; diff --git a/tests/Database/DatabaseEloquentBelongsToTest.php b/tests/Database/DatabaseEloquentBelongsToTest.php index 18faec7c18e9..a4dce7ddbf10 100755 --- a/tests/Database/DatabaseEloquentBelongsToTest.php +++ b/tests/Database/DatabaseEloquentBelongsToTest.php @@ -78,10 +78,16 @@ public function testEagerConstraintsAreProperlyAdded() public function testIdsInEagerConstraintsCanBeZero() { + $keys = ['foreign.value', 0]; + + if (version_compare(PHP_VERSION, '8.0.0-dev', '>=')) { + sort($keys); + } + $relation = $this->getRelation(); $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); - $relation->getQuery()->shouldReceive('whereIntegerInRaw')->once()->with('relation.id', ['foreign.value', 0]); + $relation->getQuery()->shouldReceive('whereIntegerInRaw')->once()->with('relation.id', $keys); $models = [new EloquentBelongsToModelStub, new EloquentBelongsToModelStubWithZeroId]; $relation->addEagerConstraints($models); } diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 8ef987c7c938..64b5249d87af 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -31,7 +31,9 @@ protected function tearDown(): void public function testFindMethod() { $builder = m::mock(Builder::class.'[first]', [$this->getMockQueryBuilder()]); - $builder->setModel($this->getMockModel()); + $model = $this->getMockModel(); + $builder->setModel($model); + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $builder->getQuery()->shouldReceive('where')->once()->with('foo_table.foo', '=', 'bar'); $builder->shouldReceive('first')->with(['column'])->andReturn('baz'); @@ -76,6 +78,7 @@ public function testFindManyMethod() public function testFindOrNewMethodModelFound() { $model = $this->getMockModel(); + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $model->shouldReceive('findOrNew')->once()->andReturn('baz'); $builder = m::mock(Builder::class.'[first]', [$this->getMockQueryBuilder()]); @@ -91,6 +94,7 @@ public function testFindOrNewMethodModelFound() public function testFindOrNewMethodModelNotFound() { $model = $this->getMockModel(); + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $model->shouldReceive('findOrNew')->once()->andReturn(m::mock(Model::class)); $builder = m::mock(Builder::class.'[first]', [$this->getMockQueryBuilder()]); @@ -109,7 +113,9 @@ public function testFindOrFailMethodThrowsModelNotFoundException() $this->expectException(ModelNotFoundException::class); $builder = m::mock(Builder::class.'[first]', [$this->getMockQueryBuilder()]); - $builder->setModel($this->getMockModel()); + $model = $this->getMockModel(); + $model->shouldReceive('getKeyType')->once()->andReturn('int'); + $builder->setModel($model); $builder->getQuery()->shouldReceive('where')->once()->with('foo_table.foo', '=', 'bar'); $builder->shouldReceive('first')->with(['column'])->andReturn(null); $builder->findOrFail('bar', ['column']); @@ -454,7 +460,9 @@ public function testGlobalMacrosAreCalledOnBuilder() return $bar; }); - Builder::macro('bam', [Builder::class, 'getQuery']); + Builder::macro('bam', function () { + return $this->getQuery(); + }); $builder = $this->getBuilder(); @@ -1038,11 +1046,38 @@ public function testWhereKeyMethodWithInt() $int = 1; + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '=', $int); $builder->whereKey($int); } + public function testWhereKeyMethodWithStringZero() + { + $model = new EloquentBuilderTestStubStringPrimaryKey(); + $builder = $this->getBuilder()->setModel($model); + $keyName = $model->getQualifiedKeyName(); + + $int = 0; + + $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '=', (string) $int); + + $builder->whereKey($int); + } + + public function testWhereKeyMethodWithStringNull() + { + $model = new EloquentBuilderTestStubStringPrimaryKey(); + $builder = $this->getBuilder()->setModel($model); + $keyName = $model->getQualifiedKeyName(); + + $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '=', m::on(function ($argument) { + return $argument === null; + })); + + $builder->whereKey(null); + } + public function testWhereKeyMethodWithArray() { $model = $this->getMockModel(); @@ -1069,6 +1104,32 @@ public function testWhereKeyMethodWithCollection() $builder->whereKey($collection); } + public function testWhereKeyNotMethodWithStringZero() + { + $model = new EloquentBuilderTestStubStringPrimaryKey(); + $builder = $this->getBuilder()->setModel($model); + $keyName = $model->getQualifiedKeyName(); + + $int = 0; + + $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '!=', (string) $int); + + $builder->whereKeyNot($int); + } + + public function testWhereKeyNotMethodWithStringNull() + { + $model = new EloquentBuilderTestStubStringPrimaryKey(); + $builder = $this->getBuilder()->setModel($model); + $keyName = $model->getQualifiedKeyName(); + + $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '!=', m::on(function ($argument) { + return $argument === null; + })); + + $builder->whereKeyNot(null); + } + public function testWhereKeyNotMethodWithInt() { $model = $this->getMockModel(); @@ -1077,6 +1138,7 @@ public function testWhereKeyNotMethodWithInt() $int = 1; + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '!=', $int); $builder->whereKeyNot($int); @@ -1445,3 +1507,12 @@ class EloquentBuilderTestStubWithoutTimestamp extends Model protected $table = 'table'; } + +class EloquentBuilderTestStubStringPrimaryKey extends Model +{ + public $incrementing = false; + + protected $table = 'foo_table'; + + protected $keyType = 'string'; +} diff --git a/tests/Database/DatabaseEloquentCollectionTest.php b/tests/Database/DatabaseEloquentCollectionTest.php index 4ab2a4d7e3b6..ce216d705317 100755 --- a/tests/Database/DatabaseEloquentCollectionTest.php +++ b/tests/Database/DatabaseEloquentCollectionTest.php @@ -230,6 +230,35 @@ public function testMappingToNonModelsReturnsABaseCollection() $this->assertEquals(BaseCollection::class, get_class($c)); } + public function testMapWithKeys() + { + $one = m::mock(Model::class); + $two = m::mock(Model::class); + + $c = new Collection([$one, $two]); + + $key = 0; + $cAfterMap = $c->mapWithKeys(function ($item) use (&$key) { + return [$key++ => $item]; + }); + + $this->assertEquals($c->all(), $cAfterMap->all()); + $this->assertInstanceOf(Collection::class, $cAfterMap); + } + + public function testMapWithKeysToNonModelsReturnsABaseCollection() + { + $one = m::mock(Model::class); + $two = m::mock(Model::class); + + $key = 0; + $c = (new Collection([$one, $two]))->mapWithKeys(function ($item) use (&$key) { + return [$key++ => 'not-a-model']; + }); + + $this->assertEquals(BaseCollection::class, get_class($c)); + } + public function testCollectionDiffsWithGivenCollection() { $one = m::mock(Model::class); diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 1f4d8403b14d..6528de04481c 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -11,6 +11,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; +use Illuminate\Database\ConnectionResolverInterface as Resolver; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -1096,11 +1097,21 @@ public function testUnderscorePropertiesAreNotFilled() public function testGuarded() { $model = new EloquentModelStub; + + EloquentModelStub::setConnectionResolver($resolver = m::mock(Resolver::class)); + $resolver->shouldReceive('connection')->andReturn($connection = m::mock(stdClass::class)); + $connection->shouldReceive('getSchemaBuilder->getColumnListing')->andReturn(['name', 'age', 'foo']); + $model->guard(['name', 'age']); $model->fill(['name' => 'foo', 'age' => 'bar', 'foo' => 'bar']); $this->assertFalse(isset($model->name)); $this->assertFalse(isset($model->age)); $this->assertSame('bar', $model->foo); + + $model = new EloquentModelStub; + $model->guard(['name', 'age']); + $model->fill(['Foo' => 'bar']); + $this->assertFalse(isset($model->Foo)); } public function testFillableOverridesGuarded() @@ -2314,7 +2325,7 @@ public function getDates() class EloquentModelSaveStub extends Model { protected $table = 'save_stub'; - protected $guarded = ['id']; + protected $guarded = []; public function save(array $options = []) { diff --git a/tests/Database/DatabaseEloquentMorphToTest.php b/tests/Database/DatabaseEloquentMorphToTest.php index 5f9049b4a4a5..c01dfd8b924f 100644 --- a/tests/Database/DatabaseEloquentMorphToTest.php +++ b/tests/Database/DatabaseEloquentMorphToTest.php @@ -90,6 +90,16 @@ public function testMorphToWithArrayDefault() $this->assertSame('taylor', $result->username); } + public function testMorphToWithZeroMorphType() + { + $parent = $this->getMockBuilder(EloquentMorphToModelStub::class)->setMethods(['getAttribute', 'morphEagerTo', 'morphInstanceTo'])->getMock(); + $parent->method('getAttribute')->with('relation_type')->willReturn(0); + $parent->expects($this->once())->method('morphInstanceTo'); + $parent->expects($this->never())->method('morphEagerTo'); + + $parent->relation(); + } + public function testMorphToWithSpecifiedClassDefault() { $parent = new EloquentMorphToModelStub; diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index dbd11ad3b9db..7d53cb8f4a9c 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -301,24 +301,29 @@ public function testBasicWheres() public function testWheresWithArrayValue() { $builder = $this->getBuilder(); - $builder->select('*')->from('users')->where('id', [12, 30]); + $builder->select('*')->from('users')->where('id', [12]); $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('id', '=', [12, 30]); $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('id', '!=', [12, 30]); $this->assertSame('select * from "users" where "id" != ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getBuilder(); $builder->select('*')->from('users')->where('id', '<>', [12, 30]); $this->assertSame('select * from "users" where "id" <> ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('id', '=', [[12, 30]]); + $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); + $this->assertEquals([0 => 12], $builder->getBindings()); } public function testMySqlWrappingProtectsQuotationMarks() @@ -649,6 +654,16 @@ public function testWhereBetweens() $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereBetween('id', [[1, 2, 3]]); + $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereBetween('id', [[1], [2, 3]]); + $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + $builder = $this->getBuilder(); $builder->select('*')->from('users')->whereNotBetween('id', [1, 2]); $this->assertSame('select * from "users" where "id" not between ? and ?', $builder->toSql()); @@ -660,6 +675,24 @@ public function testWhereBetweens() $this->assertEquals([], $builder->getBindings()); } + public function testWhereBetweenColumns() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereBetweenColumns('id', ['users.created_at', 'users.updated_at']); + $this->assertSame('select * from "users" where "id" between "users"."created_at" and "users"."updated_at"', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereNotBetweenColumns('id', ['created_at', 'updated_at']); + $this->assertSame('select * from "users" where "id" not between "created_at" and "updated_at"', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereBetweenColumns('id', [new Raw(1), new Raw(2)]); + $this->assertSame('select * from "users" where "id" between 1 and 2', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + } + public function testBasicOrWheres() { $builder = $this->getBuilder(); @@ -1226,10 +1259,19 @@ public function testHavings() $builder = $this->getBuilder(); $builder->select(['category', new Raw('count(*) as "total"')])->from('item')->where('department', '=', 'popular')->groupBy('category')->having('total', '>', 3); $this->assertSame('select "category", count(*) as "total" from "item" where "department" = ? group by "category" having "total" > ?', $builder->toSql()); + } + public function testHavingBetweens() + { $builder = $this->getBuilder(); - $builder->select('*')->from('users')->havingBetween('last_login_date', ['2018-11-16', '2018-12-16']); - $this->assertSame('select * from "users" having "last_login_date" between ? and ?', $builder->toSql()); + $builder->select('*')->from('users')->havingBetween('id', [1, 2, 3]); + $this->assertSame('select * from "users" having "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->havingBetween('id', [[1, 2], [3, 4]]); + $this->assertSame('select * from "users" having "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); } public function testHavingShortcut() @@ -2686,8 +2728,8 @@ public function testMySqlWrappingJsonWithBooleanAndIntegerThatLooksLikeOne() public function testJsonPathEscaping() { - $expectedWithJsonEscaped = <<getMySqlBuilder(); diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index 17b8b3447dd1..9f2fd3217b1a 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -388,7 +388,6 @@ public function testEnvPathsAreUsedForCachePathsWhenSpecified() $_SERVER['APP_ROUTES_CACHE'] = '/absolute/path/routes.php'; $_SERVER['APP_EVENTS_CACHE'] = '/absolute/path/events.php'; - $ds = DIRECTORY_SEPARATOR; $this->assertSame('/absolute/path/services.php', $app->getCachedServicesPath()); $this->assertSame('/absolute/path/packages.php', $app->getCachedPackagesPath()); $this->assertSame('/absolute/path/config.php', $app->getCachedConfigPath()); diff --git a/tests/Hashing/HasherTest.php b/tests/Hashing/HasherTest.php index 3886cdef777b..462372ca16c9 100755 --- a/tests/Hashing/HasherTest.php +++ b/tests/Hashing/HasherTest.php @@ -51,6 +51,9 @@ public function testBasicArgon2idHashing() $this->assertSame('argon2id', password_get_info($value)['algoName']); } + /** + * @depends testBasicBcryptHashing + */ public function testBasicBcryptVerification() { $this->expectException(RuntimeException::class); @@ -64,6 +67,9 @@ public function testBasicBcryptVerification() (new BcryptHasher(['verify' => true]))->check('password', $argonHashed); } + /** + * @depends testBasicArgon2iHashing + */ public function testBasicArgon2iVerification() { $this->expectException(RuntimeException::class); @@ -73,6 +79,9 @@ public function testBasicArgon2iVerification() (new ArgonHasher(['verify' => true]))->check('password', $bcryptHashed); } + /** + * @depends testBasicArgon2idHashing + */ public function testBasicArgon2idVerification() { $this->expectException(RuntimeException::class); diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index b4a6d79de663..6a421d5ef6c7 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -427,4 +427,47 @@ public function testCanConfirmManyHeadersUsingAString() $request->hasHeaders('X-Test-Header'); }); } + + public function testSinkToFile() + { + $this->factory->fakeSequence()->push('abc123'); + + $destination = __DIR__.'/fixtures/sunk.txt'; + + if (file_exists($destination)) { + unlink($destination); + } + + $this->factory->withOptions(['sink' => $destination])->get('https://example.com'); + + $this->assertFileExists($destination); + $this->assertSame('abc123', file_get_contents($destination)); + + unlink($destination); + } + + public function testSinkToResource() + { + $this->factory->fakeSequence()->push('abc123'); + + $resource = fopen('php://temp', 'w'); + + $this->factory->sink($resource)->get('https://example.com'); + + $this->assertSame(0, ftell($resource)); + $this->assertSame('abc123', stream_get_contents($resource)); + } + + public function testSinkWhenStubbedByPath() + { + $this->factory->fake([ + 'foo.com/*' => ['page' => 'foo'], + ]); + + $resource = fopen('php://temp', 'w'); + + $this->factory->sink($resource)->get('http://foo.com/test'); + + $this->assertSame(json_encode(['page' => 'foo']), stream_get_contents($resource)); + } } diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 2d19a05e0cc6..0136857fc2fd 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -300,6 +300,62 @@ public function testHasMethod() $this->assertTrue($request->has('foo.baz')); } + public function testWhenHasMethod() + { + $request = Request::create('/', 'GET', ['name' => 'Taylor', 'age' => '', 'city' => null]); + + $name = $age = $city = $foo = false; + + $request->whenHas('name', function ($value) use (&$name) { + $name = $value; + }); + + $request->whenHas('age', function ($value) use (&$age) { + $age = $value; + }); + + $request->whenHas('city', function ($value) use (&$city) { + $city = $value; + }); + + $request->whenHas('foo', function () use (&$foo) { + $foo = 'test'; + }); + + $this->assertSame('Taylor', $name); + $this->assertSame('', $age); + $this->assertNull($city); + $this->assertFalse($foo); + } + + public function testWhenFilledMethod() + { + $request = Request::create('/', 'GET', ['name' => 'Taylor', 'age' => '', 'city' => null]); + + $name = $age = $city = $foo = false; + + $request->whenFilled('name', function ($value) use (&$name) { + $name = $value; + }); + + $request->whenFilled('age', function ($value) use (&$age) { + $age = 'test'; + }); + + $request->whenFilled('city', function ($value) use (&$city) { + $city = 'test'; + }); + + $request->whenFilled('foo', function () use (&$foo) { + $foo = 'test'; + }); + + $this->assertSame('Taylor', $name); + $this->assertFalse($age); + $this->assertFalse($city); + $this->assertFalse($foo); + } + public function testMissingMethod() { $request = Request::create('/', 'GET', ['name' => 'Taylor', 'age' => '', 'city' => null]); @@ -368,6 +424,23 @@ public function testFilledMethod() $this->assertTrue($request->filled('foo.bar')); } + public function testIsNotFilledMethod() + { + $request = Request::create('/', 'GET', ['name' => 'Taylor', 'age' => '', 'city' => null]); + $this->assertFalse($request->isNotFilled('name')); + $this->assertTrue($request->isNotFilled('age')); + $this->assertTrue($request->isNotFilled('city')); + $this->assertTrue($request->isNotFilled('foo')); + $this->assertFalse($request->isNotFilled(['name', 'email'])); + $this->assertTrue($request->isNotFilled(['foo', 'age'])); + $this->assertTrue($request->isNotFilled(['age', 'city'])); + + $request = Request::create('/', 'GET', ['foo' => ['bar', 'baz' => '0']]); + $this->assertFalse($request->isNotFilled('foo')); + $this->assertTrue($request->isNotFilled('foo.bar')); + $this->assertFalse($request->isNotFilled('foo.baz')); + } + public function testFilledAnyMethod() { $request = Request::create('/', 'GET', ['name' => 'Taylor', 'age' => '', 'city' => null]); diff --git a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php index 9966fbea6d5d..cf6c6cc5246f 100644 --- a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php +++ b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php @@ -8,6 +8,9 @@ use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +/** + * @requires extension pdo_mysql + */ class ApiAuthenticationWithEloquentTest extends TestCase { protected function getEnvironmentSetUp($app) diff --git a/tests/Integration/Database/DatabaseEmulatePreparesMySqlConnectionTest.php b/tests/Integration/Database/DatabaseEmulatePreparesMySqlConnectionTest.php index 65e71ebdb908..bd71a5865c29 100755 --- a/tests/Integration/Database/DatabaseEmulatePreparesMySqlConnectionTest.php +++ b/tests/Integration/Database/DatabaseEmulatePreparesMySqlConnectionTest.php @@ -4,6 +4,9 @@ use PDO; +/** + * @requires extension pdo_mysql + */ class DatabaseEmulatePreparesMySqlConnectionTest extends DatabaseMySqlConnectionTest { protected function getEnvironmentSetUp($app) diff --git a/tests/Integration/Database/DatabaseMySqlConnectionTest.php b/tests/Integration/Database/DatabaseMySqlConnectionTest.php index a2d73afc0d27..f511de027d78 100644 --- a/tests/Integration/Database/DatabaseMySqlConnectionTest.php +++ b/tests/Integration/Database/DatabaseMySqlConnectionTest.php @@ -6,6 +6,9 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +/** + * @requires extension pdo_mysql + */ class DatabaseMySqlConnectionTest extends DatabaseMySqlTestCase { const TABLE = 'player'; diff --git a/tests/Integration/Database/EloquentModelTest.php b/tests/Integration/Database/EloquentModelTest.php index 751e280b9ddf..f05e51944f25 100644 --- a/tests/Integration/Database/EloquentModelTest.php +++ b/tests/Integration/Database/EloquentModelTest.php @@ -29,6 +29,33 @@ protected function setUp(): void }); } + public function testCantUpdateGuardedAttributesUsingDifferentCasing() + { + $model = new TestModel2; + + $model->fill(['ID' => 123]); + + $this->assertNull($model->ID); + } + + public function testCantUpdateGuardedAttributeUsingJson() + { + $model = new TestModel2; + + $model->fill(['id->foo' => 123]); + + $this->assertNull($model->id); + } + + public function testCantMassFillAttributesWithTableNamesWhenUsingGuarded() + { + $model = new TestModel2; + + $model->fill(['foo.bar' => 123]); + + $this->assertCount(0, $model->getAttributes()); + } + public function testUserCanUpdateNullableDate() { $user = TestModel1::create([ diff --git a/tests/Integration/Notifications/Fixtures/html.blade.php b/tests/Integration/Notifications/Fixtures/html.blade.php new file mode 100644 index 000000000000..7a63f586fe3a --- /dev/null +++ b/tests/Integration/Notifications/Fixtures/html.blade.php @@ -0,0 +1 @@ +htmlContent diff --git a/tests/Integration/Notifications/Fixtures/plain.blade.php b/tests/Integration/Notifications/Fixtures/plain.blade.php new file mode 100644 index 000000000000..93198efc1290 --- /dev/null +++ b/tests/Integration/Notifications/Fixtures/plain.blade.php @@ -0,0 +1 @@ +plainContent diff --git a/tests/Integration/Notifications/SendingMailNotificationsTest.php b/tests/Integration/Notifications/SendingMailNotificationsTest.php index 9e68c6fffbc2..cd46217c373a 100644 --- a/tests/Integration/Notifications/SendingMailNotificationsTest.php +++ b/tests/Integration/Notifications/SendingMailNotificationsTest.php @@ -14,6 +14,7 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\View; use Illuminate\Support\Str; use Mockery as m; use Orchestra\Testbench\TestCase; @@ -61,6 +62,8 @@ protected function getEnvironmentSetUp($app) $app->extend(MailFactory::class, function () { return $this->mailFactory; }); + + View::addLocation(__DIR__.'/Fixtures'); } protected function setUp(): void @@ -245,6 +248,102 @@ public function testMailIsSentUsingMailable() $user->notify($notification); } + + public function testMailIsSentUsingMailMessageWithHtmlAndPlain() + { + $notification = new TestMailNotificationWithHtmlAndPlain; + $notification->id = Str::uuid()->toString(); + + $user = NotifiableUser::forceCreate([ + 'email' => 'taylor@laravel.com', + ]); + + $this->mailer->shouldReceive('send')->once()->with( + ['html', 'plain'], + array_merge($notification->toMail($user)->toArray(), [ + '__laravel_notification_id' => $notification->id, + '__laravel_notification' => get_class($notification), + '__laravel_notification_queued' => false, + ]), + m::on(function ($closure) { + $message = m::mock(Message::class); + + $message->shouldReceive('to')->once()->with(['taylor@laravel.com']); + + $message->shouldReceive('subject')->once()->with('Test Mail Notification With Html And Plain'); + + $closure($message); + + return true; + }) + ); + + $user->notify($notification); + } + + public function testMailIsSentUsingMailMessageWithHtmlOnly() + { + $notification = new TestMailNotificationWithHtmlOnly; + $notification->id = Str::uuid()->toString(); + + $user = NotifiableUser::forceCreate([ + 'email' => 'taylor@laravel.com', + ]); + + $this->mailer->shouldReceive('send')->once()->with( + 'html', + array_merge($notification->toMail($user)->toArray(), [ + '__laravel_notification_id' => $notification->id, + '__laravel_notification' => get_class($notification), + '__laravel_notification_queued' => false, + ]), + m::on(function ($closure) { + $message = m::mock(Message::class); + + $message->shouldReceive('to')->once()->with(['taylor@laravel.com']); + + $message->shouldReceive('subject')->once()->with('Test Mail Notification With Html Only'); + + $closure($message); + + return true; + }) + ); + + $user->notify($notification); + } + + public function testMailIsSentUsingMailMessageWithPlainOnly() + { + $notification = new TestMailNotificationWithPlainOnly; + $notification->id = Str::uuid()->toString(); + + $user = NotifiableUser::forceCreate([ + 'email' => 'taylor@laravel.com', + ]); + + $this->mailer->shouldReceive('send')->once()->with( + [null, 'plain'], + array_merge($notification->toMail($user)->toArray(), [ + '__laravel_notification_id' => $notification->id, + '__laravel_notification' => get_class($notification), + '__laravel_notification_queued' => false, + ]), + m::on(function ($closure) { + $message = m::mock(Message::class); + + $message->shouldReceive('to')->once()->with(['taylor@laravel.com']); + + $message->shouldReceive('subject')->once()->with('Test Mail Notification With Plain Only'); + + $closure($message); + + return true; + }) + ); + + $user->notify($notification); + } } class NotifiableUser extends Model @@ -328,3 +427,45 @@ public function toMail($notifiable) return $mailable; } } + +class TestMailNotificationWithHtmlAndPlain extends Notification +{ + public function via($notifiable) + { + return [MailChannel::class]; + } + + public function toMail($notifiable) + { + return (new MailMessage) + ->view(['html', 'plain']); + } +} + +class TestMailNotificationWithHtmlOnly extends Notification +{ + public function via($notifiable) + { + return [MailChannel::class]; + } + + public function toMail($notifiable) + { + return (new MailMessage) + ->view('html'); + } +} + +class TestMailNotificationWithPlainOnly extends Notification +{ + public function via($notifiable) + { + return [MailChannel::class]; + } + + public function toMail($notifiable) + { + return (new MailMessage) + ->view([null, 'plain']); + } +} diff --git a/tests/Integration/Queue/typed-properties.php b/tests/Integration/Queue/typed-properties.php index 17f655ab5f82..ba4666c9d3c9 100644 --- a/tests/Integration/Queue/typed-properties.php +++ b/tests/Integration/Queue/typed-properties.php @@ -11,6 +11,8 @@ class TypedPropertyTestClass public ModelSerializationTestUser $user; + public ModelSerializationTestUser $unitializedUser; + protected int $id; private array $names; diff --git a/tests/Mail/MailMailableTest.php b/tests/Mail/MailMailableTest.php index c2a9f40cb25c..73eff0500c20 100644 --- a/tests/Mail/MailMailableTest.php +++ b/tests/Mail/MailMailableTest.php @@ -54,6 +54,118 @@ public function testMailableSetsRecipientsCorrectly() $this->assertTrue($mailable->hasTo('taylor@laravel.com')); } + public function testMailableSetsCcRecipientsCorrectly() + { + $mailable = new WelcomeMailableStub; + $mailable->cc('taylor@laravel.com'); + $this->assertEquals([['name' => null, 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc('taylor@laravel.com', 'Taylor Otwell'); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc('taylor@laravel.com', 'Taylor Otwell')); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc(['taylor@laravel.com']); + $this->assertEquals([['name' => null, 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + $this->assertFalse($mailable->hasCc('taylor@laravel.com', 'Taylor Otwell')); + + $mailable = new WelcomeMailableStub; + $mailable->cc([['name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com']]); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc('taylor@laravel.com', 'Taylor Otwell')); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc(new MailableTestUserStub); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc(collect([new MailableTestUserStub])); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->cc); + $this->assertTrue($mailable->hasCc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc(collect([new MailableTestUserStub, new MailableTestUserStub])); + $this->assertEquals([ + ['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com'], + ], $mailable->cc); + $this->assertTrue($mailable->hasCc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->cc(['taylor@laravel.com', 'not-taylor@laravel.com']); + $this->assertEquals([ + ['name' => null, 'address' =>'taylor@laravel.com'], + ['name' => null, 'address' =>'not-taylor@laravel.com'], + ], $mailable->cc); + $this->assertTrue($mailable->hasCc('taylor@laravel.com')); + $this->assertTrue($mailable->hasCc('not-taylor@laravel.com')); + } + + public function testMailableSetsBccRecipientsCorrectly() + { + $mailable = new WelcomeMailableStub; + $mailable->bcc('taylor@laravel.com'); + $this->assertEquals([['name' => null, 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc('taylor@laravel.com', 'Taylor Otwell'); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com', 'Taylor Otwell')); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc(['taylor@laravel.com']); + $this->assertEquals([['name' => null, 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + $this->assertFalse($mailable->hasBcc('taylor@laravel.com', 'Taylor Otwell')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc([['name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com']]); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com', 'Taylor Otwell')); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc(new MailableTestUserStub); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc(collect([new MailableTestUserStub])); + $this->assertEquals([['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com']], $mailable->bcc); + $this->assertTrue($mailable->hasBcc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc(collect([new MailableTestUserStub, new MailableTestUserStub])); + $this->assertEquals([ + ['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell', 'address' => 'taylor@laravel.com'], + ], $mailable->bcc); + $this->assertTrue($mailable->hasBcc(new MailableTestUserStub)); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + + $mailable = new WelcomeMailableStub; + $mailable->bcc(['taylor@laravel.com', 'not-taylor@laravel.com']); + $this->assertEquals([ + ['name' => null, 'address' =>'taylor@laravel.com'], + ['name' => null, 'address' =>'not-taylor@laravel.com'], + ], $mailable->bcc); + $this->assertTrue($mailable->hasBcc('taylor@laravel.com')); + $this->assertTrue($mailable->hasBcc('not-taylor@laravel.com')); + } + public function testMailableSetsReplyToCorrectly() { $mailable = new WelcomeMailableStub; diff --git a/tests/Mail/MailSesTransportTest.php b/tests/Mail/MailSesTransportTest.php index b59a20c17a0a..3e32c55d3646 100644 --- a/tests/Mail/MailSesTransportTest.php +++ b/tests/Mail/MailSesTransportTest.php @@ -33,7 +33,6 @@ public function testGetTransport() /** @var \Illuminate\Mail\Transport\SesTransport $transport */ $transport = $manager->createTransport(['transport' => 'ses']); - /** @var \Aws\Ses\SesClient $ses */ $ses = $transport->ses(); $this->assertSame('us-east-1', $ses->getRegion()); diff --git a/tests/Notifications/NotificationMailMessageTest.php b/tests/Notifications/NotificationMailMessageTest.php index bcd8b0cc3de6..87c5e3f46558 100644 --- a/tests/Notifications/NotificationMailMessageTest.php +++ b/tests/Notifications/NotificationMailMessageTest.php @@ -18,6 +18,52 @@ public function testTemplate() $this->assertSame('notifications::foo', $message->markdown); } + public function testHtmlAndPlainView() + { + $message = new MailMessage; + + $this->assertNull($message->view); + $this->assertSame([], $message->viewData); + + $message->view(['notifications::foo', 'notifications::bar'], [ + 'foo' => 'bar', + ]); + + $this->assertSame('notifications::foo', $message->view[0]); + $this->assertSame('notifications::bar', $message->view[1]); + $this->assertSame(['foo' => 'bar'], $message->viewData); + } + + public function testHtmlView() + { + $message = new MailMessage; + + $this->assertNull($message->view); + $this->assertSame([], $message->viewData); + + $message->view('notifications::foo', [ + 'foo' => 'bar', + ]); + + $this->assertSame('notifications::foo', $message->view); + $this->assertSame(['foo' => 'bar'], $message->viewData); + } + + public function testPlainView() + { + $message = new MailMessage; + + $this->assertNull($message->view); + $this->assertSame([], $message->viewData); + + $message->view([null, 'notifications::foo'], [ + 'foo' => 'bar', + ]); + + $this->assertSame('notifications::foo', $message->view[1]); + $this->assertSame(['foo' => 'bar'], $message->viewData); + } + public function testCcIsSetCorrectly() { $message = new MailMessage; diff --git a/tests/Notifications/NotificationSendQueuedNotificationTest.php b/tests/Notifications/NotificationSendQueuedNotificationTest.php index bff5767475db..bdd1f5e197a6 100644 --- a/tests/Notifications/NotificationSendQueuedNotificationTest.php +++ b/tests/Notifications/NotificationSendQueuedNotificationTest.php @@ -3,11 +3,12 @@ namespace Illuminate\Tests\Notifications; use Illuminate\Contracts\Database\ModelIdentifier; +use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Notifications\ChannelManager; +use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\SendQueuedNotifications; use Illuminate\Support\Collection; -use Illuminate\Tests\Integration\Notifications\NotifiableUser; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -52,3 +53,11 @@ public function testSerializationOfNormalNotifiable() $this->assertStringContainsString($serializedNotifiable, $serialized); } } + +class NotifiableUser extends Model +{ + use Notifiable; + + public $table = 'users'; + public $timestamps = false; +} diff --git a/tests/Queue/DynamoDbFailedJobProviderTest.php b/tests/Queue/DynamoDbFailedJobProviderTest.php index 490812996b29..b292d024da04 100644 --- a/tests/Queue/DynamoDbFailedJobProviderTest.php +++ b/tests/Queue/DynamoDbFailedJobProviderTest.php @@ -144,8 +144,6 @@ public function testNullIsReturnedIfJobNotFound() { $dynamoDbClient = m::mock(DynamoDbClient::class); - $time = time(); - $dynamoDbClient->shouldReceive('getItem')->once()->with([ 'TableName' => 'table', 'Key' => [ @@ -165,8 +163,6 @@ public function testJobsCanBeDeleted() { $dynamoDbClient = m::mock(DynamoDbClient::class); - $time = time(); - $dynamoDbClient->shouldReceive('deleteItem')->once()->with([ 'TableName' => 'table', 'Key' => [ diff --git a/tests/Queue/RedisQueueIntegrationTest.php b/tests/Queue/RedisQueueIntegrationTest.php index 0be104151669..3843c2630e01 100644 --- a/tests/Queue/RedisQueueIntegrationTest.php +++ b/tests/Queue/RedisQueueIntegrationTest.php @@ -91,7 +91,7 @@ public function testBlockingPop($driver) $this->setQueue('phpredis'); sleep(1); $this->queue->push(new RedisQueueIntegrationTestJob(12)); - die; + exit; } else { $this->fail('Cannot fork'); } diff --git a/tests/Redis/RedisConnectorTest.php b/tests/Redis/RedisConnectorTest.php new file mode 100644 index 000000000000..599fa2f2aad3 --- /dev/null +++ b/tests/Redis/RedisConnectorTest.php @@ -0,0 +1,163 @@ +setUpRedis(); + } + + protected function tearDown(): void + { + parent::tearDown(); + + $this->tearDownRedis(); + + m::close(); + } + + public function testDefaultConfiguration() + { + $host = env('REDIS_HOST', '127.0.0.1'); + $port = env('REDIS_PORT', 6379); + + $predisClient = $this->redis['predis']->connection()->client(); + $parameters = $predisClient->getConnection()->getParameters(); + $this->assertEquals('tcp', $parameters->scheme); + $this->assertEquals($host, $parameters->host); + $this->assertEquals($port, $parameters->port); + + $phpRedisClient = $this->redis['phpredis']->connection()->client(); + $this->assertEquals($host, $phpRedisClient->getHost()); + $this->assertEquals($port, $phpRedisClient->getPort()); + } + + public function testUrl() + { + $host = env('REDIS_HOST', '127.0.0.1'); + $port = env('REDIS_PORT', 6379); + + $predis = new RedisManager(new Application, 'predis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'url' => "redis://{$host}:{$port}", + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $predisClient = $predis->connection()->client(); + $parameters = $predisClient->getConnection()->getParameters(); + $this->assertEquals('tcp', $parameters->scheme); + $this->assertEquals($host, $parameters->host); + $this->assertEquals($port, $parameters->port); + + $phpRedis = new RedisManager(new Application, 'phpredis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'url' => "redis://{$host}:{$port}", + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $phpRedisClient = $phpRedis->connection()->client(); + $this->assertEquals("tcp://{$host}", $phpRedisClient->getHost()); + $this->assertEquals($port, $phpRedisClient->getPort()); + } + + public function testUrlWithScheme() + { + $host = env('REDIS_HOST', '127.0.0.1'); + $port = env('REDIS_PORT', 6379); + + $predis = new RedisManager(new Application, 'predis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'url' => "tls://{$host}:{$port}", + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $predisClient = $predis->connection()->client(); + $parameters = $predisClient->getConnection()->getParameters(); + $this->assertEquals('tls', $parameters->scheme); + $this->assertEquals($host, $parameters->host); + $this->assertEquals($port, $parameters->port); + + $phpRedis = new RedisManager(new Application, 'phpredis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'url' => "tcp://{$host}:{$port}", + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $phpRedisClient = $phpRedis->connection()->client(); + $this->assertEquals("tcp://{$host}", $phpRedisClient->getHost()); + $this->assertEquals($port, $phpRedisClient->getPort()); + } + + public function testScheme() + { + $host = env('REDIS_HOST', '127.0.0.1'); + $port = env('REDIS_PORT', 6379); + + $predis = new RedisManager(new Application, 'predis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'scheme' => 'tls', + 'host' => $host, + 'port' => $port, + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $predisClient = $predis->connection()->client(); + $parameters = $predisClient->getConnection()->getParameters(); + $this->assertEquals('tls', $parameters->scheme); + $this->assertEquals($host, $parameters->host); + $this->assertEquals($port, $parameters->port); + + $phpRedis = new RedisManager(new Application, 'phpredis', [ + 'cluster' => false, + 'options' => [ + 'prefix' => 'test_', + ], + 'default' => [ + 'scheme' => 'tcp', + 'host' => $host, + 'port' => $port, + 'database' => 5, + 'timeout' => 0.5, + ], + ]); + $phpRedisClient = $phpRedis->connection()->client(); + $this->assertEquals("tcp://{$host}", $phpRedisClient->getHost()); + $this->assertEquals($port, $phpRedisClient->getPort()); + } +} diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index f055fbc4d42b..a6bc297aa40d 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -140,6 +140,15 @@ public function testCanRegisterRouteWithControllerAction() $this->seeMiddleware('controller-middleware'); } + public function testCanRegisterRouteWithControllerActionArray() + { + $this->router->middleware('controller-middleware') + ->get('users', [RouteRegistrarControllerStub::class, 'index']); + + $this->seeResponse('controller', Request::create('users', 'GET')); + $this->seeMiddleware('controller-middleware'); + } + public function testCanRegisterRouteWithArrayAndControllerAction() { $this->router->middleware('controller-middleware')->put('users', [ @@ -357,6 +366,31 @@ public function testCanSetShallowOptionOnRegisteredResource() $this->assertFalse($this->router->getRoutes()->hasNamedRoute('users.tasks.show')); } + public function testCanSetScopedOptionOnRegisteredResource() + { + $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped(); + $this->assertSame( + ['user' => null], + $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() + ); + $this->assertSame( + ['user' => null, 'task' => null], + $this->router->getRoutes()->getByName('users.tasks.show')->bindingFields() + ); + + $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped([ + 'task' => 'slug', + ]); + $this->assertSame( + ['user' => null], + $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() + ); + $this->assertSame( + ['user' => null, 'task' => 'slug'], + $this->router->getRoutes()->getByName('users.tasks.show')->bindingFields() + ); + } + public function testCanExcludeMethodsOnRegisteredApiResource() { $this->router->apiResource('users', RouteRegistrarControllerStub::class) @@ -525,6 +559,22 @@ public function testResourceWithoutMiddlewareRegistration() $this->assertEquals(['one'], $this->getRoute()->excludedMiddleware()); } + public function testResourceWheres() + { + $wheres = [ + 'user' => '\d+', + 'test' => '[a-z]+', + ]; + + $this->router->resource('users', RouteRegistrarControllerStub::class) + ->where($wheres); + + /** @var \Illuminate\Routing\Route $route */ + foreach ($this->router->getRoutes() as $route) { + $this->assertEquals($wheres, $route->wheres); + } + } + public function testCanSetRouteName() { $this->router->as('users.index')->get('users', function () { diff --git a/tests/Support/ConfigurationUrlParserTest.php b/tests/Support/ConfigurationUrlParserTest.php index 5a23513f5b5b..e4cfa0bb0a73 100644 --- a/tests/Support/ConfigurationUrlParserTest.php +++ b/tests/Support/ConfigurationUrlParserTest.php @@ -23,6 +23,8 @@ public function testDriversAliases() 'postgres' => 'pgsql', 'postgresql' => 'pgsql', 'sqlite3' => 'sqlite', + 'redis' => 'tcp', + 'rediss' => 'tls', ], ConfigurationUrlParser::getDriverAliases()); ConfigurationUrlParser::addDriverAlias('some-particular-alias', 'mysql'); @@ -33,6 +35,8 @@ public function testDriversAliases() 'postgres' => 'pgsql', 'postgresql' => 'pgsql', 'sqlite3' => 'sqlite', + 'redis' => 'tcp', + 'rediss' => 'tls', 'some-particular-alias' => 'mysql', ], ConfigurationUrlParser::getDriverAliases()); @@ -355,7 +359,7 @@ public function databaseUrls() 'database' => 0, ], [ - 'driver' => 'redis', + 'driver' => 'tcp', 'host' => 'ec2-111-1-1-1.compute-1.amazonaws.com', 'port' => 111, 'database' => 0, @@ -367,12 +371,12 @@ public function databaseUrls() [ 'url' => 'redis://h:asdfqwer1234asdf@ec2-111-1-1-1.compute-1.amazonaws.com:111/', 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, + 'password' => null, + 'port' => 6379, 'database' => 2, ], [ - 'driver' => 'redis', + 'driver' => 'tcp', 'host' => 'ec2-111-1-1-1.compute-1.amazonaws.com', 'port' => 111, 'database' => 2, @@ -380,6 +384,40 @@ public function databaseUrls() 'password' => 'asdfqwer1234asdf', ], ], + 'Redis Example with tls scheme' => [ + [ + 'url' => 'tls://h:asdfqwer1234asdf@ec2-111-1-1-1.compute-1.amazonaws.com:111', + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], + [ + 'driver' => 'tls', + 'host' => 'ec2-111-1-1-1.compute-1.amazonaws.com', + 'port' => 111, + 'database' => 0, + 'username' => 'h', + 'password' => 'asdfqwer1234asdf', + ], + ], + 'Redis Example with rediss scheme' => [ + [ + 'url' => 'rediss://h:asdfqwer1234asdf@ec2-111-1-1-1.compute-1.amazonaws.com:111', + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], + [ + 'driver' => 'tls', + 'host' => 'ec2-111-1-1-1.compute-1.amazonaws.com', + 'port' => 111, + 'database' => 0, + 'username' => 'h', + 'password' => 'asdfqwer1234asdf', + ], + ], ]; } } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 158a4308d7b4..765561aecb17 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase; use ReflectionClass; use stdClass; +use Symfony\Component\VarDumper\VarDumper; class SupportCollectionTest extends TestCase { @@ -207,9 +208,11 @@ public function testSkipMethod($collection) { $data = new $collection([1, 2, 3, 4, 5, 6]); - $data = $data->skip(4)->values(); + // Total items to skip is smaller than collection length + $this->assertSame([5, 6], $data->skip(4)->values()->all()); - $this->assertSame([5, 6], $data->all()); + // Total items to skip is more than collection length + $this->assertSame([], $data->skip(10)->values()->all()); } /** @@ -219,15 +222,35 @@ public function testSkipUntil($collection) { $data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]); - $data = $data->skipUntil(3)->values(); + // Item at the beginning of the collection + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->skipUntil(1)->values()->all()); + + // Item at the middle of the collection + $this->assertSame([3, 3, 4, 4], $data->skipUntil(3)->values()->all()); + + // Item not in the collection + $this->assertSame([], $data->skipUntil(5)->values()->all()); + + // Item at the beginning of the collection + $data = $data->skipUntil(function ($value, $key) { + return $value <= 1; + })->values(); + + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->all()); + + // Item at the middle of the collection + $data = $data->skipUntil(function ($value, $key) { + return $value >= 3; + })->values(); $this->assertSame([3, 3, 4, 4], $data->all()); + // Item not in the collection $data = $data->skipUntil(function ($value, $key) { - return $value > 3; + return $value >= 5; })->values(); - $this->assertSame([4, 4], $data->all()); + $this->assertSame([], $data->all()); } /** @@ -237,10 +260,30 @@ public function testSkipWhile($collection) { $data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]); - $data = $data->skipWhile(1)->values(); + // Item at the beginning of the collection + $this->assertSame([2, 2, 3, 3, 4, 4], $data->skipWhile(1)->values()->all()); + + // Item not in the collection + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->skipWhile(5)->values()->all()); - $this->assertSame([2, 2, 3, 3, 4, 4], $data->all()); + // Item in the collection but not at the beginning + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->skipWhile(2)->values()->all()); + // Item not in the collection + $data = $data->skipWhile(function ($value, $key) { + return $value >= 5; + })->values(); + + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->all()); + + // Item in the collection but not at the beginning + $data = $data->skipWhile(function ($value, $key) { + return $value >= 2; + })->values(); + + $this->assertSame([1, 1, 2, 2, 3, 3, 4, 4], $data->all()); + + // Item at the beginning of the collection $data = $data->skipWhile(function ($value, $key) { return $value < 3; })->values(); @@ -436,7 +479,7 @@ public function testCountable($collection) /** * @dataProvider collectionClassProvider */ - public function testCountableByWithoutPredicate($collection) + public function testCountByStandalone($collection) { $c = new $collection(['foo', 'foo', 'foo', 'bar', 'bar', 'foobar']); $this->assertEquals(['foo' => 3, 'bar' => 2, 'foobar' => 1], $c->countBy()->all()); @@ -451,7 +494,19 @@ public function testCountableByWithoutPredicate($collection) /** * @dataProvider collectionClassProvider */ - public function testCountableByWithPredicate($collection) + public function testCountByWithKey($collection) + { + $c = new $collection([ + ['key' => 'a'], ['key' => 'a'], ['key' => 'a'], ['key' => 'a'], + ['key' => 'b'], ['key' => 'b'], ['key' => 'b'], + ]); + $this->assertEquals(['a' => 4, 'b' => 3], $c->countBy('key')->all()); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testCountableByWithCallback($collection) { $c = new $collection(['alice', 'aaron', 'bob', 'carla']); $this->assertEquals(['a' => 2, 'b' => 1, 'c' => 1], $c->countBy(function ($name) { @@ -3365,6 +3420,24 @@ public function testConcatWithCollection($collection) $this->assertSame($expected, $actual); } + /** + * @dataProvider collectionClassProvider + */ + public function testDump($collection) + { + $log = new Collection(); + + VarDumper::setHandler(function ($value) use ($log) { + $log->add($value); + }); + + (new $collection([1, 2, 3]))->dump('one', 'two'); + + $this->assertSame(['one', 'two', [1, 2, 3]], $log->all()); + + VarDumper::setHandler(null); + } + /** * @dataProvider collectionClassProvider */ diff --git a/tests/Support/SupportReflectorTest.php b/tests/Support/SupportReflectorTest.php index 55c4940f7543..df5b3e414e46 100644 --- a/tests/Support/SupportReflectorTest.php +++ b/tests/Support/SupportReflectorTest.php @@ -57,6 +57,20 @@ public function testUnionTypeName() $this->assertNull(Reflector::getParameterClassName($method->getParameters()[0])); } + + public function testIsCallable() + { + $this->assertTrue(Reflector::isCallable(function () { + })); + $this->assertTrue(Reflector::isCallable([B::class, 'f'])); + $this->assertFalse(Reflector::isCallable([TestClassWithCall::class, 'f'])); + $this->assertTrue(Reflector::isCallable([new TestClassWithCall, 'f'])); + $this->assertTrue(Reflector::isCallable([TestClassWithCallStatic::class, 'f'])); + $this->assertFalse(Reflector::isCallable([new TestClassWithCallStatic, 'f'])); + $this->assertFalse(Reflector::isCallable([new TestClassWithCallStatic])); + $this->assertFalse(Reflector::isCallable(['TotallyMissingClass', 'foo'])); + $this->assertTrue(Reflector::isCallable(['TotallyMissingClass', 'foo'], true)); + } } class A @@ -82,3 +96,17 @@ public function f(A|Model $x) }' ); } + +class TestClassWithCall +{ + public function __call($method, $parameters) + { + } +} + +class TestClassWithCallStatic +{ + public static function __callStatic($method, $parameters) + { + } +} diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 1bbcb4f54817..87bc3c0956c4 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -462,6 +462,24 @@ public function testAsciiNull() $this->assertSame('', Str::slug(null)); } + public function testPadBoth() + { + $this->assertSame('__Alien___', Str::padBoth('Alien', 10, '_')); + $this->assertSame(' Alien ', Str::padBoth('Alien', 10)); + } + + public function testPadLeft() + { + $this->assertSame('-=-=-Alien', Str::padLeft('Alien', 10, '-=')); + $this->assertSame(' Alien', Str::padLeft('Alien', 10)); + } + + public function testPadRight() + { + $this->assertSame('Alien-----', Str::padRight('Alien', 10, '-')); + $this->assertSame('Alien ', Str::padRight('Alien', 10)); + } + public function validUuidList() { return [ diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index d27f9ddf6d85..f742dfba6753 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -510,4 +510,22 @@ public function testSubstrCount() $this->assertSame(3, $this->stringable('laravelPHPFramework')->substrCount('a', 1, -2)); $this->assertSame(1, $this->stringable('laravelPHPFramework')->substrCount('a', -10, -3)); } + + public function testPadBoth() + { + $this->assertSame('__Alien___', (string) $this->stringable('Alien')->padBoth(10, '_')); + $this->assertSame(' Alien ', (string) $this->stringable('Alien')->padBoth(10)); + } + + public function testPadLeft() + { + $this->assertSame('-=-=-Alien', (string) $this->stringable('Alien')->padLeft(10, '-=')); + $this->assertSame(' Alien', (string) $this->stringable('Alien')->padLeft(10)); + } + + public function testPadRight() + { + $this->assertSame('Alien-----', (string) $this->stringable('Alien')->padRight(10, '-')); + $this->assertSame('Alien ', (string) $this->stringable('Alien')->padRight(10)); + } } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 3498c271119a..6886cd3ebc5b 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -78,6 +78,20 @@ public function testAssertViewHasWithValue() $response->assertViewHas('foo', 'bar'); } + public function testAssertViewHasNested() + { + $response = $this->makeMockResponse([ + 'render' => 'hello world', + 'gatherData' => [ + 'foo' => [ + 'nested' => 'bar', + ], + ], + ]); + + $response->assertViewHas('foo.nested'); + } + public function testAssertViewHasWithNestedValue() { $response = $this->makeMockResponse([ @@ -92,6 +106,30 @@ public function testAssertViewHasWithNestedValue() $response->assertViewHas('foo.nested', 'bar'); } + public function testAssertViewMissing() + { + $response = $this->makeMockResponse([ + 'render' => 'hello world', + 'gatherData' => ['foo' => 'bar'], + ]); + + $response->assertViewMissing('baz'); + } + + public function testAssertViewMissingNested() + { + $response = $this->makeMockResponse([ + 'render' => 'hello world', + 'gatherData' => [ + 'foo' => [ + 'nested' => 'bar', + ], + ], + ]); + + $response->assertViewMissing('foo.baz'); + } + public function testAssertSeeInOrder() { $response = $this->makeMockResponse([ @@ -573,6 +611,20 @@ public function testAssertJsonValidationErrorsCustomErrorsName() $testResponse->assertJsonValidationErrors('foo', 'data'); } + public function testAssertJsonValidationErrorsCustomNestedErrorsName() + { + $data = [ + 'status' => 'ok', + 'data' => ['errors' => ['foo' => 'oops']], + ]; + + $testResponse = TestResponse::fromBaseResponse( + (new Response)->setContent(json_encode($data)) + ); + + $testResponse->assertJsonValidationErrors('foo', 'data.errors'); + } + public function testAssertJsonValidationErrorsCanFail() { $this->expectException(AssertionFailedError::class); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 16cbd24c1e2d..12881c1fe373 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -4,6 +4,7 @@ use DateTime; use DateTimeImmutable; +use Egulias\EmailValidator\Validation\NoRFCWarningsValidation; use Illuminate\Container\Container; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Guard; @@ -1129,6 +1130,14 @@ public function testRequiredUnless() $v = new Validator($trans, ['first' => 'sven'], ['last' => 'required_unless:first,taylor,sven']); $this->assertTrue($v->passes()); + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => false], ['bar' => 'required_unless:foo,false']); + $this->assertTrue($v->passes()); + + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => false], ['bar' => 'required_unless:foo,true']); + $this->assertTrue($v->fails()); + // error message when passed multiple values (required_unless:foo,bar,baz) $trans = $this->getIlluminateArrayTranslator(); $trans->addLines(['validation.required_unless' => 'The :attribute field is required unless :other is in :values.'], 'en'); @@ -1280,6 +1289,9 @@ public function testGreaterThan() $v = new Validator($trans, ['lhs' => 15.0], ['lhs' => 'numeric|gt:10']); $this->assertTrue($v->passes()); + $v = new Validator($trans, ['lhs' => 5, 10 => 1], ['lhs' => 'numeric|gt:10']); + $this->assertTrue($v->fails()); + $v = new Validator($trans, ['lhs' => '15'], ['lhs' => 'numeric|gt:10']); $this->assertTrue($v->passes()); @@ -1541,6 +1553,10 @@ public function testValidateJson() $trans = $this->getIlluminateArrayTranslator(); $v = new Validator($trans, ['foo' => '{"name":"John","age":"34"}'], ['foo' => 'json']); $this->assertTrue($v->passes()); + + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => ['array']], ['foo' => 'json']); + $this->assertFalse($v->passes()); } public function testValidateBoolean() @@ -1818,14 +1834,14 @@ public function testValidateMax() $this->assertFalse($v->passes()); $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['isValid', 'getSize'])->setConstructorArgs([__FILE__, basename(__FILE__)])->getMock(); - $file->expects($this->any())->method('isValid')->willReturn(true); - $file->expects($this->at(1))->method('getSize')->willReturn(3072); + $file->method('isValid')->willReturn(true); + $file->method('getSize')->willReturn(3072); $v = new Validator($trans, ['photo' => $file], ['photo' => 'Max:10']); $this->assertTrue($v->passes()); $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['isValid', 'getSize'])->setConstructorArgs([__FILE__, basename(__FILE__)])->getMock(); - $file->expects($this->at(0))->method('isValid')->willReturn(true); - $file->expects($this->at(1))->method('getSize')->willReturn(4072); + $file->method('isValid')->willReturn(true); + $file->method('getSize')->willReturn(4072); $v = new Validator($trans, ['photo' => $file], ['photo' => 'Max:2']); $this->assertFalse($v->passes()); @@ -2411,6 +2427,17 @@ public function testValidateEmailWithFilterUnicodeCheck() $this->assertFalse($v->passes()); } + public function testValidateEmailWithCustomClassCheck() + { + $container = m::mock(Container::class); + $container->shouldReceive('make')->with(NoRFCWarningsValidation::class)->andReturn(new NoRFCWarningsValidation()); + + $v = new Validator($this->getIlluminateArrayTranslator(), ['x' => 'foo@bar '], ['x' => 'email:'.NoRFCWarningsValidation::class]); + $v->setContainer($container); + + $this->assertFalse($v->passes()); + } + /** * @dataProvider validUrls */ @@ -2722,43 +2749,49 @@ public function testValidateImage() $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file->expects($this->any())->method('guessExtension')->willReturn('php'); $file->expects($this->any())->method('getClientOriginalExtension')->willReturn('php'); - $v = new Validator($trans, ['x' => $file], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file], ['x' => 'image']); $this->assertFalse($v->passes()); $file2 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); - $file2->expects($this->any())->method('guessExtension')->willReturn('jpeg'); + $file2->expects($this->any())->method('guessExtension')->willReturn('jpg'); $file2->expects($this->any())->method('getClientOriginalExtension')->willReturn('jpeg'); - $v = new Validator($trans, ['x' => $file2], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file2], ['x' => 'image']); + $this->assertTrue($v->passes()); + + $file2 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); + $file2->expects($this->any())->method('guessExtension')->willReturn('jpg'); + $file2->expects($this->any())->method('getClientOriginalExtension')->willReturn('jpg'); + $v = new Validator($trans, ['x' => $file2], ['x' => 'image']); $this->assertTrue($v->passes()); $file3 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file3->expects($this->any())->method('guessExtension')->willReturn('gif'); $file3->expects($this->any())->method('getClientOriginalExtension')->willReturn('gif'); - $v = new Validator($trans, ['x' => $file3], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file3], ['x' => 'image']); $this->assertTrue($v->passes()); $file4 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file4->expects($this->any())->method('guessExtension')->willReturn('bmp'); $file4->expects($this->any())->method('getClientOriginalExtension')->willReturn('bmp'); - $v = new Validator($trans, ['x' => $file4], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file4], ['x' => 'image']); $this->assertTrue($v->passes()); $file5 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file5->expects($this->any())->method('guessExtension')->willReturn('png'); $file5->expects($this->any())->method('getClientOriginalExtension')->willReturn('png'); - $v = new Validator($trans, ['x' => $file5], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file5], ['x' => 'image']); $this->assertTrue($v->passes()); $file6 = $this->getMockBuilder(UploadedFile::class)->setMethods(['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']); + $v = new Validator($trans, ['x' => $file6], ['x' => 'image']); $this->assertTrue($v->passes()); $file7 = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file7->expects($this->any())->method('guessExtension')->willReturn('webp'); $file7->expects($this->any())->method('getClientOriginalExtension')->willReturn('webp'); - $v = new Validator($trans, ['x' => $file7], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file7], ['x' => 'image']); $this->assertTrue($v->passes()); } @@ -2770,7 +2803,7 @@ public function testValidateImageDoesNotAllowPhpExtensionsOnImageMime() $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); $file->expects($this->any())->method('guessExtension')->willReturn('jpeg'); $file->expects($this->any())->method('getClientOriginalExtension')->willReturn('php'); - $v = new Validator($trans, ['x' => $file], ['x' => 'Image']); + $v = new Validator($trans, ['x' => $file], ['x' => 'image']); $this->assertFalse($v->passes()); } @@ -2873,22 +2906,36 @@ public function testValidateImageDimensions() $v = new Validator($trans, ['x' => $svgFile], ['x' => 'dimensions:max_width=1,max_height=1']); $this->assertTrue($v->passes()); + + // Knowing that demo image4.png has width = 64 and height = 65 + $uploadedFile = new UploadedFile(__DIR__.'/fixtures/image4.png', '', null, null, true); + $trans = $this->getIlluminateArrayTranslator(); + + // Ensure validation doesn't erroneously fail when ratio doesn't matches + $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:ratio=1']); + $this->assertFalse($v->passes()); } - /** - * @requires extension fileinfo - */ - public function testValidatePhpMimetypes() + public function testValidateMimetypes() { $trans = $this->getIlluminateArrayTranslator(); - $uploadedFile = [__DIR__.'/ValidationRuleTest.php', '', null, null, true]; - $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); - $file->expects($this->any())->method('guessExtension')->willReturn('rtf'); - $file->expects($this->any())->method('getClientOriginalExtension')->willReturn('rtf'); + $uploadedFile = [__FILE__, '', null, null, true]; + $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['getMimeType'])->setConstructorArgs($uploadedFile)->getMock(); + $file->expects($this->any())->method('getMimeType')->willReturn('text/rtf'); $v = new Validator($trans, ['x' => $file], ['x' => 'mimetypes:text/*']); $this->assertTrue($v->passes()); + + $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['getMimeType'])->setConstructorArgs($uploadedFile)->getMock(); + $file->expects($this->any())->method('getMimeType')->willReturn('application/pdf'); + $v = new Validator($trans, ['x' => $file], ['x' => 'mimetypes:text/rtf']); + $this->assertFalse($v->passes()); + + $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['getMimeType'])->setConstructorArgs($uploadedFile)->getMock(); + $file->expects($this->any())->method('getMimeType')->willReturn('image/jpeg'); + $v = new Validator($trans, ['x' => $file], ['x' => 'mimetypes:image/jpeg']); + $this->assertTrue($v->passes()); } public function testValidateMime() @@ -2907,6 +2954,18 @@ public function testValidateMime() $file2->expects($this->any())->method('isValid')->willReturn(false); $v = new Validator($trans, ['x' => $file2], ['x' => 'mimes:pdf']); $this->assertFalse($v->passes()); + + $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); + $file->expects($this->any())->method('guessExtension')->willReturn('jpg'); + $file->expects($this->any())->method('getClientOriginalExtension')->willReturn('jpg'); + $v = new Validator($trans, ['x' => $file], ['x' => 'mimes:jpeg']); + $this->assertTrue($v->passes()); + + $file = $this->getMockBuilder(UploadedFile::class)->setMethods(['guessExtension', 'getClientOriginalExtension'])->setConstructorArgs($uploadedFile)->getMock(); + $file->expects($this->any())->method('guessExtension')->willReturn('jpg'); + $file->expects($this->any())->method('getClientOriginalExtension')->willReturn('jpeg'); + $v = new Validator($trans, ['x' => $file], ['x' => 'mimes:jpg']); + $this->assertTrue($v->passes()); } public function testValidateMimeEnforcesPhpCheck() @@ -3930,6 +3989,15 @@ public function testPlaceholdersAreReplaced() 'foo\.bar' => 'required|in:valid', ]); $this->assertTrue($v->fails()); + $this->assertArrayHasKey('foo.bar', $v->errors()->getMessages()); + + $v = new Validator($trans, [ + 'foo.bar' => 'valid', + ], [ + 'foo\.bar' => 'required|in:valid', + ]); + $this->assertTrue($v->passes()); + $this->assertArrayHasKey('foo.bar', $v->validated()); } public function testCoveringEmptyKeys() @@ -4892,6 +4960,51 @@ public function message() $this->assertSame('validation.string', $v->errors()->get('name')[2]); } + public function testCustomValidationObjectWithDotKeysIsCorrectlyPassedValue() + { + $v = new Validator( + $this->getIlluminateArrayTranslator(), + ['foo' => ['foo.bar' => 'baz']], + [ + 'foo' => new class implements Rule { + public function passes($attribute, $value) + { + return $value === ['foo.bar' => 'baz']; + } + + public function message() + { + return ':attribute must be baz'; + } + }, + ] + ); + + $this->assertTrue($v->passes()); + + // Test failed attributes contains proper entries + $v = new Validator( + $this->getIlluminateArrayTranslator(), + ['foo' => ['foo.bar' => 'baz']], + [ + 'foo.foo\.bar' => new class implements Rule { + public function passes($attribute, $value) + { + return false; + } + + public function message() + { + return ':attribute must be baz'; + } + }, + ] + ); + + $this->assertFalse($v->passes()); + $this->assertTrue(is_array($v->failed()['foo.foo.bar'])); + } + public function testImplicitCustomValidationObjects() { // Test passing case... diff --git a/tests/Validation/fixtures/image4.png b/tests/Validation/fixtures/image4.png new file mode 100644 index 000000000000..e7b5e4665fe6 Binary files /dev/null and b/tests/Validation/fixtures/image4.png differ diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 4ed6bd89ac4b..8b123ba5e0ea 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -26,6 +26,14 @@ public function testSlotsCanBeCompiled() $this->assertSame("@slot('foo') \n".' @endslot', trim($result)); } + public function testDynamicSlotsCanBeCompiled() + { + $result = $this->compiler()->compileSlots(' +'); + + $this->assertSame("@slot(\$foo) \n".' @endslot', trim($result)); + } + public function testBasicComponentParsing() { $this->mockViewFactory(); @@ -197,6 +205,22 @@ public function testClasslessComponents() '@endcomponentClass', trim($result)); } + public function testPackagesClasslessComponents() + { + $container = new Container; + $container->instance(Application::class, $app = Mockery::mock(Application::class)); + $container->instance(Factory::class, $factory = Mockery::mock(Factory::class)); + $app->shouldReceive('getNamespace')->andReturn('App\\'); + $factory->shouldReceive('exists')->andReturn(true); + Container::setInstance($container); + + $result = $this->compiler()->compileTags(''); + + $this->assertSame("@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +'@endcomponentClass', trim($result)); + } + public function testAttributeSanitization() { $class = new class { diff --git a/tests/View/Blade/BladeEnvironmentStatementsTest.php b/tests/View/Blade/BladeEnvironmentStatementsTest.php index 1e86d870dec3..866b9e4c40db 100644 --- a/tests/View/Blade/BladeEnvironmentStatementsTest.php +++ b/tests/View/Blade/BladeEnvironmentStatementsTest.php @@ -19,6 +19,36 @@ public function testEnvStatementsAreCompiled() $this->assertEquals($expected, $this->compiler->compileString($string)); } + public function testEnvStatementsWithMultipleStringParamsAreCompiled() + { + $string = "@env('staging', 'production') +breeze +@else +boom +@endenv"; + $expected = "environment('staging', 'production')): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEnvStatementsWithArrayParamAreCompiled() + { + $string = "@env(['staging', 'production']) +breeze +@else +boom +@endenv"; + $expected = "environment(['staging', 'production'])): ?> +breeze + +boom +"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + public function testProductionStatementsAreCompiled() { $string = '@production diff --git a/tests/View/Blade/BladeSectionMissingTest.php b/tests/View/Blade/BladeSectionMissingTest.php new file mode 100644 index 000000000000..fb600a1a3dbd --- /dev/null +++ b/tests/View/Blade/BladeSectionMissingTest.php @@ -0,0 +1,17 @@ +yieldContent("section")))): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php index 65240cb914a9..c91e74589e24 100644 --- a/tests/View/ViewComponentAttributeBagTest.php +++ b/tests/View/ViewComponentAttributeBagTest.php @@ -13,6 +13,8 @@ public function testAttributeRetrieval() $this->assertSame('class="font-bold"', (string) $bag->whereStartsWith('class')); $this->assertSame('font-bold', (string) $bag->whereStartsWith('class')->first()); + $this->assertSame('name="test"', (string) $bag->whereDoesntStartWith('class')); + $this->assertSame('test', (string) $bag->whereDoesntStartWith('class')->first()); $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->merge(['class' => 'mt-4'])); $this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->merge(['class' => 'mt-4', 'name' => 'foo'])); $this->assertSame('class="mt-4 font-bold" id="bar" name="test"', (string) $bag->merge(['class' => 'mt-4', 'id' => 'bar'])); diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php index 8ceeca0dfc5b..f05ca0682bd5 100644 --- a/tests/View/ViewComponentTest.php +++ b/tests/View/ViewComponentTest.php @@ -23,8 +23,10 @@ public function testPublicMethodsWithNoArgsAreConvertedToStringableCallablesInvo $component = new TestSampleViewComponent; $this->assertEquals(0, $component->counter); + $this->assertEquals(0, TestSampleViewComponent::$publicStaticCounter); $variables = $component->data(); $this->assertEquals(0, $component->counter); + $this->assertEquals(0, TestSampleViewComponent::$publicStaticCounter); $this->assertSame('noArgs val', $variables['noArgs']()); $this->assertSame('noArgs val', (string) $variables['noArgs']); @@ -36,6 +38,7 @@ public function testPublicMethodsWithNoArgsAreConvertedToStringableCallablesInvo $this->assertArrayNotHasKey('protectedHello', $variables); $this->assertArrayNotHasKey('privateHello', $variables); + $this->assertArrayNotHasKey('publicStaticCounter', $variables); $this->assertArrayNotHasKey('protectedCounter', $variables); $this->assertArrayNotHasKey('privateCounter', $variables); @@ -92,6 +95,8 @@ class TestSampleViewComponent extends Component { public $counter = 0; + public static $publicStaticCounter = 0; + protected $protectedCounter = 0; private $privateCounter = 0; diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index adca1d942771..bd8db37658e4 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -59,6 +59,16 @@ public function testExistsPassesAndFailsViews() $this->assertTrue($factory->exists('bar')); } + public function testRenderingOnceChecks() + { + $factory = $this->getFactory(); + $this->assertFalse($factory->hasRenderedOnce('foo')); + $factory->markAsRenderedOnce('foo'); + $this->assertTrue($factory->hasRenderedOnce('foo')); + $factory->flushState(); + $this->assertFalse($factory->hasRenderedOnce('foo')); + } + public function testFirstCreatesNewViewInstanceWithProperPath() { unset($_SERVER['__test.view']); @@ -442,6 +452,17 @@ public function testHasSection() $this->assertFalse($factory->hasSection('bar')); } + public function testSectionMissing() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hello world'; + $factory->stopSection(); + + $this->assertTrue($factory->sectionMissing('bar')); + $this->assertFalse($factory->sectionMissing('foo')); + } + public function testGetSection() { $factory = $this->getFactory();