diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cbf13408f42c..da9500c98394 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: - cron: '0 0 * * *' jobs: - tests: + linux_tests: runs-on: ubuntu-latest services: @@ -36,28 +36,63 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Cache dependencies - uses: actions/cache@v1 - with: - path: ~/.composer/cache/files - key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd + tools: composer:v2 coverage: none - name: Setup Memcached uses: niden/actions-memcached@v7 - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Execute tests run: vendor/bin/phpunit --verbose env: - CI: ${{ secrets.CI }} DB_PORT: ${{ job.services.mysql.ports[3306] }} 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 + + name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows + + steps: + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + 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 + tools: composer:v2 + coverage: none + ini-values: memory_limit=512M + + - name: Install dependencies + run: 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 b8ec281c1fcc..c0642379b166 100644 --- a/CHANGELOG-6.x.md +++ b/CHANGELOG-6.x.md @@ -1,6 +1,321 @@ # Release Notes for 6.x -## [Unreleased](https://github.com/laravel/framework/compare/v6.18.2...6.x) +## [Unreleased](https://github.com/laravel/framework/compare/v6.18.42...6.x) + + +## [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) + +### Fixed +- Fixed `Illuminate\Cache\FileStore::flush()` ([#33458](https://github.com/laravel/framework/pull/33458)) +- Fixed auto creating model by class name ([#33481](https://github.com/laravel/framework/pull/33481)) +- Don't return nested data from validator when failing an exclude rule ([#33435](https://github.com/laravel/framework/pull/33435)) +- Fixed validation nested error messages ([6615371](https://github.com/laravel/framework/commit/6615371d7c0a7431372244d21eae54696b3c19f2)) +- Fixed `Illuminate\Support\Reflector` to handle parent ([#33502](https://github.com/laravel/framework/pull/33502)) + +### Revert +- Revert [Improve SQL Server last insert id retrieval](https://github.com/laravel/framework/pull/33453) ([#33496](https://github.com/laravel/framework/pull/33496)) + + +## [v6.18.24 (2020-07-07)](https://github.com/laravel/framework/compare/v6.18.23...v6.18.24) + +### Fixed +- Fixed notifications database channel for anonymous notifiables ([#33409](https://github.com/laravel/framework/pull/33409)) +- Added float comparison null checks ([#33421](https://github.com/laravel/framework/pull/33421)) +- Improve SQL Server last insert id retrieval ([#33453](https://github.com/laravel/framework/pull/33453)) + + +## [v6.18.23 (2020-06-30)](https://github.com/laravel/framework/compare/v6.18.22...v6.18.23) + +### Fixed +- Fixed `ConfigurationUrlParser` query decoding ([#33340](https://github.com/laravel/framework/pull/33340)) +- Correct implementation of float casting comparison ([#33322](https://github.com/laravel/framework/pull/33322)) + + +## [v6.18.22 (2020-06-24)](https://github.com/laravel/framework/compare/v6.18.21...v6.18.22) + +### Revert +- Revert "Fixed `Model::originalIsEquivalent()` with floats ([#33259](https://github.com/laravel/framework/pull/33259), [d68d915](https://github.com/laravel/framework/commit/d68d91516db6d1b9cba8a72f99b2c7e8223e988f))" [bf3cb6f](https://github.com/laravel/framework/commit/bf3cb6f6979df2d6965d2e0aa731724d0e2b15e5) + + +## [v6.18.21 (2020-06-23)](https://github.com/laravel/framework/compare/v6.18.20...v6.18.21) + +### Fixed +- Fixed `Model::originalIsEquivalent()` with floats ([#33259](https://github.com/laravel/framework/pull/33259), [d68d915](https://github.com/laravel/framework/commit/d68d91516db6d1b9cba8a72f99b2c7e8223e988f)) + + +## [v6.18.20 (2020-06-16)](https://github.com/laravel/framework/compare/v6.18.19...v6.18.20) + +### Changed +- Improved the reflector ([#33184](https://github.com/laravel/framework/pull/33184)) + + +## [v6.18.19 (2020-06-09)](https://github.com/laravel/framework/compare/v6.18.18...v6.18.19) + +### Fixed +- Fixed `Model::withoutEvents()` not registering listeners inside boot() ([#33149](https://github.com/laravel/framework/pull/33149), [4bb32ae](https://github.com/laravel/framework/commit/4bb32aea50eec4c3cc8b77f463e4a96213a0af09)) + + +## [v6.18.18 (2020-06-03)](https://github.com/laravel/framework/compare/v6.18.17...v6.18.18) + +### Fixed +- Fixed `Illuminate\Database\Eloquent\Relations\MorphToMany::getCurrentlyAttachedPivots()` ([110b129](https://github.com/laravel/framework/commit/110b129531df172f03bf163f561c71123fac6296)) + + +## [v6.18.17 (2020-06-02)](https://github.com/laravel/framework/compare/v6.18.16...v6.18.17) + +### Added +- Support PHP 8's reflection API ([#33039](https://github.com/laravel/framework/pull/33039)) + +### Fixed +- Fixed `Illuminate\Database\Eloquent\Collection::getQueueableRelations()` ([00e9ed7](https://github.com/laravel/framework/commit/00e9ed76483ea6ad1264676e7b1095b23e16a433)) +- Fixed bug with update existing pivot and polymorphic many to many ([684208b](https://github.com/laravel/framework/commit/684208b10460b49fa34354cc42f33b9b7135814f)) + + +## [v6.18.15 (2020-05-19)](https://github.com/laravel/framework/compare/v6.18.14...v6.18.15) + +### Added +- Added `Illuminate\Http\Middleware\TrustHosts` ([9229264](https://github.com/laravel/framework/commit/92292649621f2aadc84ab94376244650a9f55696)) + +### Fixed +- Revert of ["Remove `strval` from `Illuminate/Validation/ValidationRuleParser::explodeWildcardRules()`"](https://github.com/laravel/framework/commit/1c76a6f3a80fa8f756740566dffd9fa1be65c123) ([52940cf](https://github.com/laravel/framework/commit/52940cf3275cfebd47ec008fd8ae5bc6d6a42dfd)) +- Fixed Queued Mail MessageSent Listener With Attachments ([#32795](https://github.com/laravel/framework/pull/32795)) +- Added error clearing before sending in `Illuminate\Mail\Mailer::sendSwiftMessage()` ([#32799](https://github.com/laravel/framework/pull/32799)) +- Avoid foundation function call in the auth component ([#32805](https://github.com/laravel/framework/pull/32805)) + +### Changed +- 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)) + + +## [v6.18.14 (2020-05-12)](https://github.com/laravel/framework/compare/v6.18.13...v6.18.14) + +### Added +- Added SSL SYSCALL EOF as a lost connection message ([#32697](https://github.com/laravel/framework/pull/32697)) + +### Fixed +- Fixed `FakerGenerator` Unique caching issue ([#32703](https://github.com/laravel/framework/pull/32703)) +- Added boolean to types that don't need character options ([#32716](https://github.com/laravel/framework/pull/32716)) +- Fixed `Illuminate\Foundation\Testing\PendingCommand` that do not resolve 'OutputStyle::class' from the container ([#32687](https://github.com/laravel/framework/pull/32687)) +- Clear resolved event facade on `Illuminate\Foundation\Testing\Concerns\MocksApplicationServices::withoutEvents()` ([d1e7f85](https://github.com/laravel/framework/commit/d1e7f85dfd79abbe4f5e01818f620f6ecc67de4d)) +- Fixed deprecated "Doctrine/Common/Inflector/Inflector" class ([#32734](https://github.com/laravel/framework/pull/32734)) + +### Changed +- Remove the undocumented dot keys support in validators ([#32764](https://github.com/laravel/framework/pull/32764)) +- Remove `strval` from `Illuminate/Validation/ValidationRuleParser::explodeWildcardRules()` [1c76a6f](https://github.com/laravel/framework/commit/1c76a6f3a80fa8f756740566dffd9fa1be65c123) + + +## [v6.18.13 (2020-05-05)](https://github.com/laravel/framework/compare/v6.18.12...v6.18.13) + +### Fixed +- Fixed `Illuminate\Database\Eloquent\Collection::getQueueableRelations()` ([7b32460](https://github.com/laravel/framework/commit/7b32469420258e9e52b24b2ffa7f491e79a3a870)) + + +## [v6.18.12 (2020-05-05)](https://github.com/laravel/framework/compare/v6.18.11...v6.18.12) + +### Added +- Add pdo try again as lost connection message ([#32605](https://github.com/laravel/framework/pull/32605)) + +### Fixed +- Fixed `Illuminate\Foundation\Testing\TestResponse::assertSessionHasInput()` ([f0639fd](https://github.com/laravel/framework/commit/f0639fda45fc2874986fe409d944dde21d42c6f3)) +- Set relation connection on eager loaded MorphTo ([#32602](https://github.com/laravel/framework/pull/32602)) +- Fixed `Illuminate\Database\Schema\Grammars\SqlServerGrammar::compileDropDefaultConstraint()` was ignoring Table prefixes ([#32606](https://github.com/laravel/framework/pull/32606)) +- Filtering null's in `hasMorph()` ([#32614](https://github.com/laravel/framework/pull/32614)) +- Fixed `Illuminate\Console\Scheduling\Schedule::compileParameters()` ([cfc3ac9](https://github.com/laravel/framework/commit/cfc3ac9c8b0a593d264ae722ab90601fa4882d0e), [36e215d](https://github.com/laravel/framework/commit/36e215dd39cd757a8ffc6b17794de60476b2289d)) +- Fixed bug with model name in `Illuminate\Database\Eloquent\RelationNotFoundException::make()` ([f72a166](https://github.com/laravel/framework/commit/f72a1662ab64cc543c532941b1ab1279001af8e9)) +- Fixed `Illuminate\Foundation\Testing\TestResponse::assertJsonCount()` not accepting falsey keys ([#32655](https://github.com/laravel/framework/pull/32655)) + +### Changed +- Changed `Illuminate/Database/Eloquent/Relations/Concerns/AsPivot::fromRawAttributes()` ([6c502c1](https://github.com/laravel/framework/commit/6c502c1135082e8b25f2720931b19d36eeec8f41)) +- Restore оnly common relations ([#32613](https://github.com/laravel/framework/pull/32613), [d82f78b](https://github.com/laravel/framework/commit/d82f78b13631c4a04b9595099da0022ca3d8b94e), [48e4d60](https://github.com/laravel/framework/commit/48e4d602d4f8fe9304e8998c5893206f67504dbf)) +- Use single space if plain email is empty in `Illuminate\Mail\Mailer::addContent()` ([0557622](https://github.com/laravel/framework/commit/055762286132d545cbc064dce645562c0d51532f)) +- Remove wasted file read when loading package manifest in `Illuminate\Foundation\PackageManifest::getManifest()` ([#32646](https://github.com/laravel/framework/pull/32646)) +- Cache `FakerGenerator` instances ([#32585](https://github.com/laravel/framework/pull/32585)) +- Do not change `character` and `collation` for some columns on change ([fccdf7c](https://github.com/laravel/framework/commit/fccdf7c42d5ceb50985b3e8243d7ba650de996d6)) + + +## [v6.18.11 (2020-04-28)](https://github.com/laravel/framework/compare/v6.18.10...v6.18.11) + +### Fixed +- Auth with each master on flushdb ([d0afa58](https://github.com/laravel/framework/commit/d0afa5846ca1d85ca07cdb580d3b9e9768ebdf41)) +- Clear resolved facades earlier ([f2ea1a2](https://github.com/laravel/framework/commit/f2ea1a23fdac94d3f0818e7ff514fbaed3f45643)) +- Register opis key so it is not tied to a deferred service provider ([a4574ea](https://github.com/laravel/framework/commit/a4574ea973bab9bd6a2ba34d36dfb8f9b55d5a4a)) +- Pass status code to schedule finish ([b815dc6](https://github.com/laravel/framework/commit/b815dc6c1b1c595f3241c493255f0fbfd67a6131)) +- Fix firstWhere behavior for relations ([#32525](https://github.com/laravel/framework/pull/32525)) +- Fixed boolean value in `Illuminate\Foundation\Testing\TestResponse::assertSessionHasErrors()` ([#32555](https://github.com/laravel/framework/pull/32555)) + + +## [v6.18.10 (2020-04-21)](https://github.com/laravel/framework/compare/v6.18.9...v6.18.10) + +### Fixed +- Check if object ([1b0bdb4](https://github.com/laravel/framework/commit/1b0bdb43062a2792befe6fd754140124a8e4dc35)) + + +## [v6.18.9 (2020-04-21)](https://github.com/laravel/framework/compare/v6.18.8...v6.18.9) + +### Fixed +- Fix `refresh()` to support `AsPivot` trait ([#32420](https://github.com/laravel/framework/pull/32420)) +- Fix orderBy with callable ([#32471](https://github.com/laravel/framework/pull/32471)) + + +## [v6.18.8 (2020-04-15)](https://github.com/laravel/framework/compare/v6.18.7...v6.18.8) + +### Fixed +- Removed dots ([e78d24f](https://github.com/laravel/framework/commit/e78d24f31b84cd81c30b5d8837731d77ec089761)) +- Duplicated mailable in-memory data attachments with different names ([#32392](https://github.com/laravel/framework/pull/32392)) +- Fix a regression caused by #32315 ([#32388](https://github.com/laravel/framework/pull/32388)) +- Duplicated mailable storage attachments with different names ([#32394](https://github.com/laravel/framework/pull/32394)) + + +## [v6.18.7 (2020-04-14)](https://github.com/laravel/framework/compare/v6.18.6...v6.18.7) + +### Fixed +- Call setlocale ([1c6a504](https://github.com/laravel/framework/commit/1c6a50424c5558782a55769a226ab834484282e1)) +- Use a map to prevent unnecessary array access ([#32296](https://github.com/laravel/framework/pull/32296)) +- Prevent timestamp update when pivot is not dirty ([#32311](https://github.com/laravel/framework/pull/32311)) +- Add support for the new composer installed.json format ([#32310](https://github.com/laravel/framework/pull/32310)) +- ValidatesAttributes::validateUrl use Symfony/Validator 5.0.7 regex ([#32315](https://github.com/laravel/framework/pull/32315)) +- Fix *scan methods for phpredis ([#32336](https://github.com/laravel/framework/pull/32336)) +- Use the router for absolute urls ([#32345](https://github.com/laravel/framework/pull/32345)) + + +## [v6.18.6 (2020-04-08)](https://github.com/laravel/framework/compare/v6.18.5...v6.18.6) + +### Security +- Prevent insecure characters in locale ([c248521](https://github.com/laravel/framework/commit/c248521f502c74c6cea7b0d221639d4aa752d5db)) + + +## [v6.18.5 (2020-04-07)](https://github.com/laravel/framework/compare/v6.18.4...v6.18.5) + +### Fixed +- Revert "Fix setting mail header" ([#32278](https://github.com/laravel/framework/pull/32278)) + + +## [v6.18.4 (2020-04-07)](https://github.com/laravel/framework/compare/v6.18.3...v6.18.4) + +### Fixed +- Added missing return in the sendNow pending mail fake ([#32095](https://github.com/laravel/framework/pull/32095)) +- Prevent long URLs from breaking email layouts ([#32189](https://github.com/laravel/framework/pull/32189)) +- Fix setting mail header ([#32272](https://github.com/laravel/framework/pull/32272)) + + +## [v6.18.3 (2020-03-24)](https://github.com/laravel/framework/compare/v6.18.2...v6.18.3) ### Fixed - Corrected suggested dependencies ([#32072](https://github.com/laravel/framework/pull/32072), [c01a70e](https://github.com/laravel/framework/commit/c01a70e33198e81d06d4b581e36e25a80acf8a68)) @@ -139,7 +454,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)) @@ -278,7 +593,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/README.md b/README.md index 17c583142a5e..ef4bc184428e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

Build Status diff --git a/composer.json b/composer.json index ff8e024c03e8..095f73bb0ac7 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,11 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^2.0", "egulias/email-validator": "^2.1.10", "league/commonmark": "^1.3", - "league/flysystem": "^1.0.8", + "league/flysystem": "^1.0.34", "monolog/monolog": "^1.12|^2.0", "nesbot/carbon": "^2.0", "opis/closure": "^3.1", @@ -36,6 +36,7 @@ "symfony/finder": "^4.3.4", "symfony/http-foundation": "^4.3.4", "symfony/http-kernel": "^4.3.4", + "symfony/polyfill-php73": "^1.17", "symfony/process": "^4.3.4", "symfony/routing": "^4.3.4", "symfony/var-dumper": "^4.3.4", @@ -112,6 +113,7 @@ } }, "suggest": { + "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", "ext-pcntl": "Required to use all features of the queue worker.", @@ -129,6 +131,7 @@ "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).", + "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 (^4.3.4).", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6ae4f5f54715..3cb5a6c6ba63 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,18 +17,19 @@ ./tests - - + + ./src - - ./src/ - - - + + + ./src/ + + - + + diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index 3e3b17b806b1..9cc701561ea4 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -450,7 +450,7 @@ protected function callbackAllowsGuests($callback) */ protected function parameterAllowsGuests($parameter) { - return ($parameter->getClass() && $parameter->allowsNull()) || + return ($parameter->hasType() && $parameter->allowsNull()) || ($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue())); } diff --git a/src/Illuminate/Auth/AuthServiceProvider.php b/src/Illuminate/Auth/AuthServiceProvider.php index 0ceb20b24089..7a6b41212784 100755 --- a/src/Illuminate/Auth/AuthServiceProvider.php +++ b/src/Illuminate/Auth/AuthServiceProvider.php @@ -3,8 +3,11 @@ namespace Illuminate\Auth; use Illuminate\Auth\Access\Gate; +use Illuminate\Auth\Middleware\RequirePassword; use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; +use Illuminate\Contracts\Routing\ResponseFactory; +use Illuminate\Contracts\Routing\UrlGenerator; use Illuminate\Support\ServiceProvider; class AuthServiceProvider extends ServiceProvider @@ -19,6 +22,7 @@ public function register() $this->registerAuthenticator(); $this->registerUserResolver(); $this->registerAccessGate(); + $this->registerRequirePassword(); $this->registerRequestRebindHandler(); $this->registerEventRebindHandler(); } @@ -72,6 +76,24 @@ protected function registerAccessGate() }); } + /** + * Register a resolver for the authenticated user. + * + * @return void + */ + protected function registerRequirePassword() + { + $this->app->bind( + RequirePassword::class, function ($app) { + return new RequirePassword( + $app[ResponseFactory::class], + $app[UrlGenerator::class], + $app['config']->get('auth.password_timeout') + ); + } + ); + } + /** * Handle the re-binding of the request binding. * diff --git a/src/Illuminate/Auth/Middleware/RequirePassword.php b/src/Illuminate/Auth/Middleware/RequirePassword.php index 00aa8dbcee81..315bdb8121ca 100644 --- a/src/Illuminate/Auth/Middleware/RequirePassword.php +++ b/src/Illuminate/Auth/Middleware/RequirePassword.php @@ -22,17 +22,26 @@ class RequirePassword */ protected $urlGenerator; + /** + * The password timeout. + * + * @var int + */ + protected $passwordTimeout; + /** * Create a new middleware instance. * * @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory * @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator + * @param int|null $passwordTimeout * @return void */ - public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator) + public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator, $passwordTimeout = null) { $this->responseFactory = $responseFactory; $this->urlGenerator = $urlGenerator; + $this->passwordTimeout = $passwordTimeout ?: 10800; } /** @@ -70,6 +79,6 @@ protected function shouldConfirmPassword($request) { $confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0); - return $confirmedAt > config('auth.password_timeout', 10800); + return $confirmedAt > $this->passwordTimeout; } } diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index 975ca6e1fca6..c6d72e449800 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -94,7 +94,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/Broadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php index 99950baea92f..f626187b069b 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Routing\BindingRegistrar; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Support\Arr; +use Illuminate\Support\Reflector; use Illuminate\Support\Str; use ReflectionClass; use ReflectionFunction; @@ -204,9 +205,9 @@ protected function resolveImplicitBindingIfPossible($key, $value, $callbackParam continue; } - $instance = $parameter->getClass()->newInstance(); + $className = Reflector::getParameterClassName($parameter); - if (! $model = $instance->resolveRouteBinding($value)) { + if (is_null($model = (new $className)->resolveRouteBinding($value))) { throw new AccessDeniedHttpException; } @@ -225,8 +226,8 @@ protected function resolveImplicitBindingIfPossible($key, $value, $callbackParam */ protected function isImplicitlyBindable($key, $parameter) { - return $parameter->name === $key && $parameter->getClass() && - $parameter->getClass()->isSubclassOf(UrlRoutable::class); + return $parameter->getName() === $key && + Reflector::isParameterSubclassOf($parameter, UrlRoutable::class); } /** diff --git a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php index 4b763634fc9b..68daf9da4b26 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php @@ -119,7 +119,9 @@ public function broadcast(array $channels, $event, array $payload = []) } throw new BroadcastException( - is_bool($response) ? 'Failed to connect to Pusher.' : $response['body'] + ! empty($response['body']) + ? sprintf('Pusher error: %s.', $response['status']) + : 'Failed to connect to Pusher.' ); } diff --git a/src/Illuminate/Cache/FileStore.php b/src/Illuminate/Cache/FileStore.php index d7e3a2e21c1e..7295d9e6d205 100755 --- a/src/Illuminate/Cache/FileStore.php +++ b/src/Illuminate/Cache/FileStore.php @@ -75,9 +75,7 @@ public function put($key, $value, $seconds) ); if ($result !== false && $result > 0) { - if (! is_null($this->filePermission)) { - $this->files->chmod($path, $this->filePermission); - } + $this->ensureFileHasCorrectPermissions($path); return true; } @@ -98,6 +96,22 @@ protected function ensureCacheDirectoryExists($path) } } + /** + * Ensure the cache file has the correct permissions. + * + * @param string $path + * @return void + */ + protected function ensureFileHasCorrectPermissions($path) + { + if (is_null($this->filePermission) || + intval($this->files->chmod($path), 8) == $this->filePermission) { + return; + } + + $this->files->chmod($path, $this->filePermission); + } + /** * Increment the value of an item in the cache. * @@ -165,7 +179,9 @@ public function flush() } foreach ($this->files->directories($this->directory) as $directory) { - if (! $this->files->deleteDirectory($directory)) { + $deleted = $this->files->deleteDirectory($directory); + + if (! $deleted || $this->files->exists($directory)) { return false; } } 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/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index d6e4b5ba2913..9d2ea99b7b01 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -253,11 +253,11 @@ protected function rootNamespace() */ protected function userProviderModel() { - $guard = config('auth.defaults.guard'); + $config = $this->laravel['config']; - $provider = config("auth.guards.{$guard}.provider"); + $provider = $config->get('auth.guards.'.$config->get('auth.defaults.guard').'.provider'); - return config("auth.providers.{$provider}.model"); + return $config->get("auth.providers.{$provider}.model"); } /** diff --git a/src/Illuminate/Console/Scheduling/CallbackEvent.php b/src/Illuminate/Console/Scheduling/CallbackEvent.php index 799c4afce7d3..96b1c1e1bfbd 100644 --- a/src/Illuminate/Console/Scheduling/CallbackEvent.php +++ b/src/Illuminate/Console/Scheduling/CallbackEvent.php @@ -28,11 +28,12 @@ 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)) { throw new InvalidArgumentException( @@ -43,6 +44,7 @@ public function __construct(EventMutex $mutex, $callback, array $parameters = [] $this->mutex = $mutex; $this->callback = $callback; $this->parameters = $parameters; + $this->timezone = $timezone; } /** diff --git a/src/Illuminate/Console/Scheduling/CommandBuilder.php b/src/Illuminate/Console/Scheduling/CommandBuilder.php index 0751d4841b25..bc833bd2710c 100644 --- a/src/Illuminate/Console/Scheduling/CommandBuilder.php +++ b/src/Illuminate/Console/Scheduling/CommandBuilder.php @@ -52,11 +52,11 @@ protected function buildBackgroundCommand(Event $event) $finished = Application::formatCommandString('schedule:finish').' "'.$event->mutexName().'"'; if (windows_os()) { - return 'start /b cmd /c "('.$event->command.' & '.$finished.')'.$redirect.$output.' 2>&1"'; + return 'start /b cmd /c "('.$event->command.' & '.$finished.' "%errorlevel%")'.$redirect.$output.' 2>&1"'; } return $this->ensureCorrectUser($event, - '('.$event->command.$redirect.$output.' 2>&1 ; '.$finished.') > ' + '('.$event->command.$redirect.$output.' 2>&1 ; '.$finished.' "$?") > ' .ProcessUtils::escapeArgument($event->getDefaultOutput()).' 2>&1 &' ); } diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 00692764bb3b..be8b6065c116 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -262,6 +262,20 @@ public function callAfterCallbacks(Container $container) } } + /** + * Call all of the "after" callbacks for the event. + * + * @param \Illuminate\Contracts\Container\Container $container + * @param int $exitCode + * @return void + */ + public function callAfterCallbacksWithExitCode(Container $container, $exitCode) + { + $this->exitCode = (int) $exitCode; + + $this->callAfterCallbacks($container); + } + /** * Build the command string. * diff --git a/src/Illuminate/Console/Scheduling/Schedule.php b/src/Illuminate/Console/Scheduling/Schedule.php index 418de6faac3f..bfbb4141a6d6 100644 --- a/src/Illuminate/Console/Scheduling/Schedule.php +++ b/src/Illuminate/Console/Scheduling/Schedule.php @@ -11,6 +11,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\CallQueuedClosure; use Illuminate\Support\ProcessUtils; +use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use RuntimeException; @@ -65,7 +66,7 @@ public function __construct($timezone = null) if (! class_exists(Container::class)) { throw new RuntimeException( - 'A container implementation is required to use the scheduler. Please install illuminate/container.' + 'A container implementation is required to use the scheduler. Please install the illuminate/container package.' ); } @@ -90,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; @@ -148,7 +149,7 @@ protected function dispatchToQueue($job, $queue, $connection) if ($job instanceof Closure) { if (! class_exists(CallQueuedClosure::class)) { throw new RuntimeException( - 'To enable support for closure jobs, please install illuminate/queue.' + 'To enable support for closure jobs, please install the illuminate/queue package.' ); } @@ -199,10 +200,10 @@ protected function compileParameters(array $parameters) { return collect($parameters)->map(function ($value, $key) { if (is_array($value)) { - $value = collect($value)->map(function ($value) { - return ProcessUtils::escapeArgument($value); - })->implode(' '); - } elseif (! is_numeric($value) && ! preg_match('/^(-.$|--.*)/i', $value)) { + return $this->compileArrayInput($key, $value); + } + + if (! is_numeric($value) && ! preg_match('/^(-.$|--.*)/i', $value)) { $value = ProcessUtils::escapeArgument($value); } @@ -210,6 +211,32 @@ protected function compileParameters(array $parameters) })->implode(' '); } + /** + * Compile array input for a command. + * + * @param string|int $key + * @param array $value + * @return string + */ + public function compileArrayInput($key, $value) + { + $value = collect($value)->map(function ($value) { + return ProcessUtils::escapeArgument($value); + }); + + if (Str::startsWith($key, '--')) { + $value = $value->map(function ($value) use ($key) { + return "{$key}={$value}"; + }); + } elseif (Str::startsWith($key, '-')) { + $value = $value->map(function ($value) use ($key) { + return "{$key} {$value}"; + }); + } + + return $value->implode(' '); + } + /** * Determine if the server is allowed to run this event. * @@ -274,7 +301,7 @@ protected function getDispatcher() $this->dispatcher = Container::getInstance()->make(Dispatcher::class); } catch (BindingResolutionException $e) { throw new RuntimeException( - 'Unable to resolve the dispatcher from the service container. Please bind it or install illuminate/bus.', + 'Unable to resolve the dispatcher from the service container. Please bind it or install the illuminate/bus package.', $e->getCode(), $e ); } diff --git a/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php b/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php index c7f4c12b78ac..c19381f08a51 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php @@ -11,7 +11,7 @@ class ScheduleFinishCommand extends Command * * @var string */ - protected $signature = 'schedule:finish {id}'; + protected $signature = 'schedule:finish {id} {code=0}'; /** * The console command description. @@ -37,6 +37,6 @@ public function handle(Schedule $schedule) { collect($schedule->events())->filter(function ($value) { return $value->mutexName() == $this->argument('id'); - })->each->callAfterCallbacks($this->laravel); + })->each->callAfterCallbacksWithExitCode($this->laravel, $this->argument('code')); } } diff --git a/src/Illuminate/Container/BoundMethod.php b/src/Illuminate/Container/BoundMethod.php index 8501232621e7..8b53f71a331d 100644 --- a/src/Illuminate/Container/BoundMethod.php +++ b/src/Illuminate/Container/BoundMethod.php @@ -157,16 +157,18 @@ protected static function getCallReflector($callback) protected static function addDependencyForCallParameter($container, $parameter, array &$parameters, &$dependencies) { - if (array_key_exists($parameter->name, $parameters)) { - $dependencies[] = $parameters[$parameter->name]; - - unset($parameters[$parameter->name]); - } elseif ($parameter->getClass() && array_key_exists($parameter->getClass()->name, $parameters)) { - $dependencies[] = $parameters[$parameter->getClass()->name]; - - unset($parameters[$parameter->getClass()->name]); - } elseif ($parameter->getClass()) { - $dependencies[] = $container->make($parameter->getClass()->name); + if (array_key_exists($paramName = $parameter->getName(), $parameters)) { + $dependencies[] = $parameters[$paramName]; + + unset($parameters[$paramName]); + } elseif (! is_null($className = Util::getParameterClassName($parameter))) { + if (array_key_exists($className, $parameters)) { + $dependencies[] = $parameters[$className]; + + unset($parameters[$className]); + } else { + $dependencies[] = $container->make($className); + } } elseif ($parameter->isDefaultValueAvailable()) { $dependencies[] = $parameter->getDefaultValue(); } diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index b595b5c9230c..c0e2082b360c 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -715,7 +715,7 @@ protected function resolve($abstract, $parameters = [], $raiseEvents = true) * Get the concrete type for a given abstract. * * @param string $abstract - * @return mixed $concrete + * @return mixed */ protected function getConcrete($abstract) { @@ -846,7 +846,7 @@ public function build($concrete) /** * Resolve all of the dependencies from the ReflectionParameters. * - * @param array $dependencies + * @param \ReflectionParameter[] $dependencies * @return array * * @throws \Illuminate\Contracts\Container\BindingResolutionException @@ -868,7 +868,7 @@ protected function resolveDependencies(array $dependencies) // If the class is null, it means the dependency is a string or some other // primitive type which we can not resolve since it is not a class and // we will just bomb out with an error since we have no-where to go. - $results[] = is_null($dependency->getClass()) + $results[] = is_null(Util::getParameterClassName($dependency)) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } @@ -920,7 +920,7 @@ protected function getLastParameterOverride() */ protected function resolvePrimitive(ReflectionParameter $parameter) { - if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) { + if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) { return $concrete instanceof Closure ? $concrete($this) : $concrete; } @@ -942,7 +942,7 @@ protected function resolvePrimitive(ReflectionParameter $parameter) protected function resolveClass(ReflectionParameter $parameter) { try { - return $this->make($parameter->getClass()->name); + return $this->make(Util::getParameterClassName($parameter)); } // If we can not resolve the class instance, we will check to see if the value diff --git a/src/Illuminate/Container/Util.php b/src/Illuminate/Container/Util.php index 5feca5510d06..0b4bb1283467 100644 --- a/src/Illuminate/Container/Util.php +++ b/src/Illuminate/Container/Util.php @@ -3,6 +3,7 @@ namespace Illuminate\Container; use Closure; +use ReflectionNamedType; class Util { @@ -35,4 +36,35 @@ public static function unwrapIfClosure($value) { return $value instanceof Closure ? $value() : $value; } + + /** + * Get the class name of the given parameter's type, if possible. + * + * From Reflector::getParameterClassName() in Illuminate\Support. + * + * @param \ReflectionParameter $parameter + * @return string|null + */ + public static function getParameterClassName($parameter) + { + $type = $parameter->getType(); + + if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { + return; + } + + $name = $type->getName(); + + if (! is_null($class = $parameter->getDeclaringClass())) { + if ($name === 'self') { + return $class->getName(); + } + + if ($name === 'parent' && $parent = $class->getParentClass()) { + return $parent->getName(); + } + } + + return $name; + } } diff --git a/src/Illuminate/Contracts/Auth/Factory.php b/src/Illuminate/Contracts/Auth/Factory.php index 3ee9c8ce643d..d76ee76489a4 100644 --- a/src/Illuminate/Contracts/Auth/Factory.php +++ b/src/Illuminate/Contracts/Auth/Factory.php @@ -8,7 +8,7 @@ interface Factory * Get a guard instance by name. * * @param string|null $name - * @return mixed + * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard */ public function guard($name = null); 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/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/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index a43b9befc1e1..3c7b43653861 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -166,7 +166,7 @@ public function when($value, $callback, $default = null) * Pass the query to a given callback. * * @param callable $callback - * @return \Illuminate\Database\Query\Builder + * @return $this */ public function tap($callback) { diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index bbefb1e4051b..5eab7a461e29 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -45,8 +45,14 @@ 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 (Exception $e) { + $commitFailed = true; + $this->handleCommitTransactionException( $e, $currentAttempt, $attempts ); @@ -54,6 +60,10 @@ public function transaction(Closure $callback, $attempts = 1) continue; } + if (! isset($commitFailed)) { + $this->fireConnectionEvent('committed'); + } + return $callbackResult; } } @@ -186,7 +196,7 @@ public function commit() */ protected function handleCommitTransactionException($e, $currentAttempt, $maxAttempts) { - $this->transactions--; + $this->transactions = max(0, $this->transactions - 1); if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { diff --git a/src/Illuminate/Database/DatabaseServiceProvider.php b/src/Illuminate/Database/DatabaseServiceProvider.php index fa0eb0dcbe61..3008e5b6bfe5 100755 --- a/src/Illuminate/Database/DatabaseServiceProvider.php +++ b/src/Illuminate/Database/DatabaseServiceProvider.php @@ -13,6 +13,13 @@ class DatabaseServiceProvider extends ServiceProvider { + /** + * The array of resolved Faker instances. + * + * @var array + */ + protected static $fakers = []; + /** * Bootstrap the application events. * @@ -75,7 +82,15 @@ protected function registerConnectionServices() protected function registerEloquentFactory() { $this->app->singleton(FakerGenerator::class, function ($app, $parameters) { - return FakerFactory::create($parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US')); + $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'); + + if (! isset(static::$fakers[$locale])) { + static::$fakers[$locale] = FakerFactory::create($locale); + } + + static::$fakers[$locale]->unique(true); + + return static::$fakers[$locale]; }); $this->app->singleton(EloquentFactory::class, function ($app) { diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 63b3b297b163..72132c164df7 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -43,6 +43,8 @@ protected function causedByLostConnection(Throwable $e) '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]: General error: 7 SSL SYSCALL error: EOF detected', ]); } } diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 00131c80054f..fe603c18fade 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -193,6 +193,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,6 +214,10 @@ public function whereKeyNot($id) return $this; } + if ($id !== null && $this->model->getKeyType() === 'string') { + $id = (string) $id; + } + return $this->where($this->model->getQualifiedKeyName(), '!=', $id); } diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 8c2ac6df4eda..7201603a9b33 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -335,7 +335,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) { @@ -557,7 +557,19 @@ public function getQueueableIds() */ public function getQueueableRelations() { - return $this->isNotEmpty() ? $this->first()->getQueueableRelations() : []; + if ($this->isEmpty()) { + return []; + } + + $relations = $this->map->getQueueableRelations()->all(); + + if (count($relations) === 0 || $relations === [[]]) { + return []; + } elseif (count($relations) === 1) { + return array_values($relations)[0]; + } else { + return array_intersect(...$relations); + } } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php index b9095c0480c7..d663a3835547 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. * @@ -152,6 +159,7 @@ public function isFillable($key) } return empty($this->getFillable()) && + strpos($key, '.') === false && ! Str::startsWith($key, '_'); } @@ -163,7 +171,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 9fb153be7a13..c5fa43a60955 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -351,7 +351,7 @@ public function getAttributeValue($key) } // If the attribute exists within the cast array, we will convert it to - // an appropriate native PHP type dependant upon the associated value + // an appropriate native PHP type dependent upon the associated value // given with the key in the pair. Dayle made this comment line up. if ($this->hasCast($key)) { return $this->castAttribute($key, $value); @@ -1178,6 +1178,12 @@ public function originalIsEquivalent($key, $current) } elseif ($this->hasCast($key, ['object', 'collection'])) { return $this->castAttribute($key, $current) == $this->castAttribute($key, $original); + } elseif ($this->hasCast($key, ['real', 'float', 'double'])) { + if (($current === null && $original !== null) || ($current !== null && $original === null)) { + return false; + } + + return abs($this->castAttribute($key, $current) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; } elseif ($this->hasCast($key)) { return $this->castAttribute($key, $current) === $this->castAttribute($key, $original); diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php index 96ed62334d49..0dc54308f395 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Eloquent\Concerns; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Events\NullDispatcher; use Illuminate\Support\Arr; use InvalidArgumentException; @@ -399,7 +400,9 @@ public static function withoutEvents(callable $callback) { $dispatcher = static::getEventDispatcher(); - static::unsetEventDispatcher(); + if ($dispatcher) { + static::setEventDispatcher(new NullDispatcher($dispatcher)); + } try { return $callback(); 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/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index cc99a7410313..f26154210ca6 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -204,7 +204,7 @@ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boole $types = (array) $types; if ($types === ['*']) { - $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->all(); + $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all(); foreach ($types as &$type) { $type = Relation::getMorphedModel($type) ?? $type; diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 45bd5bf47489..52d2dd40a998 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use Illuminate\Database\ConnectionResolverInterface as Resolver; +use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot; use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Support\Arr; use Illuminate\Support\Collection as BaseCollection; @@ -143,14 +144,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'; @@ -374,7 +375,7 @@ public function qualifyColumn($column) */ protected function removeTableFromKey($key) { - return Str::contains($key, '.') ? last(explode('.', $key)) : $key; + return $key; } /** @@ -1151,7 +1152,8 @@ public function refresh() ); $this->load(collect($this->relations)->reject(function ($relation) { - return $relation instanceof Pivot; + return $relation instanceof Pivot + || (is_object($relation) && in_array(AsPivot::class, class_uses_recursive($relation), true)); })->keys()->all()); $this->syncOriginal(); diff --git a/src/Illuminate/Database/Eloquent/RelationNotFoundException.php b/src/Illuminate/Database/Eloquent/RelationNotFoundException.php index 088429cc68f4..5acc0b309562 100755 --- a/src/Illuminate/Database/Eloquent/RelationNotFoundException.php +++ b/src/Illuminate/Database/Eloquent/RelationNotFoundException.php @@ -23,7 +23,7 @@ class RelationNotFoundException extends RuntimeException /** * Create a new exception instance. * - * @param mixed $model + * @param object $model * @param string $relation * @return static */ @@ -33,7 +33,7 @@ public static function make($model, $relation) $instance = new static("Call to undefined relationship [{$relation}] on model [{$class}]."); - $instance->model = $model; + $instance->model = $class; $instance->relation = $relation; return $instance; diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index cf8b902b6bea..59490329f341 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -574,6 +574,20 @@ public function findOrFail($id, $columns = ['*']) throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); } + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + /** * Execute the query and get the first result. * diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php index 5f1b0902fd21..c40d2db8e755 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php @@ -77,7 +77,7 @@ public static function fromRawAttributes(Model $parent, $attributes, $table, $ex $instance->timestamps = $instance->hasTimestampAttributes($attributes); - $instance->setRawAttributes($attributes, true); + $instance->setRawAttributes($attributes, $exists); return $instance; } diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 1372f628da78..3fe3b8d5de98 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -246,6 +246,20 @@ public function updateOrCreate(array $attributes, array $values = []) return $instance; } + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + /** * Execute the query and get the first related model. * diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index ce36ea5c2fee..0db82ba101bc 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -45,6 +45,10 @@ protected function setKeysForSaveQuery(Builder $query) */ public function delete() { + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + if ($this->fireModelEvent('deleting') === false) { return 0; } diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php index 354627f3f449..f0911c9dc31e 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php @@ -151,7 +151,11 @@ public function createModelByType($type) { $class = Model::getActualClassNameForMorph($type); - return new $class; + return tap(new $class, function ($instance) { + if (! $instance->getConnectionName()) { + $instance->setConnection($this->getConnection()->getName()); + } + }); } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 79a052af1a61..0adf385e13d6 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -115,6 +115,21 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, ); } + /** + * Get the pivot models that are currently attached. + * + * @return \Illuminate\Support\Collection + */ + protected function getCurrentlyAttachedPivots() + { + return parent::getCurrentlyAttachedPivots()->map(function ($record) { + return $record instanceof MorphPivot + ? $record->setMorphType($this->morphType) + ->setMorphClass($this->morphClass) + : $record; + }); + } + /** * Create a new query builder for the pivot table. * diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 3e488526edaa..0d4c7c3ae16c 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -242,7 +242,7 @@ public function select($columns = ['*']) /** * Add a subselect expression to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @param \Closure|$this|string $query * @param string $as * @return \Illuminate\Database\Query\Builder|static * @@ -2331,9 +2331,9 @@ protected function stripTableForPluck($column) return $column; } - $seperator = strpos(strtolower($column), ' as ') !== false ? ' as ' : '\.'; + $separator = strpos(strtolower($column), ' as ') !== false ? ' as ' : '\.'; - return last(preg_split('~'.$seperator.'~i', $column)); + return last(preg_split('~'.$separator.'~i', $column)); } /** diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index a661d2103f05..a7b930e16e22 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -1202,7 +1202,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/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index 8fa6b350fa6c..5956a8fb3148 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -17,7 +17,11 @@ class PostgresProcessor extends Processor */ public function processInsertGetId(Builder $query, $sql, $values, $sequence = null) { - $result = $query->getConnection()->selectFromWriteConnection($sql, $values)[0]; + $connection = $query->getConnection(); + + $connection->recordsHaveBeenModified(); + + $result = $connection->selectFromWriteConnection($sql, $values)[0]; $sequence = $sequence ?: 'id'; diff --git a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php index 3ea01d92b003..42f322db9713 100644 --- a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php +++ b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php @@ -28,7 +28,7 @@ public static function compile($grammar, Blueprint $blueprint, Fluent $command, { if (! $connection->isDoctrineAvailable()) { throw new RuntimeException(sprintf( - 'Changing columns for table "%s" requires Doctrine DBAL; install "doctrine/dbal".', + 'Changing columns for table "%s" requires Doctrine DBAL. Please install the doctrine/dbal package.', $blueprint->getTable() )); } @@ -121,7 +121,7 @@ protected static function getDoctrineColumnChangeOptions(Fluent $fluent) $options['length'] = static::calculateDoctrineTextLength($fluent['type']); } - if (in_array($fluent['type'], ['json', 'binary'])) { + if (static::doesntNeedCharacterOptions($fluent['type'])) { $options['customSchemaOptions'] = [ 'collation' => '', 'charset' => '', @@ -178,6 +178,31 @@ protected static function calculateDoctrineTextLength($type) } } + /** + * Determine if the given type does not need character / collation options. + * + * @param string $type + * @return bool + */ + protected static function doesntNeedCharacterOptions($type) + { + return in_array($type, [ + 'bigInteger', + 'binary', + 'boolean', + 'date', + 'decimal', + 'double', + 'float', + 'integer', + 'json', + 'mediumInteger', + 'smallInteger', + 'time', + 'tinyInteger', + ]); + } + /** * Get the matching Doctrine option for a given Fluent attribute name. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 2b2d381b5f03..0c1dd2e595a1 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -886,7 +886,7 @@ protected function typeMultiPolygonZ(Fluent $column) * @param \Illuminate\Support\Fluent $column * @return string */ - private function formatPostGisType(string $type, Fluent $column) + private function formatPostGisType($type, Fluent $column) { if ($column->isGeometry === null) { return sprintf('geography(%s, %s)', $type, $column->projection ?? '4326'); diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index d356e87cdead..f30be675939f 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -208,10 +208,12 @@ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $comma { $columns = "'".implode("','", $command->columns)."'"; + $tableName = $this->getTablePrefix().$blueprint->getTable(); + $sql = "DECLARE @sql NVARCHAR(MAX) = '';"; - $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$blueprint->getTable()}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; + $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$tableName}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; $sql .= 'FROM SYS.COLUMNS '; - $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$blueprint->getTable()}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; + $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$tableName}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; $sql .= 'EXEC(@sql)'; return $sql; diff --git a/src/Illuminate/Encryption/EncryptionServiceProvider.php b/src/Illuminate/Encryption/EncryptionServiceProvider.php index 31a5972d3839..cd590f12dbb3 100755 --- a/src/Illuminate/Encryption/EncryptionServiceProvider.php +++ b/src/Illuminate/Encryption/EncryptionServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; +use Opis\Closure\SerializableClosure; use RuntimeException; class EncryptionServiceProvider extends ServiceProvider @@ -14,21 +15,56 @@ class EncryptionServiceProvider extends ServiceProvider * @return void */ public function register() + { + $this->registerEncrypter(); + $this->registerOpisSecurityKey(); + } + + /** + * Register the encrypter. + * + * @return void + */ + protected function registerEncrypter() { $this->app->singleton('encrypter', function ($app) { $config = $app->make('config')->get('app'); - // If the key starts with "base64:", we will need to decode the key before handing - // it off to the encrypter. Keys may be base-64 encoded for presentation and we - // want to make sure to convert them back to the raw bytes before encrypting. - if (Str::startsWith($key = $this->key($config), 'base64:')) { - $key = base64_decode(substr($key, 7)); - } - - return new Encrypter($key, $config['cipher']); + return new Encrypter($this->parseKey($config), $config['cipher']); }); } + /** + * Configure Opis Closure signing for security. + * + * @return void + */ + protected function registerOpisSecurityKey() + { + $config = $this->app->make('config')->get('app'); + + if (! class_exists(SerializableClosure::class) || empty($config['key'])) { + return; + } + + SerializableClosure::setSecretKey($this->parseKey($config)); + } + + /** + * Parse the encryption key. + * + * @param array $config + * @return string + */ + protected function parseKey(array $config) + { + if (Str::startsWith($key = $this->key($config), $prefix = 'base64:')) { + $key = base64_decode(Str::after($key, $prefix)); + } + + return $key; + } + /** * Extract the encryption key from the given configuration. * diff --git a/src/Illuminate/Events/NullDispatcher.php b/src/Illuminate/Events/NullDispatcher.php new file mode 100644 index 000000000000..793ef1e19ccd --- /dev/null +++ b/src/Illuminate/Events/NullDispatcher.php @@ -0,0 +1,141 @@ +dispatcher = $dispatcher; + } + + /** + * Don't fire an event. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return void + */ + public function dispatch($event, $payload = [], $halt = false) + { + } + + /** + * Don't register an event and payload to be fired later. + * + * @param string $event + * @param array $payload + * @return void + */ + public function push($event, $payload = []) + { + } + + /** + * Don't dispatch an event. + * + * @param string|object $event + * @param mixed $payload + * @return array|null + */ + public function until($event, $payload = []) + { + } + + /** + * Register an event listener with the dispatcher. + * + * @param string|array $events + * @param \Closure|string $listener + * @return void + */ + public function listen($events, $listener) + { + $this->dispatcher->listen($events, $listener); + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param object|string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $this->dispatcher->subscribe($subscriber); + } + + /** + * Flush a set of pushed events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + $this->dispatcher->flush($event); + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + $this->dispatcher->forget($event); + } + + /** + * Forget all of the queued listeners. + * + * @return void + */ + public function forgetPushed() + { + $this->dispatcher->forgetPushed(); + } + + /** + * Dynamically pass method calls to the underlying dispatcher. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->forwardCallTo($this->dispatcher, $method, $parameters); + } +} diff --git a/src/Illuminate/Filesystem/FilesystemAdapter.php b/src/Illuminate/Filesystem/FilesystemAdapter.php index ddb6d0f66779..d3d62194b251 100644 --- a/src/Illuminate/Filesystem/FilesystemAdapter.php +++ b/src/Illuminate/Filesystem/FilesystemAdapter.php @@ -132,7 +132,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 +230,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 +246,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 = []) 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 16ed6e8fb4cd..010622d88b80 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -30,11 +30,12 @@ } }, "suggest": { - "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0.34).", "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).", - "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0)" + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0)." }, "config": { "sort-packages": true diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 10f733cd78cc..bec8355f1017 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -31,7 +31,7 @@ class Application extends Container implements ApplicationContract, HttpKernelIn * * @var string */ - const VERSION = '6.18.8'; + const VERSION = '6.18.43'; /** * The base path for the Laravel installation. diff --git a/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php b/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php index 543c58727a0f..e03340cc0028 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/ClosureCommand.php b/src/Illuminate/Foundation/Console/ClosureCommand.php index eb6ddc0eaaca..c0d736bdb3da 100644 --- a/src/Illuminate/Foundation/Console/ClosureCommand.php +++ b/src/Illuminate/Foundation/Console/ClosureCommand.php @@ -46,8 +46,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $parameters = []; foreach ((new ReflectionFunction($this->callback))->getParameters() as $parameter) { - if (isset($inputs[$parameter->name])) { - $parameters[$parameter->name] = $inputs[$parameter->name]; + if (isset($inputs[$parameter->getName()])) { + $parameters[$parameter->getName()] = $inputs[$parameter->getName()]; } } diff --git a/src/Illuminate/Foundation/Console/Kernel.php b/src/Illuminate/Foundation/Console/Kernel.php index 91db5e266c24..058ee7c8eeda 100644 --- a/src/Illuminate/Foundation/Console/Kernel.php +++ b/src/Illuminate/Foundation/Console/Kernel.php @@ -38,7 +38,7 @@ class Kernel implements KernelContract /** * The Artisan application instance. * - * @var \Illuminate\Console\Application + * @var \Illuminate\Console\Application|null */ protected $artisan; diff --git a/src/Illuminate/Foundation/Console/stubs/exception-render-report.stub b/src/Illuminate/Foundation/Console/stubs/exception-render-report.stub index cf877ec36820..712a40789e76 100644 --- a/src/Illuminate/Foundation/Console/stubs/exception-render-report.stub +++ b/src/Illuminate/Foundation/Console/stubs/exception-render-report.stub @@ -11,8 +11,8 @@ class DummyClass extends Exception * * @return void */ - public function report() - { + public function report() + { // } diff --git a/src/Illuminate/Foundation/Events/DiscoverEvents.php b/src/Illuminate/Foundation/Events/DiscoverEvents.php index 8d89e52805ac..0fa87135c9ff 100644 --- a/src/Illuminate/Foundation/Events/DiscoverEvents.php +++ b/src/Illuminate/Foundation/Events/DiscoverEvents.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Events; +use Illuminate\Support\Reflector; use Illuminate\Support\Str; use ReflectionClass; use ReflectionException; @@ -58,7 +59,7 @@ protected static function getListenerEvents($listeners, $basePath) } $listenerEvents[$listener->name.'@'.$method->name] = - optional($method->getParameters()[0]->getClass())->name; + Reflector::getParameterClassName($method->getParameters()[0]); } } diff --git a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php index 0f24357e20fc..6a1f028f9ce8 100644 --- a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php +++ b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -6,6 +6,7 @@ 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 +152,7 @@ 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()); + $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized())); } return $token; diff --git a/src/Illuminate/Foundation/PackageManifest.php b/src/Illuminate/Foundation/PackageManifest.php index 46617069af9b..df770aa2fba8 100644 --- a/src/Illuminate/Foundation/PackageManifest.php +++ b/src/Illuminate/Foundation/PackageManifest.php @@ -106,8 +106,6 @@ protected function getManifest() $this->build(); } - $this->files->get($this->manifestPath); - return $this->manifest = file_exists($this->manifestPath) ? $this->files->getRequire($this->manifestPath) : []; } diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 3aa10c4288b7..edb679d7cdc1 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\Foundation\Testing\TestResponse; use Illuminate\Http\Request; use Illuminate\Support\Str; @@ -560,8 +561,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/Concerns/MocksApplicationServices.php b/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php index bfca60f97c5d..7fc360e76f75 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Bus\Dispatcher as BusDispatcherContract; use Illuminate\Contracts\Events\Dispatcher as EventsDispatcherContract; use Illuminate\Contracts\Notifications\Dispatcher as NotificationDispatcher; +use Illuminate\Support\Facades\Event; use Mockery; trait MocksApplicationServices @@ -102,6 +103,8 @@ protected function withoutEvents() $this->firedEvents[] = $called; }); + Event::clearResolvedInstances(); + $this->app->instance('events', $mock); return $this; diff --git a/src/Illuminate/Foundation/Testing/PendingCommand.php b/src/Illuminate/Foundation/Testing/PendingCommand.php index 81615954e8c7..79f9ce4fb718 100644 --- a/src/Illuminate/Foundation/Testing/PendingCommand.php +++ b/src/Illuminate/Foundation/Testing/PendingCommand.php @@ -131,10 +131,10 @@ public function run() { $this->hasExecuted = true; - $this->mockConsoleOutput(); + $mock = $this->mockConsoleOutput(); try { - $exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters); + $exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters, $mock); } catch (NoMatchingExpectationException $e) { if ($e->getMethodName() === 'askQuestion') { $this->test->fail('Unexpected question "'.$e->getActualArguments()[0]->getQuestion().'" was asked.'); @@ -156,7 +156,7 @@ public function run() /** * Mock the application's console output. * - * @return void + * @return \Mockery\MockInterface */ protected function mockConsoleOutput() { @@ -181,6 +181,8 @@ protected function mockConsoleOutput() $this->app->bind(OutputStyle::class, function () use ($mock) { return $mock; }); + + return $mock; } /** diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index b9caa55f249e..2ac907f7edf4 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -75,6 +75,8 @@ abstract public function createApplication(); */ protected function setUp(): void { + Facade::clearResolvedInstances(); + if (! $this->app) { $this->refreshApplication(); } @@ -85,8 +87,6 @@ protected function setUp(): void $callback(); } - Facade::clearResolvedInstances(); - Model::setEventDispatcher($this->app['events']); $this->setUpHasRun = true; diff --git a/src/Illuminate/Foundation/Testing/TestResponse.php b/src/Illuminate/Foundation/Testing/TestResponse.php index b2a5487b1805..db8e0ff02d38 100644 --- a/src/Illuminate/Foundation/Testing/TestResponse.php +++ b/src/Illuminate/Foundation/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\Foundation\Testing\Assert as PHPUnit; use Illuminate\Foundation\Testing\Constraints\SeeInOrder; @@ -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, @@ -684,7 +686,7 @@ public function assertJsonStructure(array $structure = null, $responseData = nul */ public function assertJsonCount(int $count, $key = null) { - if ($key) { + if (! is_null($key)) { PHPUnit::assertCount( $count, data_get($this->json(), $key), "Failed to assert that the response count matched the expected {$count}" @@ -855,7 +857,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) { @@ -909,7 +911,7 @@ public function assertViewMissing($key) { $this->ensureResponseHasView(); - PHPUnit::assertArrayNotHasKey($key, $this->original->gatherData()); + PHPUnit::assertFalse(Arr::has($this->original->gatherData(), $key)); return $this; } @@ -997,7 +999,7 @@ public function assertSessionHasInput($key, $value = null) if (is_null($value)) { PHPUnit::assertTrue( - $this->session()->getOldInput($key), + $this->session()->hasOldInput($key), "Session is missing expected key [{$key}]." ); } elseif ($value instanceof Closure) { @@ -1029,7 +1031,7 @@ public function assertSessionHasErrors($keys = [], $format = null, $errorBag = ' if (is_int($key)) { PHPUnit::assertTrue($errors->has($value), "Session missing error: $value"); } else { - PHPUnit::assertContains($value, $errors->get($key, $format)); + PHPUnit::assertContains(is_bool($value) ? (string) $value : $value, $errors->get($key, $format)); } } 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/Http/Middleware/TrustHosts.php b/src/Illuminate/Http/Middleware/TrustHosts.php new file mode 100644 index 000000000000..8b563151adc0 --- /dev/null +++ b/src/Illuminate/Http/Middleware/TrustHosts.php @@ -0,0 +1,73 @@ +app = $app; + } + + /** + * Get the host patterns that should be trusted. + * + * @return array + */ + abstract public function hosts(); + + /** + * Handle the incoming request. + * + * @param \Illuminate\Http\Request $request + * @param callable $next + * @return \Illuminate\Http\Response + */ + public function handle(Request $request, $next) + { + if ($this->shouldSpecifyTrustedHosts()) { + Request::setTrustedHosts(array_filter($this->hosts())); + } + + return $next($request); + } + + /** + * Determine if the application should specify trusted hosts. + * + * @return bool + */ + protected function shouldSpecifyTrustedHosts() + { + return config('app.env') !== 'local' && + ! $this->app->runningUnitTests(); + } + + /** + * Get a regular expression matching the application URL and all of its subdomains. + * + * @return string|null + */ + protected function allSubdomainsOfApplicationUrl() + { + if ($host = parse_url($this->app['config']->get('app.url'), PHP_URL_HOST)) { + return '^(.+\.)?'.preg_quote($host).'$'; + } + } +} diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index 0c9bfa6b005f..a4018c5c2174 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -294,7 +294,7 @@ public function ips() /** * Get the client user agent. * - * @return string + * @return string|null */ public function userAgent() { 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/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/Mail/Events/MessageSent.php b/src/Illuminate/Mail/Events/MessageSent.php index 9dee09c2f73c..64aef94312b6 100644 --- a/src/Illuminate/Mail/Events/MessageSent.php +++ b/src/Illuminate/Mail/Events/MessageSent.php @@ -2,6 +2,8 @@ namespace Illuminate\Mail\Events; +use Swift_Attachment; + class MessageSent { /** @@ -30,4 +32,43 @@ public function __construct($message, $data = []) $this->data = $data; $this->message = $message; } + + /** + * Get the serializable representation of the object. + * + * @return array + */ + public function __serialize() + { + $hasAttachments = collect($this->message->getChildren()) + ->whereInstanceOf(Swift_Attachment::class) + ->isNotEmpty(); + + return $hasAttachments ? [ + 'message' => base64_encode(serialize($this->message)), + 'data' => base64_encode(serialize($this->data)), + 'hasAttachments' => true, + ] : [ + 'message' => $this->message, + 'data' => $this->data, + 'hasAttachments' => false, + ]; + } + + /** + * Marshal the object from its serialized data. + * + * @param array $data + * @return void + */ + public function __unserialize(array $data) + { + if (isset($data['hasAttachments']) && $data['hasAttachments'] === true) { + $this->message = unserialize(base64_decode($data['message'])); + $this->data = unserialize(base64_decode($data['data'])); + } else { + $this->message = $data['message']; + $this->data = $data['data']; + } + } } diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php index 01a32bded0a8..10a7ae6b6702 100755 --- a/src/Illuminate/Mail/Mailer.php +++ b/src/Illuminate/Mail/Mailer.php @@ -331,7 +331,7 @@ protected function addContent($message, $view, $plain, $raw, $data) if (isset($plain)) { $method = isset($view) ? 'addPart' : 'setBody'; - $message->$method($this->renderView($plain, $data), 'text/plain'); + $message->$method($this->renderView($plain, $data) ?: ' ', 'text/plain'); } if (isset($raw)) { @@ -482,6 +482,8 @@ protected function createMessage() */ protected function sendSwiftMessage($message) { + $this->failedRecipients = []; + try { return $this->swift->send($message, $this->failedRecipients); } finally { diff --git a/src/Illuminate/Notifications/AnonymousNotifiable.php b/src/Illuminate/Notifications/AnonymousNotifiable.php index d820239f165c..eab959b7c564 100644 --- a/src/Illuminate/Notifications/AnonymousNotifiable.php +++ b/src/Illuminate/Notifications/AnonymousNotifiable.php @@ -3,6 +3,7 @@ namespace Illuminate\Notifications; use Illuminate\Contracts\Notifications\Dispatcher; +use InvalidArgumentException; class AnonymousNotifiable { @@ -22,6 +23,10 @@ class AnonymousNotifiable */ public function route($channel, $route) { + if ($channel === 'database') { + throw new InvalidArgumentException('The database channel does not support on-demand notifications.'); + } + $this->routes[$channel] = $route; return $this; diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index 688c1cd5d8f7..c65940aad3ea 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -102,7 +102,9 @@ public function sendNow($notifiables, $notification, array $channels = null) $notificationId = Str::uuid()->toString(); foreach ((array) $viaChannels as $channel) { - $this->sendToNotifiable($notifiable, $notificationId, clone $original, $channel); + if (! ($notifiable instanceof AnonymousNotifiable && $channel === 'database')) { + $this->sendToNotifiable($notifiable, $notificationId, clone $original, $channel); + } } }); } diff --git a/src/Illuminate/Pipeline/composer.json b/src/Illuminate/Pipeline/composer.json index a5c9e57565f8..68d30a44dcdf 100644 --- a/src/Illuminate/Pipeline/composer.json +++ b/src/Illuminate/Pipeline/composer.json @@ -17,7 +17,7 @@ "php": "^7.2", "illuminate/contracts": "^6.0", "illuminate/support": "^6.0", - "symfony/debug": "^4.3" + "symfony/debug": "^4.3.4" }, "autoload": { "psr-4": { 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/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/Redis/Connections/PhpRedisConnection.php b/src/Illuminate/Redis/Connections/PhpRedisConnection.php index 41d6607c97b1..0950ec97cdcb 100644 --- a/src/Illuminate/Redis/Connections/PhpRedisConnection.php +++ b/src/Illuminate/Redis/Connections/PhpRedisConnection.php @@ -21,16 +21,25 @@ class PhpRedisConnection extends Connection implements ConnectionContract */ protected $connector; + /** + * The connection configuration array. + * + * @var array + */ + protected $config; + /** * Create a new PhpRedis connection. * * @param \Redis $client * @param callable|null $connector + * @param array $config * @return void */ - public function __construct($client, callable $connector = null) + public function __construct($client, callable $connector = null, array $config = []) { $this->client = $client; + $this->config = $config; $this->connector = $connector; } @@ -476,7 +485,13 @@ public function flushdb() } foreach ($this->client->_masters() as [$host, $port]) { - tap(new Redis)->connect($host, $port)->flushDb(); + $redis = tap(new Redis)->connect($host, $port); + + if (isset($this->config['password']) && ! empty($this->config['password'])) { + $redis->auth($this->config['password']); + } + + $redis->flushDb(); } } 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..665f328b0e9e 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 { diff --git a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php index 38c353b9fca2..b01f114205d8 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; @@ -28,7 +29,7 @@ public function connect(array $config, array $options) )); }; - return new PhpRedisConnection($connector(), $connector); + return new PhpRedisConnection($connector(), $connector, $config); } /** @@ -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', ])); } @@ -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, @@ -165,4 +166,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 84dfa186bc9b..2fdce35b4f49 100755 --- a/src/Illuminate/Redis/composer.json +++ b/src/Illuminate/Redis/composer.json @@ -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/ImplicitRouteBinding.php b/src/Illuminate/Routing/ImplicitRouteBinding.php index b3b6ca088730..e30372dab807 100644 --- a/src/Illuminate/Routing/ImplicitRouteBinding.php +++ b/src/Illuminate/Routing/ImplicitRouteBinding.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Reflector; use Illuminate\Support\Str; class ImplicitRouteBinding @@ -22,7 +23,7 @@ public static function resolveForRoute($container, $route) $parameters = $route->parameters(); foreach ($route->signatureParameters(UrlRoutable::class) as $parameter) { - if (! $parameterName = static::getParameterName($parameter->name, $parameters)) { + if (! $parameterName = static::getParameterName($parameter->getName(), $parameters)) { continue; } @@ -32,7 +33,7 @@ public static function resolveForRoute($container, $route) continue; } - $instance = $container->make($parameter->getClass()->name); + $instance = $container->make(Reflector::getParameterClassName($parameter)); if (! $model = $instance->resolveRouteBinding($parameterValue)) { throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]); diff --git a/src/Illuminate/Routing/Matching/HostValidator.php b/src/Illuminate/Routing/Matching/HostValidator.php index 76f9d878ec04..a0ea7210cb54 100644 --- a/src/Illuminate/Routing/Matching/HostValidator.php +++ b/src/Illuminate/Routing/Matching/HostValidator.php @@ -16,10 +16,12 @@ class HostValidator implements ValidatorInterface */ public function matches(Route $route, Request $request) { - if (is_null($route->getCompiled()->getHostRegex())) { + $hostRegex = $route->getCompiled()->getHostRegex(); + + if (is_null($hostRegex)) { return true; } - return preg_match($route->getCompiled()->getHostRegex(), $request->getHost()); + return preg_match($hostRegex, $request->getHost()); } } diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 82ef0387401a..2d854ef95964 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -73,7 +73,7 @@ class Route /** * The array of matched parameters. * - * @var array + * @var array|null */ public $parameters; diff --git a/src/Illuminate/Routing/RouteDependencyResolverTrait.php b/src/Illuminate/Routing/RouteDependencyResolverTrait.php index 17213d4a6f7e..b3e887b169cb 100644 --- a/src/Illuminate/Routing/RouteDependencyResolverTrait.php +++ b/src/Illuminate/Routing/RouteDependencyResolverTrait.php @@ -3,6 +3,7 @@ namespace Illuminate\Routing; use Illuminate\Support\Arr; +use Illuminate\Support\Reflector; use ReflectionFunctionAbstract; use ReflectionMethod; use ReflectionParameter; @@ -68,15 +69,15 @@ public function resolveMethodDependencies(array $parameters, ReflectionFunctionA */ protected function transformDependency(ReflectionParameter $parameter, $parameters) { - $class = $parameter->getClass(); + $className = Reflector::getParameterClassName($parameter); // If the parameter has a type-hinted class, we will check to see if it is already in // the list of parameters. If it is we will just skip it as it is probably a model // binding and we do not want to mess with those; otherwise, we resolve it here. - if ($class && ! $this->alreadyInParameters($class->name, $parameters)) { + if ($className && ! $this->alreadyInParameters($className, $parameters)) { return $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() - : $this->container->make($class->name); + : $this->container->make($className); } } diff --git a/src/Illuminate/Routing/RouteSignatureParameters.php b/src/Illuminate/Routing/RouteSignatureParameters.php index fe5b170f5e3b..535d5edcbf32 100644 --- a/src/Illuminate/Routing/RouteSignatureParameters.php +++ b/src/Illuminate/Routing/RouteSignatureParameters.php @@ -2,6 +2,7 @@ namespace Illuminate\Routing; +use Illuminate\Support\Reflector; use Illuminate\Support\Str; use ReflectionFunction; use ReflectionMethod; @@ -22,7 +23,7 @@ public static function fromAction(array $action, $subClass = null) : (new ReflectionFunction($action['uses']))->getParameters(); return is_null($subClass) ? $parameters : array_filter($parameters, function ($p) use ($subClass) { - return $p->getClass() && $p->getClass()->isSubclassOf($subClass); + return Reflector::isParameterSubclassOf($p, $subClass); }); } diff --git a/src/Illuminate/Routing/RoutingServiceProvider.php b/src/Illuminate/Routing/RoutingServiceProvider.php index 834a0f38890c..deed73f6a804 100755 --- a/src/Illuminate/Routing/RoutingServiceProvider.php +++ b/src/Illuminate/Routing/RoutingServiceProvider.php @@ -144,7 +144,7 @@ protected function registerPsrRequest() return (new DiactorosFactory)->createRequest($app->make('request')); } - throw new BindingResolutionException('Unable to resolve PSR request. Please install symfony/psr-http-message-bridge and nyholm/psr7.'); + throw new BindingResolutionException('Unable to resolve PSR request. Please install the symfony/psr-http-message-bridge and nyholm/psr7 packages.'); }); } @@ -164,7 +164,7 @@ protected function registerPsrResponse() return new ZendPsrResponse; } - throw new BindingResolutionException('Unable to resolve PSR response. Please install nyholm/psr7.'); + throw new BindingResolutionException('Unable to resolve PSR response. Please install the nyholm/psr7 package.'); }); } diff --git a/src/Illuminate/Routing/UrlGenerator.php b/src/Illuminate/Routing/UrlGenerator.php index b8b8f08c1fc8..63e344aca213 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 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/Support/Arr.php b/src/Illuminate/Support/Arr.php index ae9a66a60199..bf30467df51f 100755 --- a/src/Illuminate/Support/Arr.php +++ b/src/Illuminate/Support/Arr.php @@ -628,7 +628,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 6bd647aed96e..91102f6862d2 100644 --- a/src/Illuminate/Support/Collection.php +++ b/src/Illuminate/Support/Collection.php @@ -428,7 +428,7 @@ public function get($key, $default = null) */ public function groupBy($groupBy, $preserveKeys = false) { - if (is_array($groupBy)) { + if (! $this->useAsCallable($groupBy) && is_array($groupBy)) { $nextGroups = $groupBy; $groupBy = array_shift($nextGroups); diff --git a/src/Illuminate/Support/ConfigurationUrlParser.php b/src/Illuminate/Support/ConfigurationUrlParser.php index e67e469ed15e..c7861d5c1c47 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', ]; /** @@ -39,12 +41,16 @@ public function parseConfiguration($config) return $config; } - $parsedUrl = $this->parseUrl($url); + $rawComponents = $this->parseUrl($url); + + $decodedComponents = $this->parseStringsToNativeTypes( + array_map('rawurldecode', $rawComponents) + ); return array_merge( $config, - $this->getPrimaryOptions($parsedUrl), - $this->getQueryOptions($parsedUrl) + $this->getPrimaryOptions($decodedComponents), + $this->getQueryOptions($rawComponents) ); } @@ -137,9 +143,7 @@ protected function parseUrl($url) throw new InvalidArgumentException('The database configuration URL is malformed.'); } - return $this->parseStringsToNativeTypes( - array_map('rawurldecode', $parsedUrl) - ); + return $parsedUrl; } /** diff --git a/src/Illuminate/Support/Facades/Auth.php b/src/Illuminate/Support/Facades/Auth.php index bc19fb0289bd..a035ced2e343 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -3,7 +3,7 @@ namespace Illuminate\Support\Facades; /** - * @method static mixed guard(string|null $name = null) + * @method static \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard guard(string|null $name = null) * @method static void shouldUse(string $name); * @method static bool check() * @method static bool guest() diff --git a/src/Illuminate/Support/Facades/Password.php b/src/Illuminate/Support/Facades/Password.php index 228b588827d5..864b5e987dde 100755 --- a/src/Illuminate/Support/Facades/Password.php +++ b/src/Illuminate/Support/Facades/Password.php @@ -40,6 +40,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/Storage.php b/src/Illuminate/Support/Facades/Storage.php index 53dafc80bd2b..1ead2bcf00da 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -10,6 +10,8 @@ * @method static string get(string $path) * @method static resource|null readStream(string $path) * @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 writeStream(string $path, resource $resource, array $options = []) * @method static string getVisibility(string $path) * @method static bool setVisibility(string $path, string $visibility) diff --git a/src/Illuminate/Support/Pluralizer.php b/src/Illuminate/Support/Pluralizer.php index 9badfc59ee89..03719d4e22d8 100755 --- a/src/Illuminate/Support/Pluralizer.php +++ b/src/Illuminate/Support/Pluralizer.php @@ -2,7 +2,10 @@ namespace Illuminate\Support; -use Doctrine\Common\Inflector\Inflector; +use Doctrine\Inflector\CachedWordInflector; +use Doctrine\Inflector\Inflector; +use Doctrine\Inflector\Rules\English; +use Doctrine\Inflector\RulesetInflector; class Pluralizer { @@ -70,7 +73,7 @@ public static function plural($value, $count = 2) return $value; } - $plural = Inflector::pluralize($value); + $plural = static::inflector()->pluralize($value); return static::matchCase($plural, $value); } @@ -83,7 +86,7 @@ public static function plural($value, $count = 2) */ public static function singular($value) { - $singular = Inflector::singularize($value); + $singular = static::inflector()->singularize($value); return static::matchCase($singular, $value); } @@ -118,4 +121,27 @@ protected static function matchCase($value, $comparison) return $value; } + + /** + * Get the inflector instance. + * + * @return \Doctrine\Inflector\Inflector + */ + public static function inflector() + { + static $inflector; + + if (is_null($inflector)) { + $inflector = new Inflector( + new CachedWordInflector(new RulesetInflector( + English\Rules::getSingularRuleset() + )), + new CachedWordInflector(new RulesetInflector( + English\Rules::getPluralRuleset() + )) + ); + } + + return $inflector; + } } diff --git a/src/Illuminate/Support/Reflector.php b/src/Illuminate/Support/Reflector.php new file mode 100644 index 000000000000..fb597f5141f1 --- /dev/null +++ b/src/Illuminate/Support/Reflector.php @@ -0,0 +1,54 @@ +getType(); + + if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { + return; + } + + $name = $type->getName(); + + if (! is_null($class = $parameter->getDeclaringClass())) { + if ($name === 'self') { + return $class->getName(); + } + + if ($name === 'parent' && $parent = $class->getParentClass()) { + return $parent->getName(); + } + } + + return $name; + } + + /** + * Determine if the parameter's type is a subclass of the given type. + * + * @param \ReflectionParameter $parameter + * @param string $className + * @return bool + */ + public static function isParameterSubclassOf($parameter, $className) + { + $paramClassName = static::getParameterClassName($parameter); + + return ($paramClassName && class_exists($paramClassName)) + ? (new ReflectionClass($paramClassName))->isSubclassOf($className) + : false; + } +} diff --git a/src/Illuminate/Support/Traits/EnumeratesValues.php b/src/Illuminate/Support/Traits/EnumeratesValues.php index 17697e1ab127..a32b51803bc3 100644 --- a/src/Illuminate/Support/Traits/EnumeratesValues.php +++ b/src/Illuminate/Support/Traits/EnumeratesValues.php @@ -145,7 +145,7 @@ public function dd(...$args) { call_user_func_array([$this, 'dump'], $args); - die(1); + exit(1); } /** diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index a026ec633dd0..828c6e7f95b3 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -17,7 +17,7 @@ "php": "^7.2", "ext-json": "*", "ext-mbstring": "*", - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "illuminate/contracts": "^6.0", "nesbot/carbon": "^2.0" }, diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index 0961c7b8370c..8ee3f0aee3bd 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -98,6 +98,16 @@ protected function getFromLocalArray($attribute, $lowerRule, $source = null) // that is not attribute specific. If we find either we'll return it. foreach ($keys as $key) { foreach (array_keys($source) as $sourceKey) { + if (strpos($sourceKey, '*') !== false) { + $pattern = str_replace('\*', '([^.]*)', preg_quote($sourceKey, '#')); + + if (preg_match('#^'.$pattern.'\z#u', $key) === 1) { + return $source[$sourceKey]; + } + + continue; + } + if (Str::is($sourceKey, $key)) { return $source[$sourceKey]; } diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index abfb8bec02ac..bc340ddebf72 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -558,7 +558,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; } @@ -925,6 +925,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; } @@ -956,6 +960,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; } @@ -987,6 +995,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; } @@ -1018,6 +1030,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; } @@ -1489,11 +1505,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/DatabaseRule.php b/src/Illuminate/Validation/Rules/DatabaseRule.php index ab5e13d6468d..e9b110ba917f 100644 --- a/src/Illuminate/Validation/Rules/DatabaseRule.php +++ b/src/Illuminate/Validation/Rules/DatabaseRule.php @@ -62,11 +62,11 @@ public function resolveTableName($table) return $table; } - $model = new $table; + if (is_subclass_of($table, Model::class)) { + return (new $table)->getTable(); + } - return $model instanceof Model - ? $model->getTable() - : $table; + return $table; } /** diff --git a/src/Illuminate/Validation/ValidationRuleParser.php b/src/Illuminate/Validation/ValidationRuleParser.php index d0d0c906e79a..ed61c229b9c0 100644 --- a/src/Illuminate/Validation/ValidationRuleParser.php +++ b/src/Illuminate/Validation/ValidationRuleParser.php @@ -131,7 +131,7 @@ protected function explodeWildcardRules($results, $attribute, $rules) foreach ($data as $key => $value) { if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) { foreach ((array) $rules as $rule) { - $this->implicitAttributes[$attribute][] = strval($key); + $this->implicitAttributes[$attribute][] = (string) $key; $results = $this->mergeRules($results, $key, $rule); } diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 0476bfb7773a..df06c5077dbf 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -213,6 +213,13 @@ class Validator implements ValidatorContract */ protected $numericRules = ['Numeric', 'Integer']; + /** + * The current placeholder for dots in rule keys. + * + * @var string + */ + protected $dotPlaceholder; + /** * Create a new Validator instance. * @@ -226,6 +233,8 @@ class Validator implements ValidatorContract public function __construct(Translator $translator, array $data, array $rules, array $messages = [], array $customAttributes = []) { + $this->dotPlaceholder = Str::random(); + $this->initialRules = $rules; $this->translator = $translator; $this->customMessages = $messages; @@ -250,7 +259,11 @@ public function parseData(array $data) $value = $this->parseData($value); } - $key = str_replace(['.', '*'], ['->', '__asterisk__'], $key); + $key = str_replace( + ['.', '*'], + [$this->dotPlaceholder, '__asterisk__'], + $key + ); $newData[$key] = $value; } @@ -288,8 +301,6 @@ public function passes() // rule. Any error messages will be added to the containers with each of // the other error messages, returning true if we don't have messages. foreach ($this->rules as $attribute => $rules) { - $attribute = str_replace('\.', '->', $attribute); - if ($this->shouldBeExcluded($attribute)) { $this->removeAttribute($attribute); @@ -358,7 +369,8 @@ protected function shouldBeExcluded($attribute) */ protected function removeAttribute($attribute) { - unset($this->data[$attribute], $this->rules[$attribute]); + Arr::forget($this->data, $attribute); + Arr::forget($this->rules, $attribute); } /** @@ -908,6 +920,10 @@ public function getRules() */ public function setRules(array $rules) { + $rules = collect($rules)->mapWithKeys(function ($value, $key) { + return [str_replace('\.', $this->dotPlaceholder, $key) => $value]; + })->toArray(); + $this->initialRules = $rules; $this->rules = []; diff --git a/tests/Cache/CacheFileStoreTest.php b/tests/Cache/CacheFileStoreTest.php index f81ca162b2b6..aad8b7dd21ad 100755 --- a/tests/Cache/CacheFileStoreTest.php +++ b/tests/Cache/CacheFileStoreTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Carbon; +use Mockery as m; use PHPUnit\Framework\TestCase; class CacheFileStoreTest extends TestCase @@ -79,6 +80,27 @@ public function testStoreItemProperlyStoresValues() $this->assertTrue($result); } + public function testStoreItemProperlySetsPermissions() + { + $files = m::mock(Filesystem::class); + $files->shouldIgnoreMissing(); + $store = $this->getMockBuilder(FileStore::class)->setMethods(['expiration'])->setConstructorArgs([$files, __DIR__, 0644])->getMock(); + $hash = sha1('foo'); + $cache_dir = substr($hash, 0, 2).'/'.substr($hash, 2, 2); + $files->shouldReceive('put')->withArgs([__DIR__.'/'.$cache_dir.'/'.$hash, m::any(), m::any()])->andReturnUsing(function ($name, $value) { + return strlen($value); + }); + $files->shouldReceive('chmod')->withArgs([__DIR__.'/'.$cache_dir.'/'.$hash])->andReturnValues(['0600', '0644'])->times(3); + $files->shouldReceive('chmod')->withArgs([__DIR__.'/'.$cache_dir.'/'.$hash, 0644])->andReturn([true])->once(); + $result = $store->put('foo', 'foo', 10); + $this->assertTrue($result); + $result = $store->put('foo', 'bar', 10); + $this->assertTrue($result); + $result = $store->put('foo', 'baz', 10); + $this->assertTrue($result); + m::close(); + } + public function testForeversAreStoredWithHighTimestamp() { $files = $this->mockFilesystem(); diff --git a/tests/Console/ConsoleEventSchedulerTest.php b/tests/Console/ConsoleEventSchedulerTest.php index d772a45fcc00..a04a95081946 100644 --- a/tests/Console/ConsoleEventSchedulerTest.php +++ b/tests/Console/ConsoleEventSchedulerTest.php @@ -59,6 +59,9 @@ public function testExecCreatesNewCommand() $schedule->exec('path/to/command', ['--title' => 'A "real" test']); $schedule->exec('path/to/command', [['one', 'two']]); $schedule->exec('path/to/command', ['-1 minute']); + $schedule->exec('path/to/command', ['foo' => ['bar', 'baz']]); + $schedule->exec('path/to/command', ['--foo' => ['bar', 'baz']]); + $schedule->exec('path/to/command', ['-F' => ['bar', 'baz']]); $events = $schedule->events(); $this->assertSame('path/to/command', $events[0]->command); @@ -69,6 +72,9 @@ public function testExecCreatesNewCommand() $this->assertSame("path/to/command --title={$escape}A {$escapeReal}real{$escapeReal} test{$escape}", $events[5]->command); $this->assertSame("path/to/command {$escape}one{$escape} {$escape}two{$escape}", $events[6]->command); $this->assertSame("path/to/command {$escape}-1 minute{$escape}", $events[7]->command); + $this->assertSame("path/to/command {$escape}bar{$escape} {$escape}baz{$escape}", $events[8]->command); + $this->assertSame("path/to/command --foo={$escape}bar{$escape} --foo={$escape}baz{$escape}", $events[9]->command); + $this->assertSame("path/to/command -F {$escape}bar{$escape} -F {$escape}baz{$escape}", $events[10]->command); } public function testExecCreatesNewCommandWithTimezone() @@ -111,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/Console/Scheduling/EventTest.php b/tests/Console/Scheduling/EventTest.php index a579eead2746..a5b05a9dd787 100644 --- a/tests/Console/Scheduling/EventTest.php +++ b/tests/Console/Scheduling/EventTest.php @@ -14,22 +14,54 @@ protected function tearDown(): void m::close(); } - public function testBuildCommand() + public function testBuildCommandUsingUnix() { - $isWindows = DIRECTORY_SEPARATOR == '\\'; - $quote = ($isWindows) ? '"' : "'"; + if (windows_os()) { + $this->markTestSkipped('Skipping since operating system is Windows'); + } $event = new Event(m::mock(EventMutex::class), 'php -i'); - $defaultOutput = ($isWindows) ? 'NUL' : '/dev/null'; - $this->assertSame("php -i > {$quote}{$defaultOutput}{$quote} 2>&1", $event->buildCommand()); + $this->assertSame("php -i > '/dev/null' 2>&1", $event->buildCommand()); + } + + public function testBuildCommandUsingWindows() + { + if (! windows_os()) { + $this->markTestSkipped('Skipping since operating system is not Windows'); + } + + $event = new Event(m::mock(EventMutex::class), 'php -i'); + + $this->assertSame('php -i > "NUL" 2>&1', $event->buildCommand()); + } + + public function testBuildCommandInBackgroundUsingUnix() + { + if (windows_os()) { + $this->markTestSkipped('Skipping since operating system is Windows'); + } $event = new Event(m::mock(EventMutex::class), 'php -i'); $event->runInBackground(); - $commandSeparator = ($isWindows ? '&' : ';'); $scheduleId = '"framework'.DIRECTORY_SEPARATOR.'schedule-eeb46c93d45e928d62aaf684d727e213b7094822"'; - $this->assertSame("(php -i > {$quote}{$defaultOutput}{$quote} 2>&1 {$commandSeparator} {$quote}".PHP_BINARY."{$quote} artisan schedule:finish {$scheduleId}) > {$quote}{$defaultOutput}{$quote} 2>&1 &", $event->buildCommand()); + + $this->assertSame("(php -i > '/dev/null' 2>&1 ; '".PHP_BINARY."' artisan schedule:finish {$scheduleId} \"$?\") > '/dev/null' 2>&1 &", $event->buildCommand()); + } + + public function testBuildCommandInBackgroundUsingWindows() + { + if (! windows_os()) { + $this->markTestSkipped('Skipping since operating system is not Windows'); + } + + $event = new Event(m::mock(EventMutex::class), 'php -i'); + $event->runInBackground(); + + $scheduleId = '"framework'.DIRECTORY_SEPARATOR.'schedule-eeb46c93d45e928d62aaf684d727e213b7094822"'; + + $this->assertSame('start /b cmd /c "(php -i & "'.PHP_BINARY.'" artisan schedule:finish '.$scheduleId.' "%errorlevel%") > "NUL" 2>&1"', $event->buildCommand()); } public function testBuildCommandSendOutputTo() diff --git a/tests/Database/DatabaseConnectionTest.php b/tests/Database/DatabaseConnectionTest.php index cee57b601fd5..ae9ab5007961 100755 --- a/tests/Database/DatabaseConnectionTest.php +++ b/tests/Database/DatabaseConnectionTest.php @@ -150,8 +150,9 @@ public function testTransactionLevelNotIncrementedOnTransactionException() public function testBeginTransactionMethodRetriesOnFailure() { $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class); - $pdo->expects($this->exactly(2))->method('beginTransaction'); - $pdo->expects($this->at(0))->method('beginTransaction')->will($this->throwException(new ErrorException('server has gone away'))); + $pdo->expects($this->at(0)) + ->method('beginTransaction') + ->will($this->throwException(new ErrorException('server has gone away'))); $connection = $this->getMockConnection(['reconnect'], $pdo); $connection->expects($this->once())->method('reconnect'); $connection->beginTransaction(); diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index d772a8477a30..0b4530802a9d 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']); @@ -836,7 +842,7 @@ public function testHasWithConstraintsWithOrWhereAndHavingInSubquery() $this->assertEquals(['larry', '90210', '90220', 'fooside dr', 29], $builder->getBindings()); } - public function testHasWithContraintsAndJoinAndHavingInSubquery() + public function testHasWithConstraintsAndJoinAndHavingInSubquery() { $model = new EloquentBuilderTestModelParentStub; $builder = $model->where('bar', 'baz'); @@ -1017,11 +1023,39 @@ 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); + } + + /** @group Foo */ + 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(); @@ -1048,6 +1082,33 @@ 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); + } + + /** @group Foo */ + 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(); @@ -1056,6 +1117,7 @@ public function testWhereKeyNotMethodWithInt() $int = 1; + $model->shouldReceive('getKeyType')->once()->andReturn('int'); $builder->getQuery()->shouldReceive('where')->once()->with($keyName, '!=', $int); $builder->whereKeyNot($int); @@ -1414,3 +1476,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 8694b77ee680..cd3c4e488da0 100755 --- a/tests/Database/DatabaseEloquentCollectionTest.php +++ b/tests/Database/DatabaseEloquentCollectionTest.php @@ -422,6 +422,24 @@ public function testQueueableCollectionImplementationThrowsExceptionOnMultipleMo $c->getQueueableClass(); } + public function testQueueableRelationshipsReturnsOnlyRelationsCommonToAllModels() + { + // This is needed to prevent loading non-existing relationships on polymorphic model collections (#26126) + $c = new Collection([new class { + public function getQueueableRelations() + { + return ['user']; + } + }, new class { + public function getQueueableRelations() + { + return ['user', 'comments']; + } + }]); + + $this->assertEquals(['user'], $c->getQueueableRelations()); + } + public function testEmptyCollectionStayEmptyOnFresh() { $c = new Collection; diff --git a/tests/Database/DatabaseEloquentIntegrationTest.php b/tests/Database/DatabaseEloquentIntegrationTest.php index 492c9f885db8..957652ef0a20 100644 --- a/tests/Database/DatabaseEloquentIntegrationTest.php +++ b/tests/Database/DatabaseEloquentIntegrationTest.php @@ -1179,6 +1179,23 @@ public function testMorphToRelationsAcrossDatabaseConnections() $this->assertInstanceOf(EloquentTestItem::class, $item); } + public function testEagerLoadedMorphToRelationsOnAnotherDatabaseConnection() + { + EloquentTestPost::create(['id' => 1, 'name' => 'Default Connection Post', 'user_id' => 1]); + EloquentTestPhoto::create(['id' => 1, 'imageable_type' => EloquentTestPost::class, 'imageable_id' => 1, 'name' => 'Photo']); + + EloquentTestPost::on('second_connection') + ->create(['id' => 1, 'name' => 'Second Connection Post', 'user_id' => 1]); + EloquentTestPhoto::on('second_connection') + ->create(['id' => 1, 'imageable_type' => EloquentTestPost::class, 'imageable_id' => 1, 'name' => 'Photo']); + + $defaultConnectionPost = EloquentTestPhoto::with('imageable')->first()->imageable; + $secondConnectionPost = EloquentTestPhoto::on('second_connection')->with('imageable')->first()->imageable; + + $this->assertEquals($defaultConnectionPost->name, 'Default Connection Post'); + $this->assertEquals($secondConnectionPost->name, 'Second Connection Post'); + } + public function testBelongsToManyCustomPivot() { $john = EloquentTestUserWithCustomFriendPivot::create(['id' => 1, 'name' => 'John Doe', 'email' => 'johndoe@example.com']); diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index b5c0bc8689f2..acb9598ea8c8 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -10,6 +10,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; @@ -91,6 +92,16 @@ public function testDirtyAttributes() $this->assertTrue($model->isDirty(['foo', 'bar'])); } + public function testFloatAndNullComparisonWhenDirty() + { + $model = new EloquentModelCastingStub(); + $model->floatAttribute = null; + $model->syncOriginal(); + $this->assertFalse($model->isDirty('floatAttribute')); + $model->forceFill(['floatAttribute' => 0.0]); + $this->assertTrue($model->isDirty('floatAttribute')); + } + public function testDirtyOnCastOrDateAttributes() { $model = new EloquentModelCastingStub; @@ -148,6 +159,19 @@ public function testCleanAttributes() $this->assertFalse($model->isClean(['foo', 'bar'])); } + public function testCleanWhenFloatUpdateAttribute() + { + // test is equivalent + $model = new EloquentModelStub(['castedFloat' => 8 - 6.4]); + $model->syncOriginal(); + $this->assertTrue($model->originalIsEquivalent('castedFloat', 1.6)); + + // test is not equivalent + $model = new EloquentModelStub(['castedFloat' => 5.6]); + $model->syncOriginal(); + $this->assertFalse($model->originalIsEquivalent('castedFloat', 5.5)); + } + public function testCalculatedAttributes() { $model = new EloquentModelStub; @@ -991,11 +1015,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() @@ -1992,6 +2026,7 @@ class EloquentModelStub extends Model protected $table = 'stub'; protected $guarded = []; protected $morph_to_stub_type = EloquentModelSaveStub::class; + protected $casts = ['castedFloat' => 'float']; public function getListItemsAttribute($value) { @@ -2110,7 +2145,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/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 070cb9b8eaec..87f7268a565b 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -2622,8 +2622,8 @@ public function testMySqlWrappingJsonWithBooleanAndIntegerThatLooksLikeOne() public function testJsonPathEscaping() { - $expectedWithJsonEscaped = <<getMySqlBuilder(); diff --git a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php b/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php index 4ab4eb8a8510..3fb7300a7b5b 100644 --- a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php +++ b/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php @@ -60,7 +60,7 @@ public function testRenamingAndChangingColumnsWork() $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE BINARY, age INTEGER NOT NULL COLLATE BINARY)', + 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE BINARY, age INTEGER NOT NULL)', 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users', 'DROP TABLE __temp__users', 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', @@ -266,7 +266,7 @@ public function testAddUniqueIndexWithNameWorks() $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL COLLATE BINARY)', + 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', 'INSERT INTO users (name) SELECT name FROM __temp__users', 'DROP TABLE __temp__users', 'alter table "users" add constraint "index1" unique ("name")', @@ -283,7 +283,7 @@ public function testAddUniqueIndexWithNameWorks() $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL COLLATE BINARY)', + 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', 'INSERT INTO users (name) SELECT name FROM __temp__users', 'DROP TABLE __temp__users', 'create unique index "index1" on "users" ("name")', @@ -300,7 +300,7 @@ public function testAddUniqueIndexWithNameWorks() $expected = [ 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL COLLATE BINARY)', + 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', 'INSERT INTO users (name) SELECT name FROM __temp__users', 'DROP TABLE __temp__users', 'create unique index "index1" on "users" ("name")', diff --git a/tests/Filesystem/FilesystemAdapterTest.php b/tests/Filesystem/FilesystemAdapterTest.php index e267863fa505..e7dffba0b76f 100644 --- a/tests/Filesystem/FilesystemAdapterTest.php +++ b/tests/Filesystem/FilesystemAdapterTest.php @@ -236,8 +236,10 @@ public function testPutWithStreamInterface() $spy = m::spy($this->filesystem); $filesystemAdapter = new FilesystemAdapter($spy); - $stream = new Stream(fopen($this->tempDir.'/foo.txt', 'r')); - $filesystemAdapter->put('bar.txt', $stream); + $stream = fopen($this->tempDir.'/foo.txt', 'r'); + $guzzleStream = new Stream($stream); + $filesystemAdapter->put('bar.txt', $guzzleStream); + fclose($stream); $spy->shouldHaveReceived('putStream'); $this->assertEquals('some-data', $filesystemAdapter->get('bar.txt')); diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php index ff9419320225..ed4e5750f52f 100755 --- a/tests/Filesystem/FilesystemTest.php +++ b/tests/Filesystem/FilesystemTest.php @@ -13,12 +13,25 @@ class FilesystemTest extends TestCase { - private $tempDir; + private static $tempDir; - protected function setUp(): void + /** + * @beforeClass + */ + public static function setUpTempDir() { - $this->tempDir = __DIR__.'/tmp'; - mkdir($this->tempDir); + self::$tempDir = __DIR__.'/tmp'; + mkdir(self::$tempDir); + } + + /** + * @afterClass + */ + public static function tearDownTempDir() + { + $files = new Filesystem; + $files->deleteDirectory(self::$tempDir); + self::$tempDir = null; } protected function tearDown(): void @@ -26,27 +39,41 @@ protected function tearDown(): void m::close(); $files = new Filesystem; - $files->deleteDirectory($this->tempDir); + $files->deleteDirectory(self::$tempDir, $preserve = true); } public function testGetRetrievesFiles() { - file_put_contents($this->tempDir.'/file.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/file.txt', 'Hello World'); $files = new Filesystem; - $this->assertSame('Hello World', $files->get($this->tempDir.'/file.txt')); + $this->assertSame('Hello World', $files->get(self::$tempDir.'/file.txt')); } public function testPutStoresFiles() { $files = new Filesystem; - $files->put($this->tempDir.'/file.txt', 'Hello World'); - $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Hello World'); + $files->put(self::$tempDir.'/file.txt', 'Hello World'); + $this->assertStringEqualsFile(self::$tempDir.'/file.txt', 'Hello World'); + } + + public function testReplaceCreatesFile() + { + $tempFile = self::$tempDir.'/file.txt'; + + $filesystem = new Filesystem; + + $filesystem->replace($tempFile, 'Hello World'); + $this->assertStringEqualsFile($tempFile, 'Hello World'); } - public function testReplaceStoresFiles() + public function testReplaceWhenUnixSymlinkExists() { - $tempFile = "{$this->tempDir}/file.txt"; - $symlinkDir = "{$this->tempDir}/symlink_dir"; + if (windows_os()) { + $this->markTestSkipped('The operating system is Windows'); + } + + $tempFile = self::$tempDir.'/file.txt'; + $symlinkDir = self::$tempDir.'/symlink_dir'; $symlink = "{$symlinkDir}/symlink.txt"; mkdir($symlinkDir); @@ -84,94 +111,94 @@ public function testReplaceStoresFiles() public function testSetChmod() { - file_put_contents($this->tempDir.'/file.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/file.txt', 'Hello World'); $files = new Filesystem; - $files->chmod($this->tempDir.'/file.txt', 0755); - $filePermission = substr(sprintf('%o', fileperms($this->tempDir.'/file.txt')), -4); + $files->chmod(self::$tempDir.'/file.txt', 0755); + $filePermission = substr(sprintf('%o', fileperms(self::$tempDir.'/file.txt')), -4); $expectedPermissions = DIRECTORY_SEPARATOR == '\\' ? '0666' : '0755'; $this->assertEquals($expectedPermissions, $filePermission); } public function testGetChmod() { - file_put_contents($this->tempDir.'/file.txt', 'Hello World'); - chmod($this->tempDir.'/file.txt', 0755); + file_put_contents(self::$tempDir.'/file.txt', 'Hello World'); + chmod(self::$tempDir.'/file.txt', 0755); $files = new Filesystem; - $filePermission = $files->chmod($this->tempDir.'/file.txt'); + $filePermission = $files->chmod(self::$tempDir.'/file.txt'); $expectedPermissions = DIRECTORY_SEPARATOR == '\\' ? '0666' : '0755'; $this->assertEquals($expectedPermissions, $filePermission); } public function testDeleteRemovesFiles() { - file_put_contents($this->tempDir.'/file1.txt', 'Hello World'); - file_put_contents($this->tempDir.'/file2.txt', 'Hello World'); - file_put_contents($this->tempDir.'/file3.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/file1.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/file2.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/file3.txt', 'Hello World'); $files = new Filesystem; - $files->delete($this->tempDir.'/file1.txt'); - Assert::assertFileDoesNotExist($this->tempDir.'/file1.txt'); + $files->delete(self::$tempDir.'/file1.txt'); + Assert::assertFileDoesNotExist(self::$tempDir.'/file1.txt'); - $files->delete([$this->tempDir.'/file2.txt', $this->tempDir.'/file3.txt']); - Assert::assertFileDoesNotExist($this->tempDir.'/file2.txt'); - Assert::assertFileDoesNotExist($this->tempDir.'/file3.txt'); + $files->delete([self::$tempDir.'/file2.txt', self::$tempDir.'/file3.txt']); + Assert::assertFileDoesNotExist(self::$tempDir.'/file2.txt'); + Assert::assertFileDoesNotExist(self::$tempDir.'/file3.txt'); } public function testPrependExistingFiles() { $files = new Filesystem; - $files->put($this->tempDir.'/file.txt', 'World'); - $files->prepend($this->tempDir.'/file.txt', 'Hello '); - $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Hello World'); + $files->put(self::$tempDir.'/file.txt', 'World'); + $files->prepend(self::$tempDir.'/file.txt', 'Hello '); + $this->assertStringEqualsFile(self::$tempDir.'/file.txt', 'Hello World'); } public function testPrependNewFiles() { $files = new Filesystem; - $files->prepend($this->tempDir.'/file.txt', 'Hello World'); - $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Hello World'); + $files->prepend(self::$tempDir.'/file.txt', 'Hello World'); + $this->assertStringEqualsFile(self::$tempDir.'/file.txt', 'Hello World'); } public function testMissingFile() { $files = new Filesystem; - $this->assertTrue($files->missing($this->tempDir.'/file.txt')); + $this->assertTrue($files->missing(self::$tempDir.'/file.txt')); } public function testDeleteDirectory() { - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/file.txt', 'Hello World'); + mkdir(self::$tempDir.'/foo'); + file_put_contents(self::$tempDir.'/foo/file.txt', 'Hello World'); $files = new Filesystem; - $files->deleteDirectory($this->tempDir.'/foo'); - Assert::assertDirectoryDoesNotExist($this->tempDir.'/foo'); - Assert::assertFileDoesNotExist($this->tempDir.'/foo/file.txt'); + $files->deleteDirectory(self::$tempDir.'/foo'); + Assert::assertDirectoryDoesNotExist(self::$tempDir.'/foo'); + Assert::assertFileDoesNotExist(self::$tempDir.'/foo/file.txt'); } public function testDeleteDirectoryReturnFalseWhenNotADirectory() { - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/file.txt', 'Hello World'); + mkdir(self::$tempDir.'/bar'); + file_put_contents(self::$tempDir.'/bar/file.txt', 'Hello World'); $files = new Filesystem; - $this->assertFalse($files->deleteDirectory($this->tempDir.'/foo/file.txt')); + $this->assertFalse($files->deleteDirectory(self::$tempDir.'/bar/file.txt')); } public function testCleanDirectory() { - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/file.txt', 'Hello World'); + mkdir(self::$tempDir.'/baz'); + file_put_contents(self::$tempDir.'/baz/file.txt', 'Hello World'); $files = new Filesystem; - $files->cleanDirectory($this->tempDir.'/foo'); - $this->assertDirectoryExists($this->tempDir.'/foo'); - Assert::assertFileDoesNotExist($this->tempDir.'/foo/file.txt'); + $files->cleanDirectory(self::$tempDir.'/baz'); + $this->assertDirectoryExists(self::$tempDir.'/baz'); + Assert::assertFileDoesNotExist(self::$tempDir.'/baz/file.txt'); } public function testMacro() { - file_put_contents($this->tempDir.'/foo.txt', 'Hello World'); + file_put_contents(self::$tempDir.'/foo.txt', 'Hello World'); $files = new Filesystem; - $tempDir = $this->tempDir; + $tempDir = self::$tempDir; $files->macro('getFoo', function () use ($files, $tempDir) { return $files->get($tempDir.'/foo.txt'); }); @@ -180,12 +207,12 @@ public function testMacro() public function testFilesMethod() { - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/1.txt', '1'); - file_put_contents($this->tempDir.'/foo/2.txt', '2'); - mkdir($this->tempDir.'/foo/bar'); + mkdir(self::$tempDir.'/views'); + file_put_contents(self::$tempDir.'/views/1.txt', '1'); + file_put_contents(self::$tempDir.'/views/2.txt', '2'); + mkdir(self::$tempDir.'/views/_layouts'); $files = new Filesystem; - $results = $files->files($this->tempDir.'/foo'); + $results = $files->files(self::$tempDir.'/views'); $this->assertInstanceOf(SplFileInfo::class, $results[0]); $this->assertInstanceOf(SplFileInfo::class, $results[1]); unset($files); @@ -194,76 +221,76 @@ public function testFilesMethod() public function testCopyDirectoryReturnsFalseIfSourceIsntDirectory() { $files = new Filesystem; - $this->assertFalse($files->copyDirectory($this->tempDir.'/foo/bar/baz/breeze/boom', $this->tempDir)); + $this->assertFalse($files->copyDirectory(self::$tempDir.'/breeze/boom/foo/bar/baz', self::$tempDir)); } public function testCopyDirectoryMovesEntireDirectory() { - mkdir($this->tempDir.'/tmp', 0777, true); - file_put_contents($this->tempDir.'/tmp/foo.txt', ''); - file_put_contents($this->tempDir.'/tmp/bar.txt', ''); - mkdir($this->tempDir.'/tmp/nested', 0777, true); - file_put_contents($this->tempDir.'/tmp/nested/baz.txt', ''); + mkdir(self::$tempDir.'/tmp', 0777, true); + file_put_contents(self::$tempDir.'/tmp/foo.txt', ''); + file_put_contents(self::$tempDir.'/tmp/bar.txt', ''); + mkdir(self::$tempDir.'/tmp/nested', 0777, true); + file_put_contents(self::$tempDir.'/tmp/nested/baz.txt', ''); $files = new Filesystem; - $files->copyDirectory($this->tempDir.'/tmp', $this->tempDir.'/tmp2'); - $this->assertDirectoryExists($this->tempDir.'/tmp2'); - $this->assertFileExists($this->tempDir.'/tmp2/foo.txt'); - $this->assertFileExists($this->tempDir.'/tmp2/bar.txt'); - $this->assertDirectoryExists($this->tempDir.'/tmp2/nested'); - $this->assertFileExists($this->tempDir.'/tmp2/nested/baz.txt'); + $files->copyDirectory(self::$tempDir.'/tmp', self::$tempDir.'/tmp2'); + $this->assertDirectoryExists(self::$tempDir.'/tmp2'); + $this->assertFileExists(self::$tempDir.'/tmp2/foo.txt'); + $this->assertFileExists(self::$tempDir.'/tmp2/bar.txt'); + $this->assertDirectoryExists(self::$tempDir.'/tmp2/nested'); + $this->assertFileExists(self::$tempDir.'/tmp2/nested/baz.txt'); } public function testMoveDirectoryMovesEntireDirectory() { - mkdir($this->tempDir.'/tmp', 0777, true); - file_put_contents($this->tempDir.'/tmp/foo.txt', ''); - file_put_contents($this->tempDir.'/tmp/bar.txt', ''); - mkdir($this->tempDir.'/tmp/nested', 0777, true); - file_put_contents($this->tempDir.'/tmp/nested/baz.txt', ''); + mkdir(self::$tempDir.'/tmp2', 0777, true); + file_put_contents(self::$tempDir.'/tmp2/foo.txt', ''); + file_put_contents(self::$tempDir.'/tmp2/bar.txt', ''); + mkdir(self::$tempDir.'/tmp2/nested', 0777, true); + file_put_contents(self::$tempDir.'/tmp2/nested/baz.txt', ''); $files = new Filesystem; - $files->moveDirectory($this->tempDir.'/tmp', $this->tempDir.'/tmp2'); - $this->assertDirectoryExists($this->tempDir.'/tmp2'); - $this->assertFileExists($this->tempDir.'/tmp2/foo.txt'); - $this->assertFileExists($this->tempDir.'/tmp2/bar.txt'); - $this->assertDirectoryExists($this->tempDir.'/tmp2/nested'); - $this->assertFileExists($this->tempDir.'/tmp2/nested/baz.txt'); - Assert::assertDirectoryDoesNotExist($this->tempDir.'/tmp'); + $files->moveDirectory(self::$tempDir.'/tmp2', self::$tempDir.'/tmp3'); + $this->assertDirectoryExists(self::$tempDir.'/tmp3'); + $this->assertFileExists(self::$tempDir.'/tmp3/foo.txt'); + $this->assertFileExists(self::$tempDir.'/tmp3/bar.txt'); + $this->assertDirectoryExists(self::$tempDir.'/tmp3/nested'); + $this->assertFileExists(self::$tempDir.'/tmp3/nested/baz.txt'); + Assert::assertDirectoryDoesNotExist(self::$tempDir.'/tmp2'); } public function testMoveDirectoryMovesEntireDirectoryAndOverwrites() { - mkdir($this->tempDir.'/tmp', 0777, true); - file_put_contents($this->tempDir.'/tmp/foo.txt', ''); - file_put_contents($this->tempDir.'/tmp/bar.txt', ''); - mkdir($this->tempDir.'/tmp/nested', 0777, true); - file_put_contents($this->tempDir.'/tmp/nested/baz.txt', ''); - mkdir($this->tempDir.'/tmp2', 0777, true); - file_put_contents($this->tempDir.'/tmp2/foo2.txt', ''); - file_put_contents($this->tempDir.'/tmp2/bar2.txt', ''); + mkdir(self::$tempDir.'/tmp4', 0777, true); + file_put_contents(self::$tempDir.'/tmp4/foo.txt', ''); + file_put_contents(self::$tempDir.'/tmp4/bar.txt', ''); + mkdir(self::$tempDir.'/tmp4/nested', 0777, true); + file_put_contents(self::$tempDir.'/tmp4/nested/baz.txt', ''); + mkdir(self::$tempDir.'/tmp5', 0777, true); + file_put_contents(self::$tempDir.'/tmp5/foo2.txt', ''); + file_put_contents(self::$tempDir.'/tmp5/bar2.txt', ''); $files = new Filesystem; - $files->moveDirectory($this->tempDir.'/tmp', $this->tempDir.'/tmp2', true); - $this->assertDirectoryExists($this->tempDir.'/tmp2'); - $this->assertFileExists($this->tempDir.'/tmp2/foo.txt'); - $this->assertFileExists($this->tempDir.'/tmp2/bar.txt'); - $this->assertDirectoryExists($this->tempDir.'/tmp2/nested'); - $this->assertFileExists($this->tempDir.'/tmp2/nested/baz.txt'); - Assert::assertFileDoesNotExist($this->tempDir.'/tmp2/foo2.txt'); - Assert::assertFileDoesNotExist($this->tempDir.'/tmp2/bar2.txt'); - Assert::assertDirectoryDoesNotExist($this->tempDir.'/tmp'); + $files->moveDirectory(self::$tempDir.'/tmp4', self::$tempDir.'/tmp5', true); + $this->assertDirectoryExists(self::$tempDir.'/tmp5'); + $this->assertFileExists(self::$tempDir.'/tmp5/foo.txt'); + $this->assertFileExists(self::$tempDir.'/tmp5/bar.txt'); + $this->assertDirectoryExists(self::$tempDir.'/tmp5/nested'); + $this->assertFileExists(self::$tempDir.'/tmp5/nested/baz.txt'); + Assert::assertFileDoesNotExist(self::$tempDir.'/tmp5/foo2.txt'); + Assert::assertFileDoesNotExist(self::$tempDir.'/tmp5/bar2.txt'); + Assert::assertDirectoryDoesNotExist(self::$tempDir.'/tmp4'); } public function testMoveDirectoryReturnsFalseWhileOverwritingAndUnableToDeleteDestinationDirectory() { - mkdir($this->tempDir.'/tmp', 0777, true); - file_put_contents($this->tempDir.'/tmp/foo.txt', ''); - mkdir($this->tempDir.'/tmp2', 0777, true); + mkdir(self::$tempDir.'/tmp6', 0777, true); + file_put_contents(self::$tempDir.'/tmp6/foo.txt', ''); + mkdir(self::$tempDir.'/tmp7', 0777, true); $files = m::mock(Filesystem::class)->makePartial(); $files->shouldReceive('deleteDirectory')->once()->andReturn(false); - $this->assertFalse($files->moveDirectory($this->tempDir.'/tmp', $this->tempDir.'/tmp2', true)); + $this->assertFalse($files->moveDirectory(self::$tempDir.'/tmp6', self::$tempDir.'/tmp7', true)); } public function testGetThrowsExceptionNonexisitingFile() @@ -271,14 +298,14 @@ public function testGetThrowsExceptionNonexisitingFile() $this->expectException(FileNotFoundException::class); $files = new Filesystem; - $files->get($this->tempDir.'/unknown-file.txt'); + $files->get(self::$tempDir.'/unknown-file.txt'); } public function testGetRequireReturnsProperly() { - file_put_contents($this->tempDir.'/file.php', ''); + file_put_contents(self::$tempDir.'/file.php', ''); $files = new Filesystem; - $this->assertSame('Howdy?', $files->getRequire($this->tempDir.'/file.php')); + $this->assertSame('Howdy?', $files->getRequire(self::$tempDir.'/file.php')); } public function testGetRequireThrowsExceptionNonExistingFile() @@ -286,75 +313,75 @@ public function testGetRequireThrowsExceptionNonExistingFile() $this->expectException(FileNotFoundException::class); $files = new Filesystem; - $files->getRequire($this->tempDir.'/file.php'); + $files->getRequire(self::$tempDir.'/file.php'); } public function testAppendAddsDataToFile() { - file_put_contents($this->tempDir.'/file.txt', 'foo'); + file_put_contents(self::$tempDir.'/file.txt', 'foo'); $files = new Filesystem; - $bytesWritten = $files->append($this->tempDir.'/file.txt', 'bar'); + $bytesWritten = $files->append(self::$tempDir.'/file.txt', 'bar'); $this->assertEquals(mb_strlen('bar', '8bit'), $bytesWritten); - $this->assertFileExists($this->tempDir.'/file.txt'); - $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'foobar'); + $this->assertFileExists(self::$tempDir.'/file.txt'); + $this->assertStringEqualsFile(self::$tempDir.'/file.txt', 'foobar'); } public function testMoveMovesFiles() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $files->move($this->tempDir.'/foo.txt', $this->tempDir.'/bar.txt'); - $this->assertFileExists($this->tempDir.'/bar.txt'); - Assert::assertFileDoesNotExist($this->tempDir.'/foo.txt'); + $files->move(self::$tempDir.'/foo.txt', self::$tempDir.'/bar.txt'); + $this->assertFileExists(self::$tempDir.'/bar.txt'); + Assert::assertFileDoesNotExist(self::$tempDir.'/foo.txt'); } public function testNameReturnsName() { - file_put_contents($this->tempDir.'/foobar.txt', 'foo'); + file_put_contents(self::$tempDir.'/foobar.txt', 'foo'); $filesystem = new Filesystem; - $this->assertSame('foobar', $filesystem->name($this->tempDir.'/foobar.txt')); + $this->assertSame('foobar', $filesystem->name(self::$tempDir.'/foobar.txt')); } public function testExtensionReturnsExtension() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertSame('txt', $files->extension($this->tempDir.'/foo.txt')); + $this->assertSame('txt', $files->extension(self::$tempDir.'/foo.txt')); } public function testBasenameReturnsBasename() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertSame('foo.txt', $files->basename($this->tempDir.'/foo.txt')); + $this->assertSame('foo.txt', $files->basename(self::$tempDir.'/foo.txt')); } public function testDirnameReturnsDirectory() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertEquals($this->tempDir, $files->dirname($this->tempDir.'/foo.txt')); + $this->assertEquals(self::$tempDir, $files->dirname(self::$tempDir.'/foo.txt')); } public function testTypeIdentifiesFile() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertSame('file', $files->type($this->tempDir.'/foo.txt')); + $this->assertSame('file', $files->type(self::$tempDir.'/foo.txt')); } public function testTypeIdentifiesDirectory() { - mkdir($this->tempDir.'/foo'); + mkdir(self::$tempDir.'/foo-dir'); $files = new Filesystem; - $this->assertSame('dir', $files->type($this->tempDir.'/foo')); + $this->assertSame('dir', $files->type(self::$tempDir.'/foo-dir')); } public function testSizeOutputsSize() { - $size = file_put_contents($this->tempDir.'/foo.txt', 'foo'); + $size = file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertEquals($size, $files->size($this->tempDir.'/foo.txt')); + $this->assertEquals($size, $files->size(self::$tempDir.'/foo.txt')); } /** @@ -362,54 +389,54 @@ public function testSizeOutputsSize() */ public function testMimeTypeOutputsMimeType() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - $this->assertSame('text/plain', $files->mimeType($this->tempDir.'/foo.txt')); + $this->assertSame('text/plain', $files->mimeType(self::$tempDir.'/foo.txt')); } public function testIsWritable() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; - @chmod($this->tempDir.'/foo.txt', 0444); - $this->assertFalse($files->isWritable($this->tempDir.'/foo.txt')); - @chmod($this->tempDir.'/foo.txt', 0777); - $this->assertTrue($files->isWritable($this->tempDir.'/foo.txt')); + @chmod(self::$tempDir.'/foo.txt', 0444); + $this->assertFalse($files->isWritable(self::$tempDir.'/foo.txt')); + @chmod(self::$tempDir.'/foo.txt', 0777); + $this->assertTrue($files->isWritable(self::$tempDir.'/foo.txt')); } public function testIsReadable() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $files = new Filesystem; // chmod is noneffective on Windows if (DIRECTORY_SEPARATOR === '\\') { - $this->assertTrue($files->isReadable($this->tempDir.'/foo.txt')); + $this->assertTrue($files->isReadable(self::$tempDir.'/foo.txt')); } else { - @chmod($this->tempDir.'/foo.txt', 0000); - $this->assertFalse($files->isReadable($this->tempDir.'/foo.txt')); - @chmod($this->tempDir.'/foo.txt', 0777); - $this->assertTrue($files->isReadable($this->tempDir.'/foo.txt')); + @chmod(self::$tempDir.'/foo.txt', 0000); + $this->assertFalse($files->isReadable(self::$tempDir.'/foo.txt')); + @chmod(self::$tempDir.'/foo.txt', 0777); + $this->assertTrue($files->isReadable(self::$tempDir.'/foo.txt')); } - $this->assertFalse($files->isReadable($this->tempDir.'/doesnotexist.txt')); + $this->assertFalse($files->isReadable(self::$tempDir.'/doesnotexist.txt')); } public function testGlobFindsFiles() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); - file_put_contents($this->tempDir.'/bar.txt', 'bar'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/bar.txt', 'bar'); $files = new Filesystem; - $glob = $files->glob($this->tempDir.'/*.txt'); - $this->assertContains($this->tempDir.'/foo.txt', $glob); - $this->assertContains($this->tempDir.'/bar.txt', $glob); + $glob = $files->glob(self::$tempDir.'/*.txt'); + $this->assertContains(self::$tempDir.'/foo.txt', $glob); + $this->assertContains(self::$tempDir.'/bar.txt', $glob); } public function testAllFilesFindsFiles() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); - file_put_contents($this->tempDir.'/bar.txt', 'bar'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/bar.txt', 'bar'); $files = new Filesystem; $allFiles = []; - foreach ($files->allFiles($this->tempDir) as $file) { + foreach ($files->allFiles(self::$tempDir) as $file) { $allFiles[] = $file->getFilename(); } $this->assertContains('foo.txt', $allFiles); @@ -418,32 +445,29 @@ public function testAllFilesFindsFiles() public function testDirectoriesFindsDirectories() { - mkdir($this->tempDir.'/foo'); - mkdir($this->tempDir.'/bar'); + mkdir(self::$tempDir.'/film'); + mkdir(self::$tempDir.'/music'); $files = new Filesystem; - $directories = $files->directories($this->tempDir); - $this->assertContains($this->tempDir.DIRECTORY_SEPARATOR.'foo', $directories); - $this->assertContains($this->tempDir.DIRECTORY_SEPARATOR.'bar', $directories); + $directories = $files->directories(self::$tempDir); + $this->assertContains(self::$tempDir.DIRECTORY_SEPARATOR.'film', $directories); + $this->assertContains(self::$tempDir.DIRECTORY_SEPARATOR.'music', $directories); } public function testMakeDirectory() { $files = new Filesystem; - $this->assertTrue($files->makeDirectory($this->tempDir.'/foo')); - $this->assertFileExists($this->tempDir.'/foo'); + $this->assertTrue($files->makeDirectory(self::$tempDir.'/created')); + $this->assertFileExists(self::$tempDir.'/created'); } /** * @requires extension pcntl + * @requires function pcntl_fork */ public function testSharedGet() { if (PHP_OS == 'Darwin') { - $this->markTestSkipped('Skipping on MacOS'); - } - - if (! function_exists('pcntl_fork')) { - $this->markTestSkipped('Skipping since the pcntl extension is not available'); + $this->markTestSkipped('The operating system is MacOS.'); } $content = str_repeat('123456', 1000000); @@ -456,8 +480,8 @@ public function testSharedGet() if (! $pid) { $files = new Filesystem; - $files->put($this->tempDir.'/file.txt', $content, true); - $read = $files->get($this->tempDir.'/file.txt', true); + $files->put(self::$tempDir.'/file.txt', $content, true); + $read = $files->get(self::$tempDir.'/file.txt', true); exit(strlen($read) === strlen($content) ? 1 : 0); } @@ -468,17 +492,17 @@ public function testSharedGet() $result *= $status; } - $this->assertTrue($result === 1); + $this->assertSame(1, $result); } public function testRequireOnceRequiresFileProperly() { $filesystem = new Filesystem; - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/foo.php', 'requireOnce($this->tempDir.'/foo/foo.php'); - file_put_contents($this->tempDir.'/foo/foo.php', 'requireOnce($this->tempDir.'/foo/foo.php'); + mkdir(self::$tempDir.'/scripts'); + file_put_contents(self::$tempDir.'/scripts/foo.php', 'requireOnce(self::$tempDir.'/scripts/foo.php'); + file_put_contents(self::$tempDir.'/scripts/foo.php', 'requireOnce(self::$tempDir.'/scripts/foo.php'); $this->assertTrue(function_exists('random_function_xyz')); $this->assertFalse(function_exists('random_function_xyz_changed')); } @@ -487,41 +511,44 @@ public function testCopyCopiesFileProperly() { $filesystem = new Filesystem; $data = 'contents'; - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/foo.txt', $data); - $filesystem->copy($this->tempDir.'/foo/foo.txt', $this->tempDir.'/foo/foo2.txt'); - $this->assertFileExists($this->tempDir.'/foo/foo2.txt'); - $this->assertEquals($data, file_get_contents($this->tempDir.'/foo/foo2.txt')); + mkdir(self::$tempDir.'/text'); + file_put_contents(self::$tempDir.'/text/foo.txt', $data); + $filesystem->copy(self::$tempDir.'/text/foo.txt', self::$tempDir.'/text/foo2.txt'); + $this->assertFileExists(self::$tempDir.'/text/foo2.txt'); + $this->assertEquals($data, file_get_contents(self::$tempDir.'/text/foo2.txt')); } public function testIsFileChecksFilesProperly() { $filesystem = new Filesystem; - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/foo.txt', 'contents'); - $this->assertTrue($filesystem->isFile($this->tempDir.'/foo/foo.txt')); - $this->assertFalse($filesystem->isFile($this->tempDir.'./foo')); + mkdir(self::$tempDir.'/help'); + file_put_contents(self::$tempDir.'/help/foo.txt', 'contents'); + $this->assertTrue($filesystem->isFile(self::$tempDir.'/help/foo.txt')); + $this->assertFalse($filesystem->isFile(self::$tempDir.'./help')); } public function testFilesMethodReturnsFileInfoObjects() { - mkdir($this->tempDir.'/foo'); - file_put_contents($this->tempDir.'/foo/1.txt', '1'); - file_put_contents($this->tempDir.'/foo/2.txt', '2'); - mkdir($this->tempDir.'/foo/bar'); + mkdir(self::$tempDir.'/objects'); + file_put_contents(self::$tempDir.'/objects/1.txt', '1'); + file_put_contents(self::$tempDir.'/objects/2.txt', '2'); + mkdir(self::$tempDir.'/objects/bar'); $files = new Filesystem; - $this->assertContainsOnlyInstancesOf(SplFileInfo::class, $files->files($this->tempDir.'/foo')); + $this->assertContainsOnlyInstancesOf(SplFileInfo::class, $files->files(self::$tempDir.'/objects')); unset($files); } public function testAllFilesReturnsFileInfoObjects() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); - file_put_contents($this->tempDir.'/bar.txt', 'bar'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/bar.txt', 'bar'); $files = new Filesystem; - $this->assertContainsOnlyInstancesOf(SplFileInfo::class, $files->allFiles($this->tempDir)); + $this->assertContainsOnlyInstancesOf(SplFileInfo::class, $files->allFiles(self::$tempDir)); } + /** + * @requires extension ftp + */ public function testCreateFtpDriver() { $filesystem = new FilesystemManager(new Application); @@ -533,7 +560,7 @@ public function testCreateFtpDriver() 'unsupportedParam' => true, ]); - /** @var Ftp $adapter */ + /** @var \League\Flysystem\Adapter\Ftp $adapter */ $adapter = $driver->getAdapter(); $this->assertEquals(0700, $adapter->getPermPublic()); $this->assertSame('ftp.example.com', $adapter->getHost()); @@ -542,13 +569,13 @@ public function testCreateFtpDriver() public function testHash() { - file_put_contents($this->tempDir.'/foo.txt', 'foo'); + file_put_contents(self::$tempDir.'/foo.txt', 'foo'); $filesystem = new Filesystem; - $this->assertSame('acbd18db4cc2f85cedef654fccc4a4d8', $filesystem->hash($this->tempDir.'/foo.txt')); + $this->assertSame('acbd18db4cc2f85cedef654fccc4a4d8', $filesystem->hash(self::$tempDir.'/foo.txt')); } /** - * @param string $file + * @param string $file * @return int */ private function getFilePermissions($file) diff --git a/tests/Foundation/Bootstrap/LoadEnvironmentVariablesTest.php b/tests/Foundation/Bootstrap/LoadEnvironmentVariablesTest.php index 378ab7b9f29c..9720273fa8bb 100644 --- a/tests/Foundation/Bootstrap/LoadEnvironmentVariablesTest.php +++ b/tests/Foundation/Bootstrap/LoadEnvironmentVariablesTest.php @@ -11,6 +11,9 @@ class LoadEnvironmentVariablesTest extends TestCase { protected function tearDown(): void { + unset($_ENV['FOO']); + unset($_SERVER['FOO']); + putenv('FOO'); m::close(); } diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index c3f1aa1450b3..582248e887ca 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -359,11 +359,12 @@ public function testCachePathsResolveToBootstrapCacheDirectory() { $app = new Application('/base/path'); - $this->assertSame('/base/path/bootstrap/cache/services.php', $app->getCachedServicesPath()); - $this->assertSame('/base/path/bootstrap/cache/packages.php', $app->getCachedPackagesPath()); - $this->assertSame('/base/path/bootstrap/cache/config.php', $app->getCachedConfigPath()); - $this->assertSame('/base/path/bootstrap/cache/routes.php', $app->getCachedRoutesPath()); - $this->assertSame('/base/path/bootstrap/cache/events.php', $app->getCachedEventsPath()); + $ds = DIRECTORY_SEPARATOR; + $this->assertSame('/base/path'.$ds.'bootstrap'.$ds.'cache/services.php', $app->getCachedServicesPath()); + $this->assertSame('/base/path'.$ds.'bootstrap'.$ds.'cache/packages.php', $app->getCachedPackagesPath()); + $this->assertSame('/base/path'.$ds.'bootstrap'.$ds.'cache/config.php', $app->getCachedConfigPath()); + $this->assertSame('/base/path'.$ds.'bootstrap'.$ds.'cache/routes.php', $app->getCachedRoutesPath()); + $this->assertSame('/base/path'.$ds.'bootstrap'.$ds.'cache/events.php', $app->getCachedEventsPath()); } public function testEnvPathsAreUsedForCachePathsWhenSpecified() @@ -375,6 +376,7 @@ 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()); @@ -399,11 +401,12 @@ public function testEnvPathsAreUsedAndMadeAbsoluteForCachePathsWhenSpecifiedAsRe $_SERVER['APP_ROUTES_CACHE'] = 'relative/path/routes.php'; $_SERVER['APP_EVENTS_CACHE'] = 'relative/path/events.php'; - $this->assertSame('/base/path/relative/path/services.php', $app->getCachedServicesPath()); - $this->assertSame('/base/path/relative/path/packages.php', $app->getCachedPackagesPath()); - $this->assertSame('/base/path/relative/path/config.php', $app->getCachedConfigPath()); - $this->assertSame('/base/path/relative/path/routes.php', $app->getCachedRoutesPath()); - $this->assertSame('/base/path/relative/path/events.php', $app->getCachedEventsPath()); + $ds = DIRECTORY_SEPARATOR; + $this->assertSame('/base/path'.$ds.'relative/path/services.php', $app->getCachedServicesPath()); + $this->assertSame('/base/path'.$ds.'relative/path/packages.php', $app->getCachedPackagesPath()); + $this->assertSame('/base/path'.$ds.'relative/path/config.php', $app->getCachedConfigPath()); + $this->assertSame('/base/path'.$ds.'relative/path/routes.php', $app->getCachedRoutesPath()); + $this->assertSame('/base/path'.$ds.'relative/path/events.php', $app->getCachedEventsPath()); unset( $_SERVER['APP_SERVICES_CACHE'], @@ -423,11 +426,12 @@ public function testEnvPathsAreUsedAndMadeAbsoluteForCachePathsWhenSpecifiedAsRe $_SERVER['APP_ROUTES_CACHE'] = 'relative/path/routes.php'; $_SERVER['APP_EVENTS_CACHE'] = 'relative/path/events.php'; - $this->assertSame('/relative/path/services.php', $app->getCachedServicesPath()); - $this->assertSame('/relative/path/packages.php', $app->getCachedPackagesPath()); - $this->assertSame('/relative/path/config.php', $app->getCachedConfigPath()); - $this->assertSame('/relative/path/routes.php', $app->getCachedRoutesPath()); - $this->assertSame('/relative/path/events.php', $app->getCachedEventsPath()); + $ds = DIRECTORY_SEPARATOR; + $this->assertSame($ds.'relative/path/services.php', $app->getCachedServicesPath()); + $this->assertSame($ds.'relative/path/packages.php', $app->getCachedPackagesPath()); + $this->assertSame($ds.'relative/path/config.php', $app->getCachedConfigPath()); + $this->assertSame($ds.'relative/path/routes.php', $app->getCachedRoutesPath()); + $this->assertSame($ds.'relative/path/events.php', $app->getCachedEventsPath()); unset( $_SERVER['APP_SERVICES_CACHE'], diff --git a/tests/Foundation/FoundationTestResponseTest.php b/tests/Foundation/FoundationTestResponseTest.php index 3b3aef467c07..049f778b86f2 100644 --- a/tests/Foundation/FoundationTestResponseTest.php +++ b/tests/Foundation/FoundationTestResponseTest.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([ @@ -484,6 +522,9 @@ public function testAssertJsonCount() { $response = TestResponse::fromBaseResponse(new Response(new JsonSerializableMixedResourcesStub)); + // With falsey key + $response->assertJsonCount(1, '0'); + // With simple key $response->assertJsonCount(3, 'bars'); @@ -918,6 +959,7 @@ public function jsonSerialize() 'foobar_foo' => 'foo', 'foobar_bar' => 'bar', ], + '0' => ['foo'], 'bars' => [ ['bar' => 'foo 0', 'foo' => 'bar 0'], ['bar' => 'foo 1', 'foo' => 'bar 1'], 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/HttpJsonResponseTest.php b/tests/Http/HttpJsonResponseTest.php index e3234badfa61..b8a286839d6c 100644 --- a/tests/Http/HttpJsonResponseTest.php +++ b/tests/Http/HttpJsonResponseTest.php @@ -14,10 +14,8 @@ class HttpJsonResponseTest extends TestCase { /** * @dataProvider setAndRetrieveDataProvider - * - * @param mixed $data */ - public function testSetAndRetrieveData($data): void + public function testSetAndRetrieveData($data) { $response = new JsonResponse($data); @@ -25,7 +23,7 @@ public function testSetAndRetrieveData($data): void $this->assertSame('bar', $response->getData()->foo); } - public function setAndRetrieveDataProvider(): array + public function setAndRetrieveDataProvider() { return [ 'Jsonable data' => [new JsonResponseTestJsonableObject], @@ -79,8 +77,6 @@ public function testInvalidArgumentExceptionOnJsonError($data) } /** - * @param mixed $data - * * @dataProvider jsonErrorDataProvider */ public function testGracefullyHandledSomeJsonErrorsWithPartialOutputOnError($data) @@ -88,9 +84,6 @@ public function testGracefullyHandledSomeJsonErrorsWithPartialOutputOnError($dat new JsonResponse(['data' => $data], 200, [], JSON_PARTIAL_OUTPUT_ON_ERROR); } - /** - * @return array - */ public function jsonErrorDataProvider() { // Resources can't be encoded diff --git a/tests/Integration/Database/DatabaseMySqlConnectionTest.php b/tests/Integration/Database/DatabaseMySqlConnectionTest.php index 86fb1b659b7f..d0ce732573e1 100644 --- a/tests/Integration/Database/DatabaseMySqlConnectionTest.php +++ b/tests/Integration/Database/DatabaseMySqlConnectionTest.php @@ -25,8 +25,8 @@ protected function setUp(): void { parent::setUp(); - if (! isset($_SERVER['CI'])) { - $this->markTestSkipped('This test is only executed on CI.'); + if (! isset($_SERVER['CI']) || windows_os()) { + $this->markTestSkipped('This test is only executed on CI in Linux.'); } if (! Schema::hasTable(self::TABLE)) { @@ -46,12 +46,8 @@ protected function tearDown(): void /** * @dataProvider floatComparisonsDataProvider - * - * @param float $value the value to compare against the JSON value - * @param string $operator the comparison operator to use. e.g. '<', '>', '=' - * @param bool $shouldMatch true if the comparison should match, false if not */ - public function testJsonFloatComparison(float $value, string $operator, bool $shouldMatch): void + public function testJsonFloatComparison($value, $operator, $shouldMatch) { DB::table(self::TABLE)->insert([self::JSON_COL => '{"rank":'.self::FLOAT_VAL.'}']); @@ -62,7 +58,7 @@ public function testJsonFloatComparison(float $value, string $operator, bool $sh ); } - public function floatComparisonsDataProvider(): array + public function floatComparisonsDataProvider() { return [ [0.2, '=', true], @@ -77,7 +73,7 @@ public function floatComparisonsDataProvider(): array ]; } - public function testFloatValueStoredCorrectly(): void + public function testFloatValueStoredCorrectly() { DB::table(self::TABLE)->insert([self::FLOAT_COL => self::FLOAT_VAL]); diff --git a/tests/Integration/Database/EloquentBelongsToManyTest.php b/tests/Integration/Database/EloquentBelongsToManyTest.php index d3a2ed4f4e5e..32be59a479c5 100644 --- a/tests/Integration/Database/EloquentBelongsToManyTest.php +++ b/tests/Integration/Database/EloquentBelongsToManyTest.php @@ -629,6 +629,22 @@ public function testWherePivotOnString() $this->assertEquals($relationTag->getAttributes(), $tag->getAttributes()); } + public function testFirstWhere() + { + $tag = Tag::create(['name' => 'foo']); + $post = Post::create(['title' => Str::random()]); + + DB::table('posts_tags')->insert([ + ['post_id' => $post->id, 'tag_id' => $tag->id, 'flag' => 'foo'], + ]); + + $relationTag = $post->tags()->firstWhere('name', 'foo'); + $this->assertEquals($relationTag->getAttributes(), $tag->getAttributes()); + + $relationTag = $post->tags()->firstWhere('name', '=', 'foo'); + $this->assertEquals($relationTag->getAttributes(), $tag->getAttributes()); + } + public function testWherePivotOnBoolean() { $tag = Tag::create(['name' => Str::random()]); diff --git a/tests/Integration/Database/EloquentHasManyThroughTest.php b/tests/Integration/Database/EloquentHasManyThroughTest.php index a8fc63822136..08fc7254f95a 100644 --- a/tests/Integration/Database/EloquentHasManyThroughTest.php +++ b/tests/Integration/Database/EloquentHasManyThroughTest.php @@ -50,13 +50,25 @@ public function testBasicCreateAndRetrieve() $team1 = Team::create(['id' => 10, 'owner_id' => $user->id]); $team2 = Team::create(['owner_id' => $user->id]); - $mate1 = User::create(['name' => Str::random(), 'team_id' => $team1->id]); - $mate2 = User::create(['name' => Str::random(), 'team_id' => $team2->id]); + $mate1 = User::create(['name' => 'John', 'team_id' => $team1->id]); + $mate2 = User::create(['name' => 'Jack', 'team_id' => $team2->id, 'slug' => null]); User::create(['name' => Str::random()]); $this->assertEquals([$mate1->id, $mate2->id], $user->teamMates->pluck('id')->toArray()); $this->assertEquals([$user->id], User::has('teamMates')->pluck('id')->toArray()); + + $result = $user->teamMates()->first(); + $this->assertEquals( + $mate1->refresh()->getAttributes() + ['laravel_through_key' => '1'], + $result->getAttributes() + ); + + $result = $user->teamMates()->firstWhere('name', 'Jack'); + $this->assertEquals( + $mate2->refresh()->getAttributes() + ['laravel_through_key' => '1'], + $result->getAttributes() + ); } public function testGlobalScopeColumns() diff --git a/tests/Integration/Database/EloquentModelRefreshTest.php b/tests/Integration/Database/EloquentModelRefreshTest.php index 0fdfa8781996..8fb2bcb95180 100644 --- a/tests/Integration/Database/EloquentModelRefreshTest.php +++ b/tests/Integration/Database/EloquentModelRefreshTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Integration\Database\EloquentModelRefreshTest; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -57,6 +58,23 @@ public function testItSyncsOriginalOnRefresh() $this->assertSame('patrick', $post->getOriginal('title')); } + + public function testAsPivot() + { + Schema::create('post_posts', function (Blueprint $table) { + $table->bigInteger('foreign_id'); + $table->bigInteger('related_id'); + }); + + $post = AsPivotPost::create(['title' => 'parent']); + $child = AsPivotPost::create(['title' => 'child']); + + $post->children()->attach($child->getKey()); + + $this->assertEquals(1, $post->children->count()); + + $post->children->first()->refresh(); + } } class Post extends Model @@ -76,3 +94,20 @@ protected static function boot() }); } } + +class AsPivotPost extends Post +{ + public function children() + { + return $this + ->belongsToMany(static::class, (new AsPivotPostPivot())->getTable(), 'foreign_id', 'related_id') + ->using(AsPivotPostPivot::class); + } +} + +class AsPivotPostPivot extends Model +{ + use AsPivot; + + protected $table = 'post_posts'; +} 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/Database/EloquentModelWithoutEventsTest.php b/tests/Integration/Database/EloquentModelWithoutEventsTest.php new file mode 100644 index 000000000000..7a15415f2bb8 --- /dev/null +++ b/tests/Integration/Database/EloquentModelWithoutEventsTest.php @@ -0,0 +1,52 @@ +increments('id'); + $table->text('project')->nullable(); + }); + } + + public function testWithoutEventsRegistersBootedListenersForLater() + { + $model = AutoFilledModel::withoutEvents(function () { + return AutoFilledModel::create(); + }); + + $this->assertNull($model->project); + + $model->save(); + + $this->assertEquals('Laravel', $model->project); + } +} + +class AutoFilledModel extends Model +{ + public $table = 'auto_filled_models'; + public $timestamps = false; + protected $guarded = ['id']; + + public static function boot() + { + parent::boot(); + + static::saving(function ($model) { + $model->project = 'Laravel'; + }); + } +} diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 88fa384f8f16..8b18d7b17d44 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -56,7 +56,7 @@ public function testRegisterCustomDoctrineType() $expected = [ 'CREATE TEMPORARY TABLE __temp__test AS SELECT test_column FROM test', 'DROP TABLE test', - 'CREATE TABLE test (test_column TINYINT NOT NULL COLLATE BINARY)', + 'CREATE TABLE test (test_column TINYINT NOT NULL)', 'INSERT INTO test (test_column) SELECT test_column FROM __temp__test', 'DROP TABLE __temp__test', ]; diff --git a/tests/Integration/Mail/RenderingMailWithLocaleTest.php b/tests/Integration/Mail/RenderingMailWithLocaleTest.php index 80cd2c71b91d..e86601f3ea3c 100644 --- a/tests/Integration/Mail/RenderingMailWithLocaleTest.php +++ b/tests/Integration/Mail/RenderingMailWithLocaleTest.php @@ -31,14 +31,14 @@ public function testMailableRendersInDefaultLocale() { $mail = new RenderedTestMail; - $this->assertStringContainsString('name'.PHP_EOL, $mail->render()); + $this->assertStringContainsString('name', $mail->render()); } public function testMailableRendersInSelectedLocale() { $mail = (new RenderedTestMail)->locale('es'); - $this->assertStringContainsString('nombre'.PHP_EOL, $mail->render()); + $this->assertStringContainsString('nombre', $mail->render()); } public function testMailableRendersInAppSelectedLocale() @@ -47,7 +47,7 @@ public function testMailableRendersInAppSelectedLocale() $mail = new RenderedTestMail; - $this->assertStringContainsString('nombre'.PHP_EOL, $mail->render()); + $this->assertStringContainsString('nombre', $mail->render()); } } 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/Integration/Routing/RouteRedirectTest.php b/tests/Integration/Routing/RouteRedirectTest.php index 8bb48e3610b1..558525c27761 100644 --- a/tests/Integration/Routing/RouteRedirectTest.php +++ b/tests/Integration/Routing/RouteRedirectTest.php @@ -12,11 +12,6 @@ class RouteRedirectTest extends TestCase { /** * @dataProvider routeRedirectDataSets - * - * @param string $redirectFrom - * @param string $redirectTo - * @param string $requestUri - * @param string $redirectUri */ public function testRouteRedirect($redirectFrom, $redirectTo, $requestUri, $redirectUri) { @@ -28,7 +23,7 @@ public function testRouteRedirect($redirectFrom, $redirectTo, $requestUri, $redi $response->assertStatus(301); } - public function routeRedirectDataSets(): array + public function routeRedirectDataSets() { return [ 'route redirect with no parameters' => ['from', 'to', '/from', '/to'], diff --git a/tests/Notifications/NotificationRoutesNotificationsTest.php b/tests/Notifications/NotificationRoutesNotificationsTest.php index f7d903ad34c6..af02994f64e1 100644 --- a/tests/Notifications/NotificationRoutesNotificationsTest.php +++ b/tests/Notifications/NotificationRoutesNotificationsTest.php @@ -5,6 +5,8 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Notifications\Dispatcher; use Illuminate\Notifications\RoutesNotifications; +use Illuminate\Support\Facades\Notification; +use InvalidArgumentException; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; @@ -50,6 +52,15 @@ public function testNotificationOptionRouting() $this->assertSame('bar', $instance->routeNotificationFor('foo')); $this->assertSame('taylor@laravel.com', $instance->routeNotificationFor('mail')); } + + public function testOnDemandNotificationsCannotUseDatabaseChannel() + { + $this->expectExceptionObject( + new InvalidArgumentException('The database channel does not support on-demand notifications.') + ); + + Notification::route('database', 'foo'); + } } class RoutesNotificationsTestInstance diff --git a/tests/Notifications/NotificationSenderTest.php b/tests/Notifications/NotificationSenderTest.php index 8dd398f47bf2..6c5a6aaf849d 100644 --- a/tests/Notifications/NotificationSenderTest.php +++ b/tests/Notifications/NotificationSenderTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher; use Illuminate\Contracts\Events\Dispatcher as EventDispatcher; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Notifications\ChannelManager; use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; @@ -34,6 +35,32 @@ public function testItCanSendQueuedNotificationsWithAStringVia() $sender->send($notifiable, new DummyQueuedNotificationWithStringVia()); } + + public function testItCanSendNotificationsWithAnEmptyStringVia() + { + $notifiable = new AnonymousNotifiable; + $manager = m::mock(ChannelManager::class); + $bus = m::mock(BusDispatcher::class); + $bus->shouldNotReceive('dispatch'); + $events = m::mock(EventDispatcher::class); + + $sender = new NotificationSender($manager, $bus, $events); + + $sender->sendNow($notifiable, new DummyNotificationWithEmptyStringVia()); + } + + public function testItCannotSendNotificationsViaDatabaseForAnonymousNotifiables() + { + $notifiable = new AnonymousNotifiable; + $manager = m::mock(ChannelManager::class); + $bus = m::mock(BusDispatcher::class); + $bus->shouldNotReceive('dispatch'); + $events = m::mock(EventDispatcher::class); + + $sender = new NotificationSender($manager, $bus, $events); + + $sender->sendNow($notifiable, new DummyNotificationWithDatabaseVia()); + } } class DummyQueuedNotificationWithStringVia extends Notification implements ShouldQueue @@ -51,3 +78,35 @@ public function via($notifiable) return 'mail'; } } + +class DummyNotificationWithEmptyStringVia extends Notification +{ + use Queueable; + + /** + * Get the notification channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ''; + } +} + +class DummyNotificationWithDatabaseVia extends Notification +{ + use Queueable; + + /** + * Get the notification channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return 'database'; + } +} diff --git a/tests/Queue/QueueWorkerTest.php b/tests/Queue/QueueWorkerTest.php index b369ab4335cf..d084ca1e3f4a 100755 --- a/tests/Queue/QueueWorkerTest.php +++ b/tests/Queue/QueueWorkerTest.php @@ -59,19 +59,21 @@ public function testWorkerCanWorkUntilQueueIsEmpty() $secondJob = new WorkerFakeJob, ]]); - $this->expectException(LoopBreakerException::class); + try { + $worker->daemon('default', 'queue', $workerOptions); - $worker->daemon('default', 'queue', $workerOptions); + $this->fail('Expected LoopBreakerException to be thrown.'); + } catch (LoopBreakerException $e) { + $this->assertTrue($firstJob->fired); - $this->assertTrue($firstJob->fired); + $this->assertTrue($secondJob->fired); - $this->assertTrue($secondJob->fired); + $this->assertSame(0, $worker->stoppedWithStatus); - $this->assertSame(0, $worker->stoppedWithStatus); + $this->events->shouldHaveReceived('dispatch')->with(m::type(JobProcessing::class))->twice(); - $this->events->shouldHaveReceived('dispatch')->with(m::type(JobProcessing::class))->twice(); - - $this->events->shouldHaveReceived('dispatch')->with(m::type(JobProcessed::class))->twice(); + $this->events->shouldHaveReceived('dispatch')->with(m::type(JobProcessed::class))->twice(); + } } public function testJobCanBeFiredBasedOnPriority() @@ -276,21 +278,23 @@ public function testJobRunsIfAppIsNotInMaintenanceMode() $maintenanceModeChecker = function () { if ($this->maintenanceFlags) { - return array_pop($this->maintenanceFlags); + return array_shift($this->maintenanceFlags); } throw new LoopBreakerException; }; - $this->expectException(LoopBreakerException::class); - $worker = $this->getWorker('default', ['queue' => [$firstJob, $secondJob]], $maintenanceModeChecker); - $worker->daemon('default', 'queue', $this->workerOptions()); + try { + $worker->daemon('default', 'queue', $this->workerOptions()); - $this->assertEquals($firstJob->attempts, 1); + $this->fail('Expected LoopBreakerException to be thrown'); + } catch (LoopBreakerException $e) { + $this->assertSame(1, $firstJob->attempts); - $this->assertEquals($firstJob->attempts, 0); + $this->assertSame(0, $secondJob->attempts); + } } public function testJobDoesNotFireIfDeleted() @@ -349,6 +353,7 @@ private function workerOptions(array $overrides = []) class InsomniacWorker extends Worker { public $sleptFor; + public $stopOnMemoryExceeded = false; public function sleep($seconds) { @@ -366,6 +371,11 @@ public function daemonShouldRun(WorkerOptions $options, $connectionName, $queue) { return ! ($this->isDownForMaintenance)(); } + + public function memoryExceeded($memoryLimit) + { + return $this->stopOnMemoryExceeded; + } } class WorkerFakeManager extends QueueManager diff --git a/tests/Queue/RedisQueueIntegrationTest.php b/tests/Queue/RedisQueueIntegrationTest.php index b4196a6d4e89..e023fe49b104 100644 --- a/tests/Queue/RedisQueueIntegrationTest.php +++ b/tests/Queue/RedisQueueIntegrationTest.php @@ -67,6 +67,7 @@ public function testExpiredJobsArePopped($driver) /** * @dataProvider redisDriverProvider + * @requires extension pcntl * * @param mixed $driver * @@ -74,7 +75,12 @@ public function testExpiredJobsArePopped($driver) */ public function testBlockingPop($driver) { + if (! function_exists('pcntl_fork')) { + $this->markTestSkipped('Skipping since the pcntl extension is not available'); + } + $this->tearDownRedis(); + if ($pid = pcntl_fork() > 0) { $this->setUpRedis(); $this->setQueue($driver, 'default', null, 60, 10); @@ -84,7 +90,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/Support/ConfigurationUrlParserTest.php b/tests/Support/ConfigurationUrlParserTest.php index 4e6439d4a164..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()); @@ -174,6 +178,17 @@ public function databaseUrls() 'driver' => 'mysql', ], ], + 'simple URL with percent encoding in query' => [ + 'mysql://foo:bar%25bar@localhost/baz?timezone=%2B00%3A00', + [ + 'username' => 'foo', + 'password' => 'bar%bar', + 'host' => 'localhost', + 'database' => 'baz', + 'driver' => 'mysql', + 'timezone' => '+00:00', + ], + ], 'URL with mssql alias driver' => [ 'mssql://null', [ @@ -344,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, @@ -356,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, @@ -369,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 c7e225ad8792..bdb4048a4f49 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -364,10 +364,13 @@ public function testForgetSingleKey() $c = new Collection(['foo', 'bar']); $c = $c->forget(0)->all(); $this->assertFalse(isset($c['foo'])); + $this->assertFalse(isset($c[0])); + $this->assertTrue(isset($c[1])); $c = new Collection(['foo' => 'bar', 'baz' => 'qux']); $c = $c->forget('foo')->all(); $this->assertFalse(isset($c['foo'])); + $this->assertTrue(isset($c['baz'])); } public function testForgetArrayOfKeys() @@ -2263,6 +2266,30 @@ public function testGroupByAttribute($collection) $this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray()); } + /** + * @dataProvider collectionClassProvider + */ + public function testGroupByCallable($collection) + { + $data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]); + + $result = $data->groupBy([$this, 'sortByRating']); + $this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray()); + + $result = $data->groupBy([$this, 'sortByUrl']); + $this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray()); + } + + public function sortByRating(array $value) + { + return $value['rating']; + } + + public function sortByUrl(array $value) + { + return $value['url']; + } + /** * @dataProvider collectionClassProvider */ diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index e37b4bf93416..9e5b6464f4ca 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -492,7 +492,7 @@ public function testRetry() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertTrue(microtime(true) - $startTime >= 0.1); + $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.02); } public function testRetryWithPassingWhenCallback() @@ -513,7 +513,7 @@ public function testRetryWithPassingWhenCallback() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertTrue(microtime(true) - $startTime >= 0.1); + $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.02); } public function testRetryWithFailingWhenCallback() diff --git a/tests/Support/SupportReflectorTest.php b/tests/Support/SupportReflectorTest.php new file mode 100644 index 000000000000..55c4940f7543 --- /dev/null +++ b/tests/Support/SupportReflectorTest.php @@ -0,0 +1,84 @@ +getMethod('send'); + + $this->assertSame(Mailable::class, Reflector::getParameterClassName($method->getParameters()[0])); + } + + public function testEmptyClassName() + { + $method = (new ReflectionClass(MailFake::class))->getMethod('assertSent'); + + $this->assertNull(Reflector::getParameterClassName($method->getParameters()[0])); + } + + public function testStringTypeName() + { + $method = (new ReflectionClass(BusFake::class))->getMethod('dispatchedAfterResponse'); + + $this->assertNull(Reflector::getParameterClassName($method->getParameters()[0])); + } + + public function testSelfClassName() + { + $method = (new ReflectionClass(Model::class))->getMethod('newPivot'); + + $this->assertSame(Model::class, Reflector::getParameterClassName($method->getParameters()[0])); + } + + public function testParentClassName() + { + $method = (new ReflectionClass(B::class))->getMethod('f'); + + $this->assertSame(A::class, Reflector::getParameterClassName($method->getParameters()[0])); + } + + /** + * @requires PHP 8 + */ + public function testUnionTypeName() + { + $method = (new ReflectionClass(C::class))->getMethod('f'); + + $this->assertNull(Reflector::getParameterClassName($method->getParameters()[0])); + } +} + +class A +{ +} + +class B extends A +{ + public function f(parent $x) + { + } +} + +if (PHP_MAJOR_VERSION >= 8) { + eval(' +namespace Illuminate\Tests\Support; + +class C +{ + public function f(A|Model $x) + { + } +}' + ); +} diff --git a/tests/Validation/ValidationExistsRuleTest.php b/tests/Validation/ValidationExistsRuleTest.php index 3208b25156e8..d5cce447cfd0 100644 --- a/tests/Validation/ValidationExistsRuleTest.php +++ b/tests/Validation/ValidationExistsRuleTest.php @@ -58,6 +58,10 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule = new Exists(NoTableNameModel::class, 'column'); $rule->where('foo', 'bar'); $this->assertSame('exists:no_table_name_models,column,foo,"bar"', (string) $rule); + + $rule = new Exists(ClassWithRequiredConstructorParameters::class, 'column'); + $rule->where('foo', 'bar'); + $this->assertSame('exists:'.ClassWithRequiredConstructorParameters::class.',column,foo,"bar"', (string) $rule); } public function testItChoosesValidRecordsUsingWhereInRule() @@ -203,3 +207,15 @@ class NoTableNameModel extends Eloquent protected $guarded = []; public $timestamps = false; } + +class ClassWithRequiredConstructorParameters +{ + private $bar; + private $baz; + + public function __construct($bar, $baz) + { + $this->bar = $bar; + $this->baz = $baz; + } +} diff --git a/tests/Validation/ValidationUniqueRuleTest.php b/tests/Validation/ValidationUniqueRuleTest.php index 46b591e4abce..c967ab7c077c 100644 --- a/tests/Validation/ValidationUniqueRuleTest.php +++ b/tests/Validation/ValidationUniqueRuleTest.php @@ -26,6 +26,10 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule->where('foo', 'bar'); $this->assertSame('unique:no_table_names,NULL,NULL,id,foo,"bar"', (string) $rule); + $rule = new Unique(ClassWithNonEmptyConstructor::class); + $rule->where('foo', 'bar'); + $this->assertSame('unique:'.ClassWithNonEmptyConstructor::class.',NULL,NULL,id,foo,"bar"', (string) $rule); + $rule = new Unique('table', 'column'); $rule->ignore('Taylor, Otwell', 'id_column'); $rule->where('foo', 'bar'); @@ -78,3 +82,15 @@ class NoTableName extends Model protected $guarded = []; public $timestamps = false; } + +class ClassWithNonEmptyConstructor +{ + private $bar; + private $baz; + + public function __construct($bar, $baz) + { + $this->bar = $bar; + $this->baz = $baz; + } +} diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 57e3aeb25a04..d9a02a78389e 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -36,6 +36,32 @@ protected function tearDown(): void m::close(); } + public function testNestedErrorMessagesAreRetrievedFromLocalArray() + { + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, [ + 'users' => [ + [ + 'name' => 'Taylor Otwell', + 'posts' => [ + [ + 'name' => '', + ], + ], + ], + ], + ], [ + 'users.*.name' => ['required'], + 'users.*.posts.*.name' => ['required'], + ], [ + 'users.*.name.required' => 'user name is required', + 'users.*.posts.*.name.required' => 'post name is required', + ]); + + $this->assertFalse($v->passes()); + $this->assertEquals('post name is required', $v->errors()->all()[0]); + } + public function testSometimesWorksOnNestedArrays() { $trans = $this->getIlluminateArrayTranslator(); @@ -1064,6 +1090,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'); @@ -2278,20 +2312,24 @@ public function testValidateEmail() $v = new Validator($trans, ['x' => ['not a string']], ['x' => 'Email']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['x' => new class { - public function __toString() - { - return 'aslsdlks'; - } - }], ['x' => 'Email']); + $v = new Validator($trans, [ + 'x' => new class { + public function __toString() + { + return 'aslsdlks'; + } + }, + ], ['x' => 'Email']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['x' => new class { - public function __toString() - { - return 'foo@gmail.com'; - } - }], ['x' => 'Email']); + $v = new Validator($trans, [ + 'x' => new class { + public function __toString() + { + return 'foo@gmail.com'; + } + }, + ], ['x' => 'Email']); $this->assertTrue($v->passes()); $v = new Validator($trans, ['x' => 'foo@gmail.com'], ['x' => 'Email']); @@ -2760,7 +2798,7 @@ public function testValidateImageDimensions() $v = new Validator($trans, ['x' => $svgXmlUploadedFile], ['x' => 'dimensions:max_width=1,max_height=1']); $this->assertTrue($v->passes()); - $svgXmlFile = new File(__DIR__.'/fixtures/image.svg', '', 'image/svg+xml', null, null, true); + $svgXmlFile = new UploadedFile(__DIR__.'/fixtures/image.svg', '', 'image/svg+xml', null, null, true); $trans = $this->getIlluminateArrayTranslator(); $v = new Validator($trans, ['x' => $svgXmlFile], ['x' => 'dimensions:max_width=1,max_height=1']); @@ -2773,11 +2811,19 @@ public function testValidateImageDimensions() $v = new Validator($trans, ['x' => $svgUploadedFile], ['x' => 'dimensions:max_width=1,max_height=1']); $this->assertTrue($v->passes()); - $svgFile = new File(__DIR__.'/fixtures/image2.svg', '', 'image/svg', null, null, true); + $svgFile = new UploadedFile(__DIR__.'/fixtures/image2.svg', '', 'image/svg', null, null, true); $trans = $this->getIlluminateArrayTranslator(); $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()); } /** @@ -2871,9 +2917,11 @@ public function testValidateAlpha() $this->assertTrue($v->passes()); $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['x' => 'aslsdlks + $v = new Validator($trans, [ + 'x' => 'aslsdlks 1 -1'], ['x' => 'Alpha']); +1', + ], ['x' => 'Alpha']); $this->assertFalse($v->passes()); $v = new Validator($trans, ['x' => 'http://google.com'], ['x' => 'Alpha']); @@ -3521,9 +3569,11 @@ public function testCustomValidators() $trans = $this->getIlluminateArrayTranslator(); $v = new Validator($trans, ['name' => 'taylor'], ['name' => 'foo_bar']); - $v->addExtensions(['FooBar' => function () { - return false; - }]); + $v->addExtensions([ + 'FooBar' => function () { + return false; + }, + ]); $v->setFallbackMessages(['foo_bar' => 'foo!']); $this->assertFalse($v->passes()); $v->messages()->setFormat(':message'); @@ -3698,21 +3748,27 @@ public function testValidateImplicitEachWithAsterisksForRequiredNonExistingKey() $v = new Validator($trans, $data, ['names.*.first' => 'required']); $this->assertFalse($v->passes()); - $data = ['people' => [ - ['cars' => [['model' => 2005], []]], - ]]; + $data = [ + 'people' => [ + ['cars' => [['model' => 2005], []]], + ], + ]; $v = new Validator($trans, $data, ['people.*.cars.*.model' => 'required']); $this->assertFalse($v->passes()); - $data = ['people' => [ - ['name' => 'test', 'cars' => [['model' => 2005], ['name' => 'test2']]], - ]]; + $data = [ + 'people' => [ + ['name' => 'test', 'cars' => [['model' => 2005], ['name' => 'test2']]], + ], + ]; $v = new Validator($trans, $data, ['people.*.cars.*.model' => 'required']); $this->assertFalse($v->passes()); - $data = ['people' => [ - ['phones' => ['iphone', 'android'], 'cars' => [['model' => 2005], ['name' => 'test2']]], - ]]; + $data = [ + 'people' => [ + ['phones' => ['iphone', 'android'], 'cars' => [['model' => 2005], ['name' => 'test2']]], + ], + ]; $v = new Validator($trans, $data, ['people.*.cars.*.model' => 'required']); $this->assertFalse($v->passes()); @@ -3759,6 +3815,9 @@ public function testParsingArrayKeysWithDot() $v = new Validator($trans, ['foo' => ['bar' => 'valid'], 'foo.bar' => ''], ['foo\.bar' => 'required']); $this->assertTrue($v->fails()); + $v = new Validator($trans, ['foo' => ['bar' => 'valid'], 'foo.bar' => 'zxc'], ['foo\.bar' => 'required']); + $this->assertFalse($v->fails()); + $v = new Validator($trans, ['foo' => ['bar.baz' => '']], ['foo.bar\.baz' => 'required']); $this->assertTrue($v->fails()); @@ -3766,6 +3825,32 @@ public function testParsingArrayKeysWithDot() $this->assertTrue($v->fails()); } + public function testPassingSlashVulnerability() + { + $trans = $this->getIlluminateArrayTranslator(); + + $v = new Validator($trans, [ + 'matrix' => ['\\' => ['invalid'], '1\\' => ['invalid']], + ], [ + 'matrix.*.*' => 'integer', + ]); + $this->assertTrue($v->fails()); + + $v = new Validator($trans, [ + 'matrix' => ['\\' => [1], '1\\' => [1]], + ], [ + 'matrix.*.*' => 'integer', + ]); + $this->assertTrue($v->passes()); + + $v = new Validator($trans, [ + 'foo' => ['bar' => 'valid'], 'foo.bar' => 'invalid', 'foo->bar' => 'valid', + ], [ + 'foo\.bar' => 'required|in:valid', + ]); + $this->assertTrue($v->fails()); + } + public function testCoveringEmptyKeys() { $trans = $this->getIlluminateArrayTranslator(); @@ -3851,41 +3936,55 @@ public function testValidateImplicitEachWithAsterisksConfirmed() $trans = $this->getIlluminateArrayTranslator(); // confirmed passes - $v = new Validator($trans, ['foo' => [ - ['password' => 'foo0', 'password_confirmation' => 'foo0'], - ['password' => 'foo1', 'password_confirmation' => 'foo1'], - ]], ['foo.*.password' => 'confirmed']); + $v = new Validator($trans, [ + 'foo' => [ + ['password' => 'foo0', 'password_confirmation' => 'foo0'], + ['password' => 'foo1', 'password_confirmation' => 'foo1'], + ], + ], ['foo.*.password' => 'confirmed']); $this->assertTrue($v->passes()); // nested confirmed passes - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['password' => 'bar0', 'password_confirmation' => 'bar0'], - ['password' => 'bar1', 'password_confirmation' => 'bar1'], - ]], - ['bar' => [ - ['password' => 'bar2', 'password_confirmation' => 'bar2'], - ['password' => 'bar3', 'password_confirmation' => 'bar3'], - ]], - ]], ['foo.*.bar.*.password' => 'confirmed']); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['password' => 'bar0', 'password_confirmation' => 'bar0'], + ['password' => 'bar1', 'password_confirmation' => 'bar1'], + ], + ], + [ + 'bar' => [ + ['password' => 'bar2', 'password_confirmation' => 'bar2'], + ['password' => 'bar3', 'password_confirmation' => 'bar3'], + ], + ], + ], + ], ['foo.*.bar.*.password' => 'confirmed']); $this->assertTrue($v->passes()); // confirmed fails - $v = new Validator($trans, ['foo' => [ - ['password' => 'foo0', 'password_confirmation' => 'bar0'], - ['password' => 'foo1'], - ]], ['foo.*.password' => 'confirmed']); + $v = new Validator($trans, [ + 'foo' => [ + ['password' => 'foo0', 'password_confirmation' => 'bar0'], + ['password' => 'foo1'], + ], + ], ['foo.*.password' => 'confirmed']); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.password')); $this->assertTrue($v->messages()->has('foo.1.password')); // nested confirmed fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['password' => 'bar0'], - ['password' => 'bar1', 'password_confirmation' => 'bar2'], - ]], - ]], ['foo.*.bar.*.password' => 'confirmed']); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['password' => 'bar0'], + ['password' => 'bar1', 'password_confirmation' => 'bar2'], + ], + ], + ], + ], ['foo.*.bar.*.password' => 'confirmed']); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.password')); $this->assertTrue($v->messages()->has('foo.0.bar.1.password')); @@ -3896,37 +3995,49 @@ public function testValidateImplicitEachWithAsterisksDifferent() $trans = $this->getIlluminateArrayTranslator(); // different passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'foo', 'last' => 'bar'], - ['name' => 'bar', 'last' => 'foo'], - ]], ['foo.*.name' => ['different:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'foo', 'last' => 'bar'], + ['name' => 'bar', 'last' => 'foo'], + ], + ], ['foo.*.name' => ['different:foo.*.last']]); $this->assertTrue($v->passes()); // nested different passes - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => 'foo', 'last' => 'bar'], - ['name' => 'bar', 'last' => 'foo'], - ]], - ]], ['foo.*.bar.*.name' => ['different:foo.*.bar.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => 'foo', 'last' => 'bar'], + ['name' => 'bar', 'last' => 'foo'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['different:foo.*.bar.*.last']]); $this->assertTrue($v->passes()); // different fails - $v = new Validator($trans, ['foo' => [ - ['name' => 'foo', 'last' => 'foo'], - ['name' => 'bar', 'last' => 'bar'], - ]], ['foo.*.name' => ['different:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'foo', 'last' => 'foo'], + ['name' => 'bar', 'last' => 'bar'], + ], + ], ['foo.*.name' => ['different:foo.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested different fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => 'foo', 'last' => 'foo'], - ['name' => 'bar', 'last' => 'bar'], - ]], - ]], ['foo.*.bar.*.name' => ['different:foo.*.bar.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => 'foo', 'last' => 'foo'], + ['name' => 'bar', 'last' => 'bar'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['different:foo.*.bar.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -3937,37 +4048,49 @@ public function testValidateImplicitEachWithAsterisksSame() $trans = $this->getIlluminateArrayTranslator(); // same passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'foo', 'last' => 'foo'], - ['name' => 'bar', 'last' => 'bar'], - ]], ['foo.*.name' => ['same:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'foo', 'last' => 'foo'], + ['name' => 'bar', 'last' => 'bar'], + ], + ], ['foo.*.name' => ['same:foo.*.last']]); $this->assertTrue($v->passes()); // nested same passes - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => 'foo', 'last' => 'foo'], - ['name' => 'bar', 'last' => 'bar'], - ]], - ]], ['foo.*.bar.*.name' => ['same:foo.*.bar.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => 'foo', 'last' => 'foo'], + ['name' => 'bar', 'last' => 'bar'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['same:foo.*.bar.*.last']]); $this->assertTrue($v->passes()); // same fails - $v = new Validator($trans, ['foo' => [ - ['name' => 'foo', 'last' => 'bar'], - ['name' => 'bar', 'last' => 'foo'], - ]], ['foo.*.name' => ['same:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'foo', 'last' => 'bar'], + ['name' => 'bar', 'last' => 'foo'], + ], + ], ['foo.*.name' => ['same:foo.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested same fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => 'foo', 'last' => 'bar'], - ['name' => 'bar', 'last' => 'foo'], - ]], - ]], ['foo.*.bar.*.name' => ['same:foo.*.bar.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => 'foo', 'last' => 'bar'], + ['name' => 'bar', 'last' => 'foo'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['same:foo.*.bar.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -3978,35 +4101,45 @@ public function testValidateImplicitEachWithAsterisksRequired() $trans = $this->getIlluminateArrayTranslator(); // required passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first'], - ['name' => 'second'], - ]], ['foo.*.name' => ['Required']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first'], + ['name' => 'second'], + ], + ], ['foo.*.name' => ['Required']]); $this->assertTrue($v->passes()); // nested required passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first'], - ['name' => 'second'], - ]], ['foo.*.name' => ['Required']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first'], + ['name' => 'second'], + ], + ], ['foo.*.name' => ['Required']]); $this->assertTrue($v->passes()); // required fails - $v = new Validator($trans, ['foo' => [ - ['name' => null], - ['name' => null, 'last' => 'last'], - ]], ['foo.*.name' => ['Required']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null], + ['name' => null, 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null], - ['name' => null], - ]], - ]], ['foo.*.bar.*.name' => ['Required']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null], + ['name' => null], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4017,35 +4150,45 @@ public function testValidateImplicitEachWithAsterisksRequiredIf() $trans = $this->getIlluminateArrayTranslator(); // required_if passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'foo'], - ['last' => 'bar'], - ]], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'foo'], + ['last' => 'bar'], + ], + ], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); $this->assertTrue($v->passes()); // nested required_if passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'foo'], - ['last' => 'bar'], - ]], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'foo'], + ['last' => 'bar'], + ], + ], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); $this->assertTrue($v->passes()); // required_if fails - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'foo'], - ['name' => null, 'last' => 'foo'], - ]], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'foo'], + ['name' => null, 'last' => 'foo'], + ], + ], ['foo.*.name' => ['Required_if:foo.*.last,foo']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required_if fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'last' => 'foo'], - ['name' => null, 'last' => 'foo'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_if:foo.*.bar.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'last' => 'foo'], + ['name' => null, 'last' => 'foo'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_if:foo.*.bar.*.last,foo']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4056,35 +4199,45 @@ public function testValidateImplicitEachWithAsterisksRequiredUnless() $trans = $this->getIlluminateArrayTranslator(); // required_unless passes - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'foo'], - ['name' => 'second', 'last' => 'bar'], - ]], ['foo.*.name' => ['Required_unless:foo.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'foo'], + ['name' => 'second', 'last' => 'bar'], + ], + ], ['foo.*.name' => ['Required_unless:foo.*.last,foo']]); $this->assertTrue($v->passes()); // nested required_unless passes - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'foo'], - ['name' => 'second', 'last' => 'foo'], - ]], ['foo.*.bar.*.name' => ['Required_unless:foo.*.bar.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'foo'], + ['name' => 'second', 'last' => 'foo'], + ], + ], ['foo.*.bar.*.name' => ['Required_unless:foo.*.bar.*.last,foo']]); $this->assertTrue($v->passes()); // required_unless fails - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'baz'], - ['name' => null, 'last' => 'bar'], - ]], ['foo.*.name' => ['Required_unless:foo.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'baz'], + ['name' => null, 'last' => 'bar'], + ], + ], ['foo.*.name' => ['Required_unless:foo.*.last,foo']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required_unless fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'last' => 'bar'], - ['name' => null, 'last' => 'bar'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_unless:foo.*.bar.*.last,foo']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'last' => 'bar'], + ['name' => null, 'last' => 'bar'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_unless:foo.*.bar.*.last,foo']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4095,41 +4248,53 @@ public function testValidateImplicitEachWithAsterisksRequiredWith() $trans = $this->getIlluminateArrayTranslator(); // required_with passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'last'], - ['name' => 'second', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_with:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'last'], + ['name' => 'second', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_with:foo.*.last']]); $this->assertTrue($v->passes()); // nested required_with passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'last'], - ['name' => 'second', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_with:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'last'], + ['name' => 'second', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_with:foo.*.last']]); $this->assertTrue($v->passes()); // required_with fails - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'last'], - ['name' => null, 'last' => 'last'], - ]], ['foo.*.name' => ['Required_with:foo.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'last'], + ['name' => null, 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_with:foo.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); - $v = new Validator($trans, ['fields' => [ - 'fr' => ['name' => '', 'content' => 'ragnar'], - 'es' => ['name' => '', 'content' => 'lagertha'], - ]], ['fields.*.name' => 'required_with:fields.*.content']); + $v = new Validator($trans, [ + 'fields' => [ + 'fr' => ['name' => '', 'content' => 'ragnar'], + 'es' => ['name' => '', 'content' => 'lagertha'], + ], + ], ['fields.*.name' => 'required_with:fields.*.content']); $this->assertFalse($v->passes()); // nested required_with fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'last' => 'last'], - ['name' => null, 'last' => 'last'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_with:foo.*.bar.*.last']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'last' => 'last'], + ['name' => null, 'last' => 'last'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_with:foo.*.bar.*.last']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4140,35 +4305,45 @@ public function testValidateImplicitEachWithAsterisksRequiredWithAll() $trans = $this->getIlluminateArrayTranslator(); // required_with_all passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'last', 'middle' => 'middle'], - ['name' => 'second', 'last' => 'last', 'middle' => 'middle'], - ]], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'last', 'middle' => 'middle'], + ['name' => 'second', 'last' => 'last', 'middle' => 'middle'], + ], + ], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); // nested required_with_all passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'last' => 'last', 'middle' => 'middle'], - ['name' => 'second', 'last' => 'last', 'middle' => 'middle'], - ]], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'last' => 'last', 'middle' => 'middle'], + ['name' => 'second', 'last' => 'last', 'middle' => 'middle'], + ], + ], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); // required_with_all fails - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'last', 'middle' => 'middle'], - ['name' => null, 'last' => 'last', 'middle' => 'middle'], - ]], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'last', 'middle' => 'middle'], + ['name' => null, 'last' => 'last', 'middle' => 'middle'], + ], + ], ['foo.*.name' => ['Required_with_all:foo.*.last,foo.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required_with_all fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'last' => 'last', 'middle' => 'middle'], - ['name' => null, 'last' => 'last', 'middle' => 'middle'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_with_all:foo.*.bar.*.last,foo.*.bar.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'last' => 'last', 'middle' => 'middle'], + ['name' => null, 'last' => 'last', 'middle' => 'middle'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_with_all:foo.*.bar.*.last,foo.*.bar.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4179,35 +4354,45 @@ public function testValidateImplicitEachWithAsterisksRequiredWithout() $trans = $this->getIlluminateArrayTranslator(); // required_without passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'middle' => 'middle'], - ['name' => 'second', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'middle' => 'middle'], + ['name' => 'second', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); // nested required_without passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first', 'middle' => 'middle'], - ['name' => 'second', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first', 'middle' => 'middle'], + ['name' => 'second', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); // required_without fails - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'last' => 'last'], - ['name' => null, 'middle' => 'middle'], - ]], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'last' => 'last'], + ['name' => null, 'middle' => 'middle'], + ], + ], ['foo.*.name' => ['Required_without:foo.*.last,foo.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required_without fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'last' => 'last'], - ['name' => null, 'middle' => 'middle'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_without:foo.*.bar.*.last,foo.*.bar.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'last' => 'last'], + ['name' => null, 'middle' => 'middle'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_without:foo.*.bar.*.last,foo.*.bar.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4218,37 +4403,47 @@ public function testValidateImplicitEachWithAsterisksRequiredWithoutAll() $trans = $this->getIlluminateArrayTranslator(); // required_without_all passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first'], - ['name' => null, 'middle' => 'middle'], - ['name' => null, 'middle' => 'middle', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first'], + ['name' => null, 'middle' => 'middle'], + ['name' => null, 'middle' => 'middle', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); // required_without_all fails // nested required_without_all passes - $v = new Validator($trans, ['foo' => [ - ['name' => 'first'], - ['name' => null, 'middle' => 'middle'], - ['name' => null, 'middle' => 'middle', 'last' => 'last'], - ]], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => 'first'], + ['name' => null, 'middle' => 'middle'], + ['name' => null, 'middle' => 'middle', 'last' => 'last'], + ], + ], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['foo' => [ - ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], - ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], - ]], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], + ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], + ], + ], ['foo.*.name' => ['Required_without_all:foo.*.last,foo.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.name')); $this->assertTrue($v->messages()->has('foo.1.name')); // nested required_without_all fails - $v = new Validator($trans, ['foo' => [ - ['bar' => [ - ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], - ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], - ]], - ]], ['foo.*.bar.*.name' => ['Required_without_all:foo.*.bar.*.last,foo.*.bar.*.middle']]); + $v = new Validator($trans, [ + 'foo' => [ + [ + 'bar' => [ + ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], + ['name' => null, 'foo' => 'foo', 'bar' => 'bar'], + ], + ], + ], + ], ['foo.*.bar.*.name' => ['Required_without_all:foo.*.bar.*.last,foo.*.bar.*.middle']]); $this->assertFalse($v->passes()); $this->assertTrue($v->messages()->has('foo.0.bar.0.name')); $this->assertTrue($v->messages()->has('foo.0.bar.1.name')); @@ -4258,24 +4453,32 @@ public function testValidateImplicitEachWithAsterisksBeforeAndAfter() { $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['foo' => [ - ['start' => '2016-04-19', 'end' => '2017-04-19'], - ]], ['foo.*.start' => ['before:foo.*.end']]); + $v = new Validator($trans, [ + 'foo' => [ + ['start' => '2016-04-19', 'end' => '2017-04-19'], + ], + ], ['foo.*.start' => ['before:foo.*.end']]); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['foo' => [ - ['start' => '2016-04-19', 'end' => '2017-04-19'], - ]], ['foo.*.end' => ['before:foo.*.start']]); + $v = new Validator($trans, [ + 'foo' => [ + ['start' => '2016-04-19', 'end' => '2017-04-19'], + ], + ], ['foo.*.end' => ['before:foo.*.start']]); $this->assertTrue($v->fails()); - $v = new Validator($trans, ['foo' => [ - ['start' => '2016-04-19', 'end' => '2017-04-19'], - ]], ['foo.*.end' => ['after:foo.*.start']]); + $v = new Validator($trans, [ + 'foo' => [ + ['start' => '2016-04-19', 'end' => '2017-04-19'], + ], + ], ['foo.*.end' => ['after:foo.*.start']]); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['foo' => [ - ['start' => '2016-04-19', 'end' => '2017-04-19'], - ]], ['foo.*.start' => ['after:foo.*.end']]); + $v = new Validator($trans, [ + 'foo' => [ + ['start' => '2016-04-19', 'end' => '2017-04-19'], + ], + ], ['foo.*.start' => ['after:foo.*.end']]); $this->assertTrue($v->fails()); } @@ -4475,17 +4678,19 @@ public function testCustomValidationObject() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 'taylor'], - ['name' => new class implements Rule { - public function passes($attribute, $value) - { - return $value === 'taylor'; - } + [ + 'name' => new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } - public function message() - { - return ':attribute must be taylor'; - } - }] + public function message() + { + return ':attribute must be taylor'; + } + }, + ] ); $this->assertTrue($v->passes()); @@ -4494,17 +4699,21 @@ public function message() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 'adam'], - ['name' => [new class implements Rule { - public function passes($attribute, $value) - { - return $value === 'taylor'; - } + [ + 'name' => [ + new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } - public function message() - { - return ':attribute must be taylor'; - } - }]] + public function message() + { + return ':attribute must be taylor'; + } + }, + ], + ] ); $this->assertTrue($v->fails()); @@ -4514,11 +4723,13 @@ public function message() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 'taylor'], - ['name.*' => function ($attribute, $value, $fail) { - if ($value !== 'taylor') { - $fail(':attribute was '.$value.' instead of taylor'); - } - }] + [ + 'name.*' => function ($attribute, $value, $fail) { + if ($value !== 'taylor') { + $fail(':attribute was '.$value.' instead of taylor'); + } + }, + ] ); $this->assertTrue($v->passes()); @@ -4527,11 +4738,13 @@ public function message() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 'adam'], - ['name' => function ($attribute, $value, $fail) { - if ($value !== 'taylor') { - $fail(':attribute was '.$value.' instead of taylor'); - } - }] + [ + 'name' => function ($attribute, $value, $fail) { + if ($value !== 'taylor') { + $fail(':attribute was '.$value.' instead of taylor'); + } + }, + ] ); $this->assertTrue($v->fails()); @@ -4579,17 +4792,19 @@ function ($attribute, $value, $fail) { $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 42], - ['name' => new class implements Rule { - public function passes($attribute, $value) - { - return $value === 'taylor'; - } + [ + 'name' => new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } - public function message() - { - return [':attribute must be taylor', ':attribute must be a first name']; - } - }] + public function message() + { + return [':attribute must be taylor', ':attribute must be a first name']; + } + }, + ] ); $this->assertTrue($v->fails()); @@ -4600,17 +4815,21 @@ public function message() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => 42], - ['name' => [new class implements Rule { - public function passes($attribute, $value) - { - return $value === 'taylor'; - } + [ + 'name' => [ + new class implements Rule { + public function passes($attribute, $value) + { + return $value === 'taylor'; + } - public function message() - { - return [':attribute must be taylor', ':attribute must be a first name']; - } - }, 'string']] + public function message() + { + return [':attribute must be taylor', ':attribute must be a first name']; + } + }, 'string', + ], + ] ); $this->assertTrue($v->fails()); @@ -4625,21 +4844,23 @@ public function testImplicitCustomValidationObjects() $v = new Validator( $this->getIlluminateArrayTranslator(), ['name' => ''], - ['name' => $rule = new class implements ImplicitRule { - public $called = false; + [ + 'name' => $rule = new class implements ImplicitRule { + public $called = false; - public function passes($attribute, $value) - { - $this->called = true; + public function passes($attribute, $value) + { + $this->called = true; - return true; - } + return true; + } - public function message() - { - return 'message'; - } - }] + public function message() + { + return 'message'; + } + }, + ] ); $this->assertTrue($v->passes()); @@ -4648,9 +4869,9 @@ public function message() public function testValidateReturnsValidatedData() { - $post = ['first' => 'john', 'preferred'=>'john', 'last' => 'doe', 'type' => 'admin']; + $post = ['first' => 'john', 'preferred' => 'john', 'last' => 'doe', 'type' => 'admin']; - $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred'=> 'required']); + $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred' => 'required']); $v->sometimes('type', 'required', function () { return false; }); @@ -4702,9 +4923,9 @@ public function testValidateReturnsValidatedDataNestedArrayRules() public function testValidateAndValidatedData() { - $post = ['first' => 'john', 'preferred'=>'john', 'last' => 'doe', 'type' => 'admin']; + $post = ['first' => 'john', 'preferred' => 'john', 'last' => 'doe', 'type' => 'admin']; - $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred'=> 'required']); + $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred' => 'required']); $v->sometimes('type', 'required', function () { return false; }); @@ -4717,10 +4938,10 @@ public function testValidateAndValidatedData() public function testValidatedNotValidateTwiceData() { - $post = ['first' => 'john', 'preferred'=>'john', 'last' => 'doe', 'type' => 'admin']; + $post = ['first' => 'john', 'preferred' => 'john', 'last' => 'doe', 'type' => 'admin']; $validateCount = 0; - $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred'=> 'required']); + $v = new Validator($this->getIlluminateArrayTranslator(), $post, ['first' => 'required', 'preferred' => 'required']); $v->after(function () use (&$validateCount) { $validateCount++; }); @@ -4960,25 +5181,26 @@ public function providesPassingExcludeIfData() 'vehicles.*.wheels.*.shape' => 'exclude_unless:vehicles.*.wheels.*.color,red|required|in:square,round', ], [ 'vehicles' => [ - ['type' => 'car', 'wheels' => [ - ['color' => 'red', 'shape' => 'square'], - ['color' => 'blue', 'shape' => 'hexagon'], - ['color' => 'red', 'shape' => 'round', 'junk' => 'no rule, still present'], - ['color' => 'blue', 'shape' => 'triangle'], - ]], + [ + 'type' => 'car', 'wheels' => [ + ['color' => 'red', 'shape' => 'square'], + ['color' => 'blue', 'shape' => 'hexagon'], + ['color' => 'red', 'shape' => 'round', 'junk' => 'no rule, still present'], + ['color' => 'blue', 'shape' => 'triangle'], + ], + ], ['type' => 'boat'], ], ], [ 'vehicles' => [ - ['type' => 'car', 'wheels' => [ - // The shape field for these blue wheels were correctly excluded (if they weren't, they would - // fail the validation). They still appear in the validated data. This behaviour is unrelated - // to the "exclude" type rules. - ['color' => 'red', 'shape' => 'square'], - ['color' => 'blue', 'shape' => 'hexagon'], - ['color' => 'red', 'shape' => 'round', 'junk' => 'no rule, still present'], - ['color' => 'blue', 'shape' => 'triangle'], - ]], + [ + 'type' => 'car', 'wheels' => [ + ['color' => 'red', 'shape' => 'square'], + ['color' => 'blue'], + ['color' => 'red', 'shape' => 'round', 'junk' => 'no rule, still present'], + ['color' => 'blue'], + ], + ], ['type' => 'boat'], ], ], @@ -5083,12 +5305,14 @@ public function providesFailingExcludeIfData() 'vehicles.*.wheels.*.shape' => 'exclude_unless:vehicles.*.wheels.*.color,red|required|in:square,round', ], [ 'vehicles' => [ - ['type' => 'car', 'wheels' => [ - ['color' => 'red', 'shape' => 'square'], - ['color' => 'blue', 'shape' => 'hexagon'], - ['color' => 'red', 'shape' => 'hexagon'], - ['color' => 'blue', 'shape' => 'triangle'], - ]], + [ + 'type' => 'car', 'wheels' => [ + ['color' => 'red', 'shape' => 'square'], + ['color' => 'blue', 'shape' => 'hexagon'], + ['color' => 'red', 'shape' => 'hexagon'], + ['color' => 'blue', 'shape' => 'triangle'], + ], + ], ['type' => 'boat', 'wheels' => 'should be excluded'], ], ], [ 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