diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml index 462ab05e8..26b112465 100644 --- a/.github/workflows/code-analysis.yaml +++ b/.github/workflows/code-analysis.yaml @@ -23,6 +23,21 @@ jobs: - name: Checkout changes uses: actions/checkout@v4 + - name: Setup extension cache + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: '8.2' + extensions: ${{ env.extensions }} + key: ${{ env.key }} + + - name: Cache extensions + uses: actions/cache@v2 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + - name: Install PHP uses: shivammathur/setup-php@v2 with: @@ -33,5 +48,8 @@ jobs: - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-scripts + - name: Clear phpstan cache + run: ./vendor/bin/phpstan clear-result-cache + - name: Analyse code run: ./vendor/bin/phpstan analyse --memory-limit=2G --no-progress diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index 065447d76..840f47e5f 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -4,8 +4,6 @@ on: pull_request: push: branches: - - 1.0 - - 1.1 - develop concurrency: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ebb1526e0..09d69e097 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,8 +3,6 @@ name: Tests on: push: branches: - - "1.0" - - "1.1" - develop pull_request: @@ -15,10 +13,10 @@ concurrency: jobs: unitTests: strategy: - max-parallel: 6 + max-parallel: 4 matrix: operatingSystem: [ubuntu-latest, windows-latest] - phpVersion: ['8.0', '8.1', '8.2', '8.3'] + phpVersion: ['8.2', '8.3'] fail-fast: false runs-on: ${{ matrix.operatingSystem }} name: ${{ matrix.operatingSystem }} / PHP ${{ matrix.phpVersion }} @@ -45,11 +43,3 @@ jobs: - name: Run tests run: ./vendor/bin/phpunit ./tests - - - name: Upload test artifacts on failure - uses: actions/upload-artifact@v4 - if: ${{ failure() }} - with: - name: ResizerTest-${{ matrix.operatingSystem }}-PHP${{ matrix.phpVersion }} - path: tests/artifacts/ResizerTest/ - if-no-files-found: error diff --git a/.gitignore b/.gitignore index bdee57b0e..baed57fce 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ composer.lock # Editor files .idea .vscode +*.code-workspace # Other files .DS_Store @@ -18,6 +19,5 @@ php_errors.log #phpunit tests/.phpunit.result.cache -.phpunit.result.cache tests/tmp tests/artifacts/* diff --git a/composer.json b/composer.json index 0e6c31cb4..1ef9379f8 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": "^8.0.2", + "php": "^8.2", "ext-ctype": "*", "ext-curl": "*", "ext-dom": "*", @@ -35,31 +35,32 @@ "ext-pdo": "*", "ext-zip": "*", - "assetic/framework": "~3.0", - "doctrine/dbal": "^2.6", + "assetic/framework": "^3.1", + "doctrine/dbal": "^3.0", "enshrined/svg-sanitize": "~0.16", - "laravel/framework": "^9.1", - "laravel/tinker": "^2.7", + "laravel/framework": "^11.0", + "laravel/tinker": "^2.8.1", "league/csv": "~9.1", "nesbot/carbon": "^2.0", "nikic/php-parser": "^4.10", "scssphp/scssphp": "~1.0", - "symfony/console": ">=6.0.9 <6.3.0", - "symfony/yaml": "^6.0", - "twig/twig": "^3.14", + "symfony/console": "^6.4|^7.0", + "symfony/process": "^7.1", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3", "wikimedia/less.php": "~3.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.0.1" }, "require-dev": { - "phpunit/phpunit": "^9.5.8", - "mockery/mockery": "^1.4.4", + "phpunit/phpunit": "^10.5|^11.0", "squizlabs/php_codesniffer": "^3.2", "php-parallel-lint/php-parallel-lint": "^1.0", - "meyfa/phpunit-assert-gd": "^3.0.0", - "dms/phpunit-arraysubset-asserts": "^0.5.0", - "larastan/larastan": "^2.8.1", - "orchestra/testbench": "^7.1.0" + "meyfa/phpunit-assert-gd": "^4.0", + "mockery/mockery": "^1.6|^2.0", + "dms/phpunit-arraysubset-asserts": "^0.5", + "orchestra/testbench": "^9.0", + "larastan/larastan": "^2.8.1" }, "suggest": { "ext-pdo_dblib": "Required to use MS SQL Server databases", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0e626dc55..daaaca3c3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,662 +1,137 @@ parameters: ignoreErrors: - - message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" - count: 1 - path: src/Auth/Models/Group.php + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\(Group|Role|User)\\)::getOriginal(Encryptable|Hash)Values\\(\\)\\.$#" + paths: + - src/Auth/Models/Group.php + - src/Auth/Models/Role.php + - src/Auth/Models/User.php - - message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\)\\:\\:getOriginalHashValues\\(\\)\\.$#" - count: 1 - path: src/Auth/Models/Group.php - - - - message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" - count: 1 - path: src/Auth/Models/Role.php - - - - message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\)\\:\\:getOriginalHashValues\\(\\)\\.$#" - count: 1 - path: src/Auth/Models/Role.php - - - - message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\User\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" - count: 1 - path: src/Auth/Models/User.php - - - - message: "#^Parameter \\#1 \\$app of class Illuminate\\\\Database\\\\DatabaseManager constructor expects Illuminate\\\\Contracts\\\\Foundation\\\\Application, Illuminate\\\\Contracts\\\\Container\\\\Container given\\.$#" - count: 1 + messages: + - "#^Parameter \\#1 \\$app of class Illuminate\\\\Database\\\\DatabaseManager constructor expects Illuminate\\\\Contracts\\\\Foundation\\\\Application, Illuminate\\\\Contracts\\\\Container\\\\Container given\\.$#" path: src/Database/Capsule/Manager.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getName\\(\\)\\.$#" - count: 1 + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface::getName\\(\\)\\.$#" path: src/Database/MemoryCache.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$is_bind\\.$#" - count: 1 + messages: + - "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model::\\$(is_bind|master_field|pivot_data|slave_(id|type))\\.$#" + - "#^Method Winter\\\\Storm\\\\Database\\\\Model::getDeferredBindingRecords\\(\\) should return Winter\\\\Storm\\\\Database\\\\Collection but returns Illuminate\\\\Database\\\\Eloquent\\\\Collection\\\\.$#" + - "#^Return type \\(Winter\\\\Storm\\\\Database\\\\Pivot\\) of method Winter\\\\Storm\\\\Database\\\\Model::newPivot\\(\\) should be compatible with return type \\(Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Pivot\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Model::newPivot\\(\\)$#" + - "#^Return type \\(Winter\\\\Storm\\\\Database\\\\Collection\\) of method Winter\\\\Storm\\\\Database\\\\Model::newCollection\\(\\) should be compatible with return type \\(Illuminate\\\\Database\\\\Eloquent\\\\Collection\\<\\(int\\|string\\), static\\(Illuminate\\\\Database\\\\Eloquent\\\\Model\\)\\>\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Model::newCollection\\(\\)#" path: src/Database/Model.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$master_field\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$pivot_data\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$slave_id\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$slave_type\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Model\\:\\:getDeferredBindingRecords\\(\\) should return Winter\\\\Storm\\\\Database\\\\Collection but returns Illuminate\\\\Database\\\\Eloquent\\\\Collection\\\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Return type \\(Winter\\\\Storm\\\\Database\\\\Pivot\\) of method Winter\\\\Storm\\\\Database\\\\Model\\:\\:newPivot\\(\\) should be compatible with return type \\(Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Pivot\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:newPivot\\(\\)$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Static property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$dispatcher \\(Illuminate\\\\Contracts\\\\Events\\\\Dispatcher\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: src/Database/Model.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Model\\:\\:errors\\(\\)\\.$#" - count: 1 + messages: + - "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Model::errors\\(\\)\\.$#" path: src/Database/ModelException.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:deleteCancel\\(\\)\\.$#" - count: 2 + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model::deleteCancel\\(\\)\\.$#" path: src/Database/Models/DeferredBinding.php - - message: "#^Parameter \\#1 \\$haystack of function str_contains expects string, int given\\.$#" - count: 1 - path: src/Database/MorphPivot.php - - - - message: "#^Parameter \\#1 \\$ids of method Winter\\\\Storm\\\\Database\\\\Pivot\\:\\:newQueryForRestoration\\(\\) expects array\\\\|string, int given\\.$#" - count: 1 - path: src/Database/MorphPivot.php - - - - message: "#^Parameter \\#2 \\$string of function explode expects string, int given\\.$#" - count: 1 + messages: + - "#^Parameter \\#1 \\$haystack of function str_contains expects string, int given\\.$#" + - "#^Parameter \\#1 \\$ids of method Winter\\\\Storm\\\\Database\\\\Pivot::newQueryForRestoration\\(\\) expects array\\\\|string, int given\\.$#" + - "#^Parameter \\#2 \\$string of function explode expects string, int given\\.$#" path: src/Database/MorphPivot.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getLeftColumnName\\(\\)\\.$#" - count: 1 + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model::getLeftColumnName\\(\\)\\.$#" path: src/Database/NestedTreeScope.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" - count: 2 - path: src/Database/Query/Grammars/MySqlGrammar.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" - count: 2 - path: src/Database/Query/Grammars/PostgresGrammar.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" - count: 2 - path: src/Database/Query/Grammars/SQLiteGrammar.php + messages: + - "#^Access to an undefined property Illuminate.+(Builder|(Column|Index)Definition|Fluent)::\\$(.+)\\.$#" + paths: + - src/Database/Schema/Grammars/*Grammar.php + - src/Database/Query/Grammars/*Grammar.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" - count: 2 - path: src/Database/Query/Grammars/SqlServerGrammar.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getName\\(\\)\\.$#" - count: 1 - path: src/Database/QueryBuilder.php - - - - message: "#^Property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$orders \\(array\\) does not accept null\\.$#" - count: 1 + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface::getName\\(\\)\\.$#" path: src/Database/QueryBuilder.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachMany.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:getRelationExistenceQueryForSelfJoin\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:getRelationExistenceQueryForSelfJoin\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Call to private method delete\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphOne\\\\.$#" - count: 3 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation::(getForeignKey|select|with(Default|Pivot|Timestamps))\\(\\)\\.$#" + - "#^Call to an undefined method (TDeclaringModel of )?Illuminate\\\\Database\\\\Eloquent\\\\Model::((un)?bind(Deferred|EventOnce)|fireEvent|getRelationDefinition|newRelationPivot|reloadRelations)\\(\\)\\.$#" + - "#^Call to an undefined method Illuminate\\\\Database\\\\Query\\\\Builder::lists\\(\\)\\.$#" + - "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\(Attach|(Belongs|Morph)To)(One|Many)::(flushDuplicateCache|getRelationExistenceQueryForSelfJoin)\\(\\)\\.$#" + - "#^Call to private method .+ of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations#" + paths: + - src/Database/Relations/*.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php + messages: + - "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model::\\$(children|sessionKey)\\.$#" + - "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\::(simpleP|p)aginate\\(\\) expects string, array given\\.$#" + - "#^Parameter \\#4 \\$page of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\::(simpleP|p)aginate\\(\\) expects int\\|null, string given.$#" - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\:\\:getForeignKey\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php - - - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$sessionKey\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindDeferred\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" - count: 2 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:fireEvent\\(\\)\\.$#" - count: 4 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getRelationDefinition\\(\\)\\.$#" - count: 2 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:newRelationPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:reloadRelations\\(\\)\\.$#" - count: 2 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:unbindDeferred\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:lists\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:flushDuplicateCache\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects array, int\\|null given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#2 \\$currentPage \\(int\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$columns \\(array\\\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#2 \\$currentPage \\(int\\|null\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$columns \\(array\\\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects string, array given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects string, array given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#4 \\$page of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects int\\|null, string given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#4 \\$page of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects int\\|null, string given\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php + paths: + - src/Database/Relations/* - - message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasMany\\\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^Call to private method whereNotIn\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasMany\\\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^If condition is always true\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasManyThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasManyThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasManyThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOne.php - - - - message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasOne\\\\.$#" - count: 2 - path: src/Database/Relations/HasOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOneThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOneThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/HasOneThrough.php - - - - message: "#^Instanceof between \\*NEVER\\* and Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough will always evaluate to false\\.$#" - count: 4 - path: src/Database/Relations/HasOneThrough.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\:\\:getForeignKey\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - - message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphMany\\\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Call to private method whereNotIn\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphMany\\\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^If condition is always true\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\:\\:getForeignKey\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphOne\\\\.$#" - count: 2 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphTo\\:\\:getForeignKey\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:lists\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:flushDuplicateCache\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects array, int\\|null given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#2 \\$currentPage \\(int\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$columns \\(array\\\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#2 \\$currentPage \\(int\\|null\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$columns \\(array\\\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects string, array given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects string, array given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#4 \\$page of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects int\\|null, string given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#4 \\$page of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:simplePaginate\\(\\) expects int\\|null, string given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:simplePaginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:simplePaginate\\(\\)$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getSortOrderColumn\\(\\)\\.$#" - count: 1 + messages: + - "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model::getSortOrderColumn\\(\\)\\.$#" path: src/Database/SortableScope.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$children\\.$#" - count: 1 - path: src/Database/TreeCollection.php - - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getParentId\\(\\)\\.$#" - count: 1 + messages: + - "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model::\\$children\\.$#" + - "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model::getParentId\\(\\)\\.$#" path: src/Database/TreeCollection.php - - message: "#^Call to an undefined method Illuminate\\\\Routing\\\\Router\\:\\:after\\(\\)\\.$#" - count: 1 - path: src/Foundation/Application.php - - - - message: "#^Call to an undefined method Illuminate\\\\Routing\\\\Router\\:\\:before\\(\\)\\.$#" - count: 1 - path: src/Foundation/Application.php - - - - message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:queue\\(\\)$#" - count: 1 - path: src/Mail/Mailer.php - - - - message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:queue\\(\\)$#" - count: 1 - path: src/Mail/Mailer.php - - - - message: "#^Parameter \\#2 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queueOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:queueOn\\(\\)$#" - count: 1 - path: src/Mail/Mailer.php - - - - message: "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:later\\(\\)$#" - count: 1 + messages: + - "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue::queue\\(\\)$#" + - "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer::queue\\(\\)$#" + - "#^Parameter \\#2 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::queueOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer::queueOn\\(\\)$#" + - "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue::later\\(\\)$#" + - "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer::later\\(\\)$#" + - "#^Parameter \\#3 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer::laterOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer::laterOn\\(\\)$#" path: src/Mail/Mailer.php - - message: "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:later\\(\\)$#" - count: 1 - path: src/Mail/Mailer.php + messages: + - "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake::queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue::queue\\(\\)$#" + - "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake::queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Support\\\\Testing\\\\Fakes\\\\MailFake::queue\\(\\)$#" + - "#^Return type \\(void\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake::send\\(\\) should be compatible with return type \\(Illuminate\\\\Mail\\\\SentMessage\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\Mailer::send\\(\\)$#" + path: src/Support/Testing/Fakes/MailFake.php - - message: "#^Parameter \\#3 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:laterOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:laterOn\\(\\)$#" - count: 1 - path: src/Mail/Mailer.php + messages: + - "#^Call to an undefined method Illuminate\\\\Routing\\\\Router::(after|before)\\(\\)\\.$#" + path: src/Foundation/Application.php - - message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:queue\\(\\)$#" - count: 1 - path: src/Support/Testing/Fakes/MailFake.php + messages: + - "#^Instanceof between \\*NEVER\\* and Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough will always evaluate to false\\.$#" + paths: + - src/Database/Relations/Concerns/DefinedConstraints.php + - src/Database/Relations/Concerns/CanBeCounted.php - - message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\)$#" - count: 1 - path: src/Support/Testing/Fakes/MailFake.php + messages: + - "#Method Winter\\\\Storm\\\\Database\\\\Model::new(Has(One|Many)(Through)?|BelongsTo(Many)?|Morph(One|To|Many|ToMany))\\(\\) should return Illuminate\\\\Database\\\\Eloquent\\\\Relations#" + paths: + - src/Database/Concerns/HasRelationships.php - - message: "#^Return type \\(void\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:send\\(\\) should be compatible with return type \\(Illuminate\\\\Mail\\\\SentMessage\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\Mailer\\:\\:send\\(\\)$#" - count: 1 - path: src/Support/Testing/Fakes/MailFake.php + messages: + - "#^Parameter .+ of method (Illuminate|Winter).+::(paginate|simplePaginate).+ (expects|should be compatible with)#" + paths: + - src/Database/Relations/Concerns/BelongsOrMorphsToMany.php - - message: "#^Call to function is_null\\(\\) with Closure will always evaluate to false\\.$#" - count: 1 - path: src/Validation/Factory.php + messages: + - "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\(Morph(One|To|Many)|BelongsTo|Has(One|Many)Through)::getForeignKey\\(\\)\\.$#" + paths: + - src/Database/Relations/Concerns/CanBeCounted.php diff --git a/phpstan.neon b/phpstan.neon index 37b9e0a2a..423e6134d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,13 +12,10 @@ parameters: - src/Parse/PHP/ArrayPrinter.php - src/Foundation/Console/KeyGenerateCommand.php - src/Scaffold/GeneratorCommand.php - ignoreErrors: - - message: '#Call to private method select\(\)#' - paths: - - src/Database/Relations/Concerns/CanBeCounted.php disableSchemaScan: true databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations stubFiles: - tests/stubs/Facades.stub + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 552bbd2a7..1a9bf1ea1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,26 +1,24 @@ - - - ./src/ - - - - ./tests - + + ./tests + + + + ./src/ + + diff --git a/src/Auth/Manager.php b/src/Auth/Manager.php index fe4ff7549..02935431c 100644 --- a/src/Auth/Manager.php +++ b/src/Auth/Manager.php @@ -171,9 +171,10 @@ public function hasUser() * Sets the user * @phpstan-param Models\User $user */ - public function setUser(Authenticatable $user) + public function setUser(Authenticatable $user): static { $this->user = $user; + return $this; } /** diff --git a/src/Auth/Models/Throttle.php b/src/Auth/Models/Throttle.php index a4cdbea58..ba03df86c 100644 --- a/src/Auth/Models/Throttle.php +++ b/src/Auth/Models/Throttle.php @@ -37,10 +37,13 @@ class Throttle extends Model /** * The attributes that should be mutated to dates. * - * @var array + * @var array */ - protected $dates = ['last_attempt_at', 'suspended_at', 'banned_at']; - + protected $casts = [ + 'suspended_at' => 'datetime', + 'last_attempt_at' => 'datetime', + 'banned_at' => 'datetime', + ]; /** * @var int Attempt limit. */ diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php index 3865d0a9f..64f6246e2 100644 --- a/src/Auth/Models/User.php +++ b/src/Auth/Models/User.php @@ -110,6 +110,13 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable */ protected $rememberTokenName = 'persist_code'; + /** + * The column name of the password field using during authentication. + * + * @var string + */ + protected $authPasswordName = 'password'; + /** * @var array The user merged permissions. */ @@ -248,7 +255,7 @@ public function attemptActivation($activationCode) */ public function checkPassword($password) { - return Hash::check($password, $this->password); + return Hash::check($password, $this->getAuthPassword()); } /** @@ -285,7 +292,7 @@ public function checkResetPasswordCode($resetCode) public function attemptResetPassword($resetCode, $newPassword) { if ($this->checkResetPasswordCode($resetCode)) { - $this->password = $newPassword; + $this->{$this->getAuthPasswordName()} = $newPassword; $this->reset_password_code = null; return $this->forceSave(); } @@ -600,13 +607,23 @@ public function getAuthIdentifier() return $this->{$this->getAuthIdentifierName()}; } + /** + * Get the name of the password attribute for the user. + * + * @return string + */ + public function getAuthPasswordName() + { + return $this->authPasswordName; + } + /** * Get the password for the user. * @return string */ public function getAuthPassword() { - return $this->password; + return $this->{$this->getAuthPasswordName()}; } /** diff --git a/src/Console/Traits/HandlesCleanup.php b/src/Console/Traits/HandlesCleanup.php index 223705436..a4bd72cf4 100644 --- a/src/Console/Traits/HandlesCleanup.php +++ b/src/Console/Traits/HandlesCleanup.php @@ -39,7 +39,7 @@ public function getSubscribedSignals(): array /** * Handle the provided Unix process signal */ - public function handleSignal(int $signal): void + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false { // Handle the signal if (method_exists($this, 'handleCleanup')) { @@ -48,8 +48,10 @@ public function handleSignal(int $signal): void // Exit cleanly at this point if this was a user termination if (in_array($signal, [SIGINT, SIGQUIT])) { - exit(0); + return 0; } + + return false; } /** diff --git a/src/Database/Builder.php b/src/Database/Builder.php index f42dcf2a2..345e16263 100644 --- a/src/Database/Builder.php +++ b/src/Database/Builder.php @@ -120,20 +120,21 @@ protected function searchWhereInternal($term, $columns, $mode, $boolean) * * This method also accepts the Laravel signature: * - * `paginate(int|null $perPage, array $columns, string $pageName, int|null $page)` + * `paginate(int|null $perPage, array $columns, string $pageName, int|null $page, \Closure|int|null $total)` * * @param int|null $perPage * @param array|int|null $currentPage * @param array|string $columns * @param string|int|null $pageName + * @param \Closure|int|null $total * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - public function paginate($perPage = null, $currentPage = null, $columns = ['*'], $pageName = 'page') + public function paginate($perPage = null, $currentPage = null, $columns = ['*'], $pageName = 'page', $total = null) { /* * Engage Laravel signature support * - * paginate($perPage, $columns, $pageName, $currentPage) + * paginate($perPage, $columns, $pageName, $currentPage, $total) */ if (is_array($currentPage)) { $_columns = $columns; @@ -153,7 +154,7 @@ public function paginate($perPage = null, $currentPage = null, $columns = ['*'], $perPage = $this->model->getPerPage(); } - $total = $this->toBase()->getCountForPagination(); + $total = value($total) ?? $this->toBase()->getCountForPagination(); $this->forPage((int) $currentPage, (int) $perPage); return $this->paginator($this->get($columns), $total, $perPage, $currentPage, [ diff --git a/src/Database/Concerns/HasAttributes.php b/src/Database/Concerns/HasAttributes.php new file mode 100644 index 000000000..dafe0b99f --- /dev/null +++ b/src/Database/Concerns/HasAttributes.php @@ -0,0 +1,34 @@ + 'datetime', + * ]; + * ``` + * + * @return array + */ + public function getDates() + { + if (! $this->usesTimestamps()) { + return $this->dates; + } + + $defaults = [ + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ]; + + return array_unique(array_merge($this->dates, $defaults)); + } +} diff --git a/src/Database/Connections/Connection.php b/src/Database/Connections/Connection.php index e42c91f50..1b4b9a628 100644 --- a/src/Database/Connections/Connection.php +++ b/src/Database/Connections/Connection.php @@ -1,74 +1,14 @@ getQueryGrammar(), - $this->getPostProcessor() - ); - } + use HasConnection; - /** - * Flush the memory cache. - * @return void - */ - public static function flushDuplicateCache() - { - MemoryCache::instance()->flush(); - } - - /** - * Log a query in the connection's query log. - * - * @param string $query - * @param array $bindings - * @param float|null $time - * @return void - */ - public function logQuery($query, $bindings, $time = null) - { - $this->fireEvent('illuminate.query', [$query, $bindings, $time, $this->getName()]); - - parent::logQuery($query, $bindings, $time); - } - - /** - * Fire an event for this connection. - * - * @param string $event - * @return array|null - */ - protected function fireConnectionEvent($event) - { - $this->fireEvent('connection.'.$this->getName().'.'.$event, $this); - - parent::fireConnectionEvent($event); - } - - /** - * Fire the given event if possible. - */ - protected function fireEvent(string $event, array|object $attributes = []): void - { - /** @var \Winter\Storm\Events\Dispatcher|null */ - $eventManager = $this->events; - - if (!isset($eventManager)) { - return; - } - - $eventManager->dispatch($event, $attributes); - } + abstract protected function getDoctrineDriver(); } diff --git a/src/Database/Connections/MariaDbConnection.php b/src/Database/Connections/MariaDbConnection.php new file mode 100644 index 000000000..a1cb86d0e --- /dev/null +++ b/src/Database/Connections/MariaDbConnection.php @@ -0,0 +1,90 @@ +withTablePrefix(new QueryGrammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\MariaDbBuilder + */ + public function getSchemaBuilder() + { + if (!isset($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new MariaDbBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Grammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\MariaDbProcessor + */ + protected function getDefaultPostProcessor() + { + return new MariaDbProcessor; + } + + /** + * Bind values to their parameters in the given statement. + * + * @param \PDOStatement $statement + * @param array $bindings + * @return void + */ + public function bindValues($statement, $bindings) + { + foreach ($bindings as $key => $value) { + $statement->bindValue( + is_string($key) ? $key : $key + 1, + $value, + is_int($value) || is_float($value) ? PDO::PARAM_INT : PDO::PARAM_STR + ); + } + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Winter\Storm\Database\PDO\MySqlDriver + */ + protected function getDoctrineDriver() + { + return new MySqlDriver; + } +} diff --git a/src/Database/Connections/MySqlConnection.php b/src/Database/Connections/MySqlConnection.php index 2458a9ee6..aab13d275 100644 --- a/src/Database/Connections/MySqlConnection.php +++ b/src/Database/Connections/MySqlConnection.php @@ -1,17 +1,21 @@ registerEloquentFactory(); - $this->registerQueueableEntityResolver(); // The connection factory is used to create the actual connection instances on diff --git a/src/Database/Model.php b/src/Database/Model.php index 1f7fed123..09af020d2 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -22,6 +22,7 @@ */ class Model extends EloquentModel implements ModelInterface { + use Concerns\HasAttributes; use Concerns\GuardsAttributes; use Concerns\HasRelationships; use Concerns\HidesAttributes; @@ -105,7 +106,6 @@ public function isDatabaseReady(): bool } // Resolver hasn't been set yet - /** @phpstan-ignore-next-line */ if (!static::getConnectionResolver()) { return false; } @@ -1051,20 +1051,6 @@ public function addJsonable($attributes = null) // Getters // - /** - * Determine if the given attribute will be processed by getAttributeValue(). - */ - public function hasAttribute(string $key): bool - { - return ( - array_key_exists($key, $this->attributes) - || array_key_exists($key, $this->casts) - || $this->hasGetMutator($key) - || $this->hasAttributeMutator($key) - || $this->isClassCastable($key) - ); - } - /** * Get an attribute from the model. * Overrides {@link Eloquent} to support loading from property-defined relations. diff --git a/src/Database/PDO/Concerns/ConnectsToDatabase.php b/src/Database/PDO/Concerns/ConnectsToDatabase.php new file mode 100644 index 000000000..cdffb75a2 --- /dev/null +++ b/src/Database/PDO/Concerns/ConnectsToDatabase.php @@ -0,0 +1,30 @@ +connection = $connection; + } + + /** + * Execute an SQL statement. + * + * @param string $statement + * @return int + */ + public function exec(string $statement): int + { + try { + $result = $this->connection->exec($statement); + + \assert($result !== false); + + return $result; + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Prepare a new SQL statement. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\PDO\Statement + */ + public function prepare(string $sql): StatementInterface + { + try { + return $this->createStatement( + $this->connection->prepare($sql) + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Execute a new query against the connection. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Result + */ + public function query(string $sql): ResultInterface + { + try { + $stmt = $this->connection->query($sql); + + \assert($stmt instanceof PDOStatement); + + return new Result($stmt); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Get the last insert ID. + * + * @param string|null $name + * @return mixed + */ + public function lastInsertId($name = null) + { + try { + if ($name === null) { + return $this->connection->lastInsertId(); + } + + return $this->connection->lastInsertId($name); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Create a new statement instance. + * + * @param \PDOStatement $stmt + * @return \Doctrine\DBAL\Driver\PDO\Statement + */ + protected function createStatement(PDOStatement $stmt): Statement + { + return new Statement($stmt); + } + + /** + * Begin a new database transaction. + * + * @return bool + */ + public function beginTransaction() + { + return $this->connection->beginTransaction(); + } + + /** + * Commit a database transaction. + * + * @return bool + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * Rollback a database transaction. + * + * @return bool + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * Wrap quotes around the given input. + * + * @param string $input + * @param int $type + * @return string + */ + public function quote($input, $type = ParameterType::STRING) + { + return $this->connection->quote($input, $type); + } + + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion() + { + return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * Get the wrapped PDO connection. + * + * @return \PDO + */ + public function getWrappedConnection(): PDO + { + return $this->connection; + } +} diff --git a/src/Database/PDO/MySqlDriver.php b/src/Database/PDO/MySqlDriver.php new file mode 100644 index 000000000..5a3c449c4 --- /dev/null +++ b/src/Database/PDO/MySqlDriver.php @@ -0,0 +1,19 @@ +connection = $connection; + } + + /** + * Prepare a new SQL statement. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Statement + */ + public function prepare(string $sql): StatementInterface + { + return new Statement( + $this->connection->prepare($sql) + ); + } + + /** + * Execute a new query against the connection. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Result + */ + public function query(string $sql): Result + { + return $this->connection->query($sql); + } + + /** + * Execute an SQL statement. + * + * @param string $statement + * @return int + */ + public function exec(string $statement): int + { + return $this->connection->exec($statement); + } + + /** + * Get the last insert ID. + * + * @param string|null $name + * @return mixed + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return $this->connection->lastInsertId($name); + } + + return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') + ->execute([$name]) + ->fetchOne(); + } + + /** + * Begin a new database transaction. + * + * @return bool + */ + public function beginTransaction() + { + return $this->connection->beginTransaction(); + } + + /** + * Commit a database transaction. + * + * @return bool + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * Rollback a database transaction. + * + * @return bool + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * Wrap quotes around the given input. + * + * @param string $value + * @param int $type + * @return string + */ + public function quote($value, $type = ParameterType::STRING) + { + $val = $this->connection->quote($value, $type); + + // Fix for a driver version terminating all values with null byte... + if (\is_string($val) && str_contains($val, "\0")) { + $val = \substr($val, 0, -1); + } + + return $val; + } + + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion() + { + return $this->connection->getServerVersion(); + } + + /** + * Get the wrapped PDO connection. + * + * @return \PDO + */ + public function getWrappedConnection(): PDO + { + return $this->connection->getWrappedConnection(); + } +} diff --git a/src/Database/PDO/SqlServerDriver.php b/src/Database/PDO/SqlServerDriver.php new file mode 100644 index 000000000..633565d9c --- /dev/null +++ b/src/Database/PDO/SqlServerDriver.php @@ -0,0 +1,32 @@ +newQuery() - ->from(new Expression('(' . $clone->toSql() . ') as ' . $this->grammar->wrap('aggregate_table'))) + ->from($clone, $this->grammar->wrap('aggregate_table')) ->mergeBindings($clone) ->setAggregate('count', $this->withoutSelectAliases($columns)) ->get() diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php index 519c9d8ba..3bd9cc66e 100644 --- a/src/Database/Relations/Concerns/DeferOneOrMany.php +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -1,5 +1,6 @@ where('master_type', get_class($this->parent)) ->where('session_key', $sessionKey) ->where('is_bind', 0) - ->whereRaw(DbDongle::parse('id > ifnull((select max(id) from '.DbDongle::getTablePrefix().'deferred_bindings where - slave_id = '.$this->getWithDeferredQualifiedKeyName().' and + ->whereRaw(DbDongle::parse('id > ifnull((select max(id) from ' . DbDongle::getTablePrefix() . 'deferred_bindings where + slave_id = ' . $this->getWithDeferredQualifiedKeyName() . ' and master_field = ? and master_type = ? and session_key = ? and @@ -122,13 +123,12 @@ public function withDeferred($sessionKey) /** * Returns the related "slave id" key in a database friendly format. - * @return \Illuminate\Database\Query\Expression */ - protected function getWithDeferredQualifiedKeyName() + protected function getWithDeferredQualifiedKeyName(): string { return $this->parent->getConnection()->raw(DbDongle::cast( DbDongle::getTablePrefix() . $this->related->getQualifiedKeyName(), 'TEXT' - )); + ))->getValue($this->parent->getGrammar()); } } diff --git a/src/Database/Schema/Grammars/Concerns/MySqlBasedGrammar.php b/src/Database/Schema/Grammars/Concerns/MySqlBasedGrammar.php new file mode 100644 index 000000000..963acdcef --- /dev/null +++ b/src/Database/Schema/Grammars/Concerns/MySqlBasedGrammar.php @@ -0,0 +1,66 @@ +getSchemaBuilder()->getColumns($blueprint->getTable())); + + foreach ($blueprint->getChangedColumns() as $column) { + $sql = sprintf( + '%s %s%s %s', + is_null($column->renameTo) ? 'modify' : 'change', + $this->wrap($column), + is_null($column->renameTo) ? '' : ' '.$this->wrap($column->renameTo), + $this->getType($column) + ); + + $oldColumn = $oldColumns->where('name', $column->name)->first(); + if (!$oldColumn instanceof ColumnDefinition) { + $oldColumn = new ColumnDefinition($oldColumn); + } + + foreach ($this->modifiers as $modifier) { + if (method_exists($this, $method = "modify{$modifier}")) { + $mod = strtolower($modifier); + $col = isset($oldColumn->{$mod}) && !isset($column->{$mod}) ? $oldColumn : $column; + $sql .= $this->{$method}($blueprint, $col); + } + } + $columns[] = $sql; + } + + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + } + + public function getDefaultValue($value) + { + if (is_string($value)) { + $value = preg_replace('#\'#', '', $value); + } + + return parent::getDefaultValue($value); + } +} diff --git a/src/Database/Schema/Grammars/MariaDbGrammar.php b/src/Database/Schema/Grammars/MariaDbGrammar.php new file mode 100755 index 000000000..fbc4f6ca2 --- /dev/null +++ b/src/Database/Schema/Grammars/MariaDbGrammar.php @@ -0,0 +1,10 @@ +getSchemaBuilder()->getColumns($blueprint->getTable())); + + foreach ($blueprint->getChangedColumns() as $column) { + $changes = ['type '.$this->getType($column).$this->modifyCollate($blueprint, $column)]; + + $oldColumn = $oldColumns->where('name', $column->name)->first(); + if (!$oldColumn instanceof ColumnDefinition) { + $oldColumn = new ColumnDefinition($oldColumn); + } + + foreach ($this->modifiers as $modifier) { + if ($modifier === 'Collate') { + continue; + } + + if (method_exists($this, $method = "modify{$modifier}")) { + $mod = strtolower($modifier); + $col = isset($oldColumn->{$mod}) && ! isset($column->{$mod}) ? $oldColumn : $column; + $constraints = (array) $this->{$method}($blueprint, $col); + + foreach ($constraints as $constraint) { + $changes[] = $constraint; + } + } + } + + $columns[] = implode(', ', $this->prefixArray('alter column '.$this->wrap($column), $changes)); + } + + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + } + + public function getDefaultValue($value) + { + if (is_string($value)) { + $value = preg_replace('#\'#', '', $value); + } + + return parent::getDefaultValue($value); + } +} diff --git a/src/Database/Schema/Grammars/SQLiteGrammar.php b/src/Database/Schema/Grammars/SQLiteGrammar.php new file mode 100755 index 000000000..bbb6e7b44 --- /dev/null +++ b/src/Database/Schema/Grammars/SQLiteGrammar.php @@ -0,0 +1,171 @@ +getSchemaBuilder(); + $table = $blueprint->getTable(); + + $changedColumns = collect($blueprint->getChangedColumns()); + $columnNames = []; + $autoIncrementColumn = null; + + $oldColumns = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())); + + $columns = collect($schema->getColumns($table)) + ->map(function ($column) use ($blueprint, $changedColumns, &$columnNames, &$autoIncrementColumn, $oldColumns) { + $column = $changedColumns->first(fn ($col) => $col->name === $column['name'], $column); + + if (! $column instanceof Fluent) { + $isGenerated = ! is_null($column['generation']); + $column = new ColumnDefinition([ + 'change' => true, + 'name' => $column['name'], + 'type' => $column['type_name'], + 'nullable' => $column['nullable'], + 'default' => $column['default'] ? new Expression($column['default']) : null, + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + 'virtualAs' => $isGenerated && $column['generation']['type'] === 'virtual' + ? $column['generation']['expression'] : null, + 'storedAs' => $isGenerated && $column['generation']['type'] === 'stored' + ? $column['generation']['expression'] : null, + ]); + } + + $name = $this->wrap($column); + $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; + + if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && + is_null($column->storedAs) && is_null($column->storedAsJson) + ) { + $columnNames[] = $name; + } + + $oldColumn = $oldColumns->where('name', $column->name)->first(); + if (!$oldColumn instanceof ColumnDefinition) { + $oldColumn = new ColumnDefinition($oldColumn); + } + $sql = $name.' '.$this->getType($column); + + foreach ($this->modifiers as $modifier) { + if (method_exists($this, $method = "modify{$modifier}")) { + $mod = strtolower($modifier); + $col = isset($oldColumn->{$mod}) && !isset($column->{$mod}) ? $oldColumn : $column; + $sql .= $this->{$method}($blueprint, $col); + } + } + return $sql; + })->all(); + + $foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ + 'columns' => $foreignKey['columns'], + 'on' => $foreignKey['foreign_table'], + 'references' => $foreignKey['foreign_columns'], + 'onUpdate' => $foreignKey['on_update'], + 'onDelete' => $foreignKey['on_delete'], + ]))->all(); + + [$primary, $indexes] = collect($schema->getIndexes($table))->map(fn ($index) => new IndexDefinition([ + 'name' => match (true) { + $index['primary'] => 'primary', + $index['unique'] => 'unique', + default => 'index', + }, + 'index' => $index['name'], + 'columns' => $index['columns'], + ]))->partition(fn ($index) => $index->name === 'primary'); + + $indexes = collect($indexes)->reject(fn ($index) => str_starts_with('sqlite_', $index->index))->map( + fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index) + )->all(); + + $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$table); + $table = $this->wrapTable($blueprint); + $columnNames = implode(', ', $columnNames); + + $foreignKeyConstraintsEnabled = $connection->scalar('pragma foreign_keys'); + + return array_filter( + array_merge( + [ + $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, + sprintf( + 'create table %s (%s%s%s)', + $tempTable, + implode(', ', $columns), + $this->addForeignKeys($foreignKeys), + $autoIncrementColumn ? '' : $this->addPrimaryKeys($primary->first()) + ), + sprintf( + 'insert into %s (%s) select %s from %s', + $tempTable, + $columnNames, + $columnNames, + $table + ), + sprintf( + 'drop table %s', + $table + ), + sprintf( + 'alter table %s rename to %s', + $tempTable, + $table + ), + ], + $indexes, + [ + $foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null + ] + ) + ); + } + + public function getDefaultValue($value) + { + if (is_string($value)) { + $value = preg_replace('#\'#', '', $value); + } + + return parent::getDefaultValue($value); + } + + /** + * Create the column definition for a varchar type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeVarChar(Fluent $column) + { + return 'varchar'; + } +} diff --git a/src/Database/Schema/Grammars/SqlServerGrammar.php b/src/Database/Schema/Grammars/SqlServerGrammar.php new file mode 100755 index 000000000..770b71dac --- /dev/null +++ b/src/Database/Schema/Grammars/SqlServerGrammar.php @@ -0,0 +1,67 @@ +compileDropDefaultConstraint($blueprint, $command)]; + $oldColumns = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())); + + foreach ($blueprint->getChangedColumns() as $column) { + $sql = sprintf( + 'alter table %s alter column %s %s', + $this->wrapTable($blueprint), + $this->wrap($column), + $this->getType($column) + ); + + $oldColumn = $oldColumns->where('name', $column->name)->first(); + if (!$oldColumn instanceof ColumnDefinition) { + $oldColumn = new ColumnDefinition($oldColumn); + } + + foreach ($this->modifiers as $modifier) { + if (method_exists($this, $method = "modify{$modifier}")) { + $mod = strtolower($modifier); + $col = isset($oldColumn->{$mod}) && !isset($column->{$mod}) ? $oldColumn : $column; + $sql .= $this->{$method}($blueprint, $col); + } + } + + $changes[] = $sql; + } + + return $changes; + } + + public function getDefaultValue($value) + { + if (is_string($value)) { + $value = preg_replace('#\'#', '', $value); + } + + return parent::getDefaultValue($value); + } +} diff --git a/src/Database/Traits/HasConnection.php b/src/Database/Traits/HasConnection.php new file mode 100644 index 000000000..905ad264f --- /dev/null +++ b/src/Database/Traits/HasConnection.php @@ -0,0 +1,193 @@ + + */ + protected $doctrineTypeMappings = []; + + /** + * Get a new query builder instance. + * + * @return \Winter\Storm\Database\QueryBuilder + */ + public function query() + { + return new QueryBuilder( + $this, + $this->getQueryGrammar(), + $this->getPostProcessor() + ); + } + + /** + * Flush the memory cache. + * @return void + */ + public static function flushDuplicateCache() + { + MemoryCache::instance()->flush(); + } + + /** + * Log a query in the connection's query log. + * + * @param string $query + * @param array $bindings + * @param float|null $time + * @return void + */ + public function logQuery($query, $bindings, $time = null) + { + $this->fireEvent('illuminate.query', [$query, $bindings, $time, $this->getName()]); + + parent::logQuery($query, $bindings, $time); + } + + /** + * Fire an event for this connection. + * + * @param string $event + * @return array|null + */ + protected function fireConnectionEvent($event) + { + $this->fireEvent('connection.'.$this->getName().'.'.$event, $this); + + parent::fireConnectionEvent($event); + } + + /** + * Fire the given event if possible. + */ + protected function fireEvent(string $event, array|object $attributes = []): void + { + /** @var \Winter\Storm\Events\Dispatcher|null */ + $eventManager = $this->events; + + if (!isset($eventManager)) { + return; + } + + $eventManager->dispatch($event, $attributes); + } + + /** + * Is Doctrine available? + * + * @return bool + */ + public function isDoctrineAvailable() + { + return class_exists('Doctrine\DBAL\Connection'); + } + + /** + * Indicates whether native alter operations will be used when dropping or renaming columns, even if Doctrine DBAL is installed. + * + * @return bool + */ + public function usingNativeSchemaOperations() + { + return ! $this->isDoctrineAvailable(); + } + + /** + * Get a Doctrine Schema Column instance. + * + * @param string $table + * @param string $column + * @return \Doctrine\DBAL\Schema\Column + */ + public function getDoctrineColumn($table, $column) + { + $schema = $this->getDoctrineSchemaManager(); + + return $schema->listTableDetails($table)->getColumn($column); + } + + /** + * Get the Doctrine DBAL schema manager for the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getDoctrineSchemaManager() + { + $connection = $this->getDoctrineConnection(); + + // Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2... + return $this->getDoctrineDriver()->getSchemaManager( + $connection, + $connection->getDatabasePlatform() + ); + } + + /** + * Get the Doctrine DBAL database connection instance. + * + * @return \Doctrine\DBAL\Connection + */ + public function getDoctrineConnection() + { + if (is_null($this->doctrineConnection)) { + $driver = $this->getDoctrineDriver(); + + $this->doctrineConnection = new DoctrineConnection(array_filter([ + 'pdo' => $this->getPdo(), + 'dbname' => $this->getDatabaseName(), + 'driver' => $driver->getName(), + 'serverVersion' => $this->getConfig('server_version'), + ]), $driver); + + foreach ($this->doctrineTypeMappings as $name => $type) { + $this->doctrineConnection + ->getDatabasePlatform() + ->registerDoctrineTypeMapping($type, $name); + } + } + + return $this->doctrineConnection; + } + + /** + * Register a custom Doctrine mapping type. + * + * @param Type|class-string $class + * @param string $name + * @param string $type + * @return void + * + * @throws \RuntimeException + */ + public function registerDoctrineType(Type|string $class, string $name, string $type): void + { + if (! $this->isDoctrineAvailable()) { + throw new RuntimeException( + 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' + ); + } + + if (! Type::hasType($name)) { + Type::getTypeRegistry() + ->register($name, is_string($class) ? new $class() : $class); + } + + $this->doctrineTypeMappings[$name] = $type; + } +} diff --git a/src/Database/Traits/Revisionable.php b/src/Database/Traits/Revisionable.php index 9dcdc459f..4e2b5763a 100644 --- a/src/Database/Traits/Revisionable.php +++ b/src/Database/Traits/Revisionable.php @@ -157,7 +157,8 @@ protected function revisionableCleanUp() protected function revisionableGetCastType($attribute) { - if (in_array($attribute, $this->getDates())) { + // dates property deprecated in Laravel 10, no longer used in getDates() + if (in_array($attribute, $this->getDates()) || $this->isDateCastable($attribute) || in_array($attribute, $this->dates)) { return 'date'; } diff --git a/src/Flash/FlashBag.php b/src/Flash/FlashBag.php index 61abb1f39..bdcacfb6d 100644 --- a/src/Flash/FlashBag.php +++ b/src/Flash/FlashBag.php @@ -166,15 +166,16 @@ public function store() /** * Removes an object with a specified key or erases the flash data. + * * @param string $key Specifies a key to remove, optional + * @return $this */ public function forget($key = null) { if ($key === null) { $this->newMessages = $this->messages = []; $this->purge(); - } - else { + } else { if (isset($this->messages[$key])) { unset($this->messages[$key]); } @@ -185,6 +186,8 @@ public function forget($key = null) $this->store(); } + + return $this; } /* diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index ab7032994..b61989c4d 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -67,11 +67,12 @@ public function version() /** * Get the path to the public / web directory. * + * @param string $path * @return string */ - public function publicPath() + public function publicPath($path = '') { - return $this->basePath; + return $this->joinPaths($this->basePath, $path); } /** @@ -401,7 +402,7 @@ public function registerConfiguredProviders($isRetry = false) }); if (Config::get('app.loadDiscoveredPackages', false)) { - $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); + $providers->splice(1, 0, $this->make(PackageManifest::class)->providers()); } $filesystem = new Filesystem; diff --git a/src/Foundation/Http/Kernel.php b/src/Foundation/Http/Kernel.php index 4735b70fc..9ca5b7111 100644 --- a/src/Foundation/Http/Kernel.php +++ b/src/Foundation/Http/Kernel.php @@ -32,7 +32,7 @@ class Kernel extends HttpKernel /** * {@inheritDoc} */ - protected $routeMiddleware = [ + protected $middlewareAliases = [ // 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, // 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, diff --git a/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php b/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php index 6a82797bf..76a452b3e 100644 --- a/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php +++ b/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php @@ -23,26 +23,25 @@ public function handle($request, Closure $next) { try { return parent::handle($request, $next); - } catch (\Illuminate\Foundation\Http\Exceptions\MaintenanceModeException $ex) { - // Smoothly handle AJAX requests - if (request()->ajax()) { - return Response::make(Lang::get('system::lang.page.maintenance.help') . "\r\n" . $ex->getMessage(), 503); - } + } catch (\Symfony\Component\HttpKernel\Exception\HttpException $ex) { + if ($ex->getStatusCode() === 503) { + // Smoothly handle AJAX requests + if (request()->ajax()) { + return Response::make(Lang::get('system::lang.page.maintenance.help') . "\r\n" . $ex->getMessage(), 503); + } - // Check if there is a project level view to override the system one - View::addNamespace('base', base_path()); - if (View::exists('base::maintenance')) { - $view = 'base::maintenance'; - } else { - $view = 'system::maintenance'; - } + // Check if there is a project level view to override the system one + View::addNamespace('base', base_path()); + if (View::exists('base::maintenance')) { + $view = 'base::maintenance'; + } else { + $view = 'system::maintenance'; + } - return Response::make(View::make($view, [ - 'message' => $ex->getMessage(), - 'wentDownAt' => $ex->wentDownAt, - 'retryAfter' => $ex->retryAfter, - 'willBeAvailableAt' => $ex->willBeAvailableAt, - ]), 503); + return Response::make(View::make($view, [ + 'message' => $ex->getMessage() + ]), 503); + } } } } diff --git a/src/Halcyon/Builder.php b/src/Halcyon/Builder.php index 2abd7f19a..de7313be3 100644 --- a/src/Halcyon/Builder.php +++ b/src/Halcyon/Builder.php @@ -703,7 +703,6 @@ protected function isCacheBusted($result) */ protected function getCache() { - /** @var \Illuminate\Cache\Repository */ $cache = $this->model->getCacheManager()->driver($this->cacheDriver); return $this->cacheTags ? $cache->tags($this->cacheTags) : $cache; diff --git a/src/Halcyon/MemoryCacheManager.php b/src/Halcyon/MemoryCacheManager.php index ccb5ea9c8..62422cdca 100644 --- a/src/Halcyon/MemoryCacheManager.php +++ b/src/Halcyon/MemoryCacheManager.php @@ -7,9 +7,9 @@ class MemoryCacheManager extends CacheManager { - public function repository(Store $store) + public function repository(Store $store, array $config = []) { - return new MemoryRepository($store); + return new MemoryRepository($store, $config); } public static function isEnabled() diff --git a/src/Support/Facades/DB.php b/src/Support/Facades/DB.php index d2c0bb8bf..15bbc8c51 100644 --- a/src/Support/Facades/DB.php +++ b/src/Support/Facades/DB.php @@ -5,7 +5,7 @@ /** * @method static \Doctrine\DBAL\Driver\PDOConnection getPdo() * @method static \Illuminate\Database\Connection connection(string $name = null) - * @method static \Winter\Storm\Database\QueryBuilder table(string $table, string $as = null) + * @method static \Winter\Storm\Database\QueryBuilder table(\Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|string $table, string|null $as = null) * @method static \Illuminate\Database\Query\Expression raw($value) * @method static array getQueryLog() * @method static array prepareBindings(array $bindings) diff --git a/src/Support/Facades/Flash.php b/src/Support/Facades/Flash.php index 7e1e1b305..b5df487f7 100644 --- a/src/Support/Facades/Flash.php +++ b/src/Support/Facades/Flash.php @@ -12,7 +12,7 @@ * @method static array|\Winter\Storm\Flash\FlashBag info(string $message = null) * @method static \Winter\Storm\Flash\FlashBag add(string $key, string $message) * @method static void store() - * @method static void forget(string $key = null) + * @method static \Winter\Storm\Flash\FlashBag forget(string $key = null) * @method static void purge(); * * @see \Winter\Storm\Flash\FlashBag diff --git a/src/Support/Facades/Mail.php b/src/Support/Facades/Mail.php index 047147133..f9e04c4ba 100755 --- a/src/Support/Facades/Mail.php +++ b/src/Support/Facades/Mail.php @@ -1,5 +1,6 @@ getMockBuilder($class) - ->setMethods(['extensionGetClassLoader']) + ->onlyMethods(['extensionGetClassLoader']) ->disableOriginalConstructor() ->getMock(); diff --git a/src/Support/helpers-array.php b/src/Support/helpers-array.php index e07c42f03..d5af63742 100644 --- a/src/Support/helpers-array.php +++ b/src/Support/helpers-array.php @@ -333,7 +333,7 @@ function array_set(&$array, $key, $value) */ function array_shuffle($array, $seed = null) { - return Arr::shuffle($array, $seed); + return Arr::shuffle($array); } } diff --git a/src/Translation/FileLoader.php b/src/Translation/FileLoader.php index 86e65954d..3dba1cc13 100644 --- a/src/Translation/FileLoader.php +++ b/src/Translation/FileLoader.php @@ -18,26 +18,31 @@ class FileLoader extends FileLoaderBase */ protected function loadNamespaceOverrides(array $lines, $locale, $group, $namespace) { - $namespace = str_replace('.', '/', $namespace); + return collect($this->paths) + ->reduce(function ($output, $path) use ($lines, $locale, $group, $namespace) { + $winterNamespace = str_replace('.', '/', $namespace); - $file = "{$this->path}/{$locale}/{$namespace}/{$group}.php"; + // Look for a Winter-managed namespace + $file = "{$path}/{$locale}/{$winterNamespace}/{$group}.php"; + if ($this->files->exists($file)) { + return array_replace_recursive($lines, $this->files->getRequire($file)); + } - if ($this->files->exists($file)) { - return array_replace_recursive($lines, $this->files->getRequire($file)); - } + // Look for a Winter-managed namespace with a Winter-formatted locale (xx-xx instead of xx_XX) + $dashLocale = str_replace('_', '-', strtolower($locale)); + $dashFile = "{$path}/{$dashLocale}/{$winterNamespace}/{$group}.php"; + if ($dashFile !== $file && $this->files->exists($dashFile)) { + return array_replace_recursive($lines, $this->files->getRequire($dashFile)); + } - // Try "xx-xx" format - $locale = str_replace('_', '-', strtolower($locale)); + // Look for a vendor-managed namespace + $file = "{$path}/vendor/{$namespace}/{$locale}/{$group}.php"; + if ($this->files->exists($file)) { + return array_replace_recursive($lines, $this->files->getRequire($file)); + } - if ("{$this->path}/{$locale}/{$namespace}/{$group}.php" !== $file) { - $file = "{$this->path}/{$locale}/{$namespace}/{$group}.php"; - - if ($this->files->exists($file)) { - return array_replace_recursive($lines, $this->files->getRequire($file)); - } - } - - return $lines; + return $lines; + }, []); } /** @@ -46,26 +51,28 @@ protected function loadNamespaceOverrides(array $lines, $locale, $group, $namesp * This is an override from the base Laravel functionality that allows "xx-xx" locale format * files as well as "xx_XX" locale format files. The "xx_XX" format is considered authorative. * - * @param string $path + * @param array $paths * @param string $locale * @param string $group * @return array */ - protected function loadPath($path, $locale, $group) + protected function loadPaths(array $paths, $locale, $group) { - if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) { - return $this->files->getRequire($full); - } - - // Try "xx-xx" format - $locale = str_replace('_', '-', strtolower($locale)); + return collect($paths) + ->reduce(function ($output, $path) use ($locale, $group) { + $file = "{$path}/{$locale}/{$group}.php"; + if ($this->files->exists($file)) { + return array_replace_recursive($output, $this->files->getRequire($file)); + } - if ("{$path}/{$locale}/{$group}.php" !== $full) { - if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) { - return $this->files->getRequire($full); - } - } + // Try "xx-xx" format + $dashLocale = str_replace('_', '-', strtolower($locale)); + $dashFile = "{$path}/{$dashLocale}/{$group}.php"; + if ($dashFile !== $file && $this->files->exists($dashFile)) { + return $this->files->getRequire($dashFile); + } - return []; + return $output; + }, []); } } diff --git a/src/Translation/Translator.php b/src/Translation/Translator.php index aef6454f1..a9609f354 100644 --- a/src/Translation/Translator.php +++ b/src/Translation/Translator.php @@ -128,9 +128,9 @@ protected function getValidationSpecific($key, $replace, $locale) /** * @inheritDoc */ - protected function localeForChoice($locale) + protected function localeForChoice($key, $locale) { - $locale = parent::localeForChoice($locale); + $locale = parent::localeForChoice($key, $locale); if (str_contains($locale, '-')) { $localeParts = explode('-', $locale, 2); diff --git a/tests/Database/QueryBuilderTest.php b/tests/Database/QueryBuilderTest.php index 0d2387ce9..4018e5846 100644 --- a/tests/Database/QueryBuilderTest.php +++ b/tests/Database/QueryBuilderTest.php @@ -105,9 +105,10 @@ protected function getConnection($connection = null, $table = null) ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() - ->setMethods([ + ->onlyMethods([ 'table', 'raw', + 'scalar', 'selectOne', 'select', 'cursor', @@ -125,6 +126,8 @@ protected function getConnection($connection = null, $table = null) 'transactionLevel', 'pretend', 'getDatabaseName', + ]) + ->addMethods([ 'getConfig', ]) ->getMock(); diff --git a/tests/Database/Schema/Grammars/MySqlSchemaGrammarTest.php b/tests/Database/Schema/Grammars/MySqlSchemaGrammarTest.php new file mode 100644 index 000000000..38ed92813 --- /dev/null +++ b/tests/Database/Schema/Grammars/MySqlSchemaGrammarTest.php @@ -0,0 +1,64 @@ +grammar = new MySqlGrammar; + } + + public function testNoInitialModifiersAddNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name'); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table `users` add `name` varchar(255) not null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->nullable()->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame("alter table `users` modify `name` varchar(255) null", $statements[0]); + } + + public function testNullableInitialModifierAddDefault() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table `users` add `name` varchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame("alter table `users` modify `name` varchar(255) null default 'admin'", $statements[0]); + } + + public function testNullableInitialModifierAddDefaultNotNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table `users` add `name` varchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->nullable(false)->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame("alter table `users` modify `name` varchar(255) not null default 'admin'", $statements[0]); + } +} diff --git a/tests/Database/Schema/Grammars/PostgresSchemaGrammarTest.php b/tests/Database/Schema/Grammars/PostgresSchemaGrammarTest.php new file mode 100644 index 000000000..dbf137158 --- /dev/null +++ b/tests/Database/Schema/Grammars/PostgresSchemaGrammarTest.php @@ -0,0 +1,72 @@ +grammar = new PostgresGrammar; + } + + public function testNoInitialModifiersAddNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name'); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar(255) not null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->nullable()->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $parts = explode(', ', $statements[0]); + $this->assertSame('alter table "users" alter column "name" type varchar(255)', $parts[0]); + $this->assertSame('alter column "name" drop not null', $parts[1]); + $this->assertSame("alter column \"name\" drop default", $parts[2]); + } + public function testNullableInitialModifierAddDefault() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $parts = explode(', ', $statements[0]); + $this->assertSame('alter table "users" alter column "name" type varchar(255)', $parts[0]); + $this->assertSame('alter column "name" null', $parts[1]); + $this->assertSame("alter column \"name\" set default 'admin'", $parts[2]); + } + + public function testNullableInitialModifierAddDefaultNotNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->nullable(false)->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $parts = explode(', ', $statements[0]); + $this->assertSame('alter table "users" alter column "name" type varchar(255)', $parts[0]); + $this->assertSame('alter column "name" set not null', $parts[1]); + $this->assertSame("alter column \"name\" set default 'admin'", $parts[2]); + } +} diff --git a/tests/Database/Schema/Grammars/SQLiteSchemaGrammarTest.php b/tests/Database/Schema/Grammars/SQLiteSchemaGrammarTest.php new file mode 100644 index 000000000..8d6b8c979 --- /dev/null +++ b/tests/Database/Schema/Grammars/SQLiteSchemaGrammarTest.php @@ -0,0 +1,64 @@ +grammar = new SQLiteGrammar; + } + + public function testNoInitialModifiersAddNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name'); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar not null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->nullable()->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertStringContainsString('"name" varchar', $statements[0]); + } + + public function testNullableInitialModifierAddDefault() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertStringContainsString("varchar default 'admin'", $statements[0]); + } + + public function testNullableInitialModifierAddDefaultNotNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add column "name" varchar', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->nullable(false)->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertStringContainsString("\"name\" varchar not null default 'admin'", $statements[0]); + } +} diff --git a/tests/Database/Schema/Grammars/SqlServerSchemaGrammarTest.php b/tests/Database/Schema/Grammars/SqlServerSchemaGrammarTest.php new file mode 100644 index 000000000..60a42873a --- /dev/null +++ b/tests/Database/Schema/Grammars/SqlServerSchemaGrammarTest.php @@ -0,0 +1,66 @@ +grammar = new SqlServerGrammar; + } + + public function testNoInitialModifiersAddNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name'); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add "name" nvarchar(255) not null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->nullable()->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame('alter table "users" alter column "name" nvarchar(255) null', $statements[1]); + } + + public function testNullableInitialModifierAddDefault() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add "name" nvarchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame('alter table "users" alter column "name" nvarchar(255) null', $statements[1]); + $this->assertSame('alter table "users" add default \'admin\' for "name"', $statements[2]); + } + + public function testNullableInitialModifierAddDefaultNotNullable() + { + $initialBlueprint = new Blueprint('users'); + $initialBlueprint->string('name')->nullable(); + $this->setupConnection($initialBlueprint); + + $statements = $this->runBlueprint($initialBlueprint); + $this->assertSame('alter table "users" add "name" nvarchar(255) null', $statements[0]); + + $changedBlueprint = new Blueprint('users'); + $changedBlueprint->string('name')->default('admin')->nullable(false)->change(); + + $statements = $this->runBlueprint($changedBlueprint); + $this->assertSame('alter table "users" alter column "name" nvarchar(255) not null', $statements[1]); + $this->assertSame('alter table "users" add default \'admin\' for "name"', $statements[2]); + } +} diff --git a/tests/Database/UpdaterTest.php b/tests/Database/UpdaterTest.php index ef60d4573..eb94125e1 100644 --- a/tests/Database/UpdaterTest.php +++ b/tests/Database/UpdaterTest.php @@ -4,7 +4,7 @@ class UpdaterTest extends TestCase { - protected Updater $updater; + protected ?Updater $updater = null; public function setUp(): void { diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php index b9239e2ba..3a9ff6bcf 100644 --- a/tests/Filesystem/FilesystemTest.php +++ b/tests/Filesystem/FilesystemTest.php @@ -22,7 +22,7 @@ public function testIsAbsolutePath($path, $expectedResult) $this->assertEquals($expectedResult, $result); } - public function providePathsForIsAbsolutePath() + public static function providePathsForIsAbsolutePath() { return [ ['/var/lib', true], diff --git a/tests/Foundation/ApplicationTest.php b/tests/Foundation/ApplicationTest.php index f138de05a..7f5127e6b 100644 --- a/tests/Foundation/ApplicationTest.php +++ b/tests/Foundation/ApplicationTest.php @@ -5,7 +5,7 @@ class ApplicationTest extends TestCase { - protected string $basePath; + protected string $basePath = ''; protected function setUp(): void { diff --git a/tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php b/tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php index 758a4b288..d1bb6ff7a 100644 --- a/tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php +++ b/tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php @@ -182,7 +182,7 @@ protected function createUrlGenerator($trustedHosts = [], $headers = [], $server { $middleware = $this->getMockBuilder(CheckForTrustedHost::class) ->disableOriginalConstructor() - ->setMethods(['hosts', 'shouldSpecifyTrustedHosts']) + ->onlyMethods(['hosts', 'shouldSpecifyTrustedHosts']) ->getMock(); $middleware->expects($this->any()) diff --git a/tests/Foundation/Http/Middleware/CheckForTrustedProxiesTest.php b/tests/Foundation/Http/Middleware/CheckForTrustedProxiesTest.php index cbde74f1f..8f0cb5495 100644 --- a/tests/Foundation/Http/Middleware/CheckForTrustedProxiesTest.php +++ b/tests/Foundation/Http/Middleware/CheckForTrustedProxiesTest.php @@ -432,7 +432,7 @@ protected function createTrustedProxyMock($trustedProxies = [], $trustedHeaders { $middleware = $this->getMockBuilder(CheckForTrustedProxies::class) ->disableOriginalConstructor() - ->setMethods(['proxies', 'headers']) + ->onlyMethods(['proxies', 'headers']) ->getMock(); $middleware->expects($this->any()) diff --git a/tests/GrammarTestCase.php b/tests/GrammarTestCase.php new file mode 100644 index 000000000..b95ffd25f --- /dev/null +++ b/tests/GrammarTestCase.php @@ -0,0 +1,43 @@ +shouldReceive('getServerVersion')->andReturn('3.35'); + $connection->shouldReceive('getSchemaBuilder')->andReturn($this->getSchemaBuilder($blueprint)); + $connection->shouldReceive('scalar')->andReturn(''); + $this->connection = $connection; + } + + protected function getSchemaBuilder(Blueprint $blueprint) + { + $schemaBuilder = m::mock(Builder::class); + $schemaBuilder->shouldReceive('getColumns')->andReturn($blueprint->getColumns()); + $schemaBuilder->shouldReceive('getForeignKeys')->andReturn([]); + $schemaBuilder->shouldReceive('getIndexes')->andReturn([]); + + return $schemaBuilder; + } + + protected function runBlueprint(Blueprint $blueprint) + { + return $blueprint->toSql($this->connection, $this->grammar); + } +} diff --git a/tests/Halcyon/HalcyonModelTest.php b/tests/Halcyon/HalcyonModelTest.php index 2bee0deec..6813750c8 100644 --- a/tests/Halcyon/HalcyonModelTest.php +++ b/tests/Halcyon/HalcyonModelTest.php @@ -377,13 +377,16 @@ protected function setDatasourceResolver() protected function setValidatorOnModel() { - $translator = $this->getMockBuilder('Illuminate\Contracts\Translation\Translator')->setMethods([ + $translator = $this->getMockBuilder('Illuminate\Contracts\Translation\Translator') + ->onlyMethods([ 'get', 'choice', - 'trans', - 'transChoice', 'setLocale', 'getLocale' + ]) + ->addMethods([ + 'trans', + 'transChoice', ])->getMock(); $translator->expects($this->any())->method('get')->will($this->returnArgument(0)); diff --git a/tests/Html/BlockBuilderTest.php b/tests/Html/BlockBuilderTest.php index a2e1fc6ab..c39f5df92 100644 --- a/tests/Html/BlockBuilderTest.php +++ b/tests/Html/BlockBuilderTest.php @@ -4,7 +4,7 @@ class BlockBuilderTest extends TestCase { - protected BlockBuilder $Block; + protected ?BlockBuilder $Block = null; public function setUp(): void { diff --git a/tests/Parse/MarkdownTest.php b/tests/Parse/MarkdownTest.php index 82fb24416..9f87d0de0 100644 --- a/tests/Parse/MarkdownTest.php +++ b/tests/Parse/MarkdownTest.php @@ -7,7 +7,7 @@ class MarkdownTest extends TestCase /** * Test fixtures that should be skipped by the data provider. */ - protected array $specialTests = [ + protected static array $specialTests = [ 'front_matter', ]; @@ -20,15 +20,15 @@ class MarkdownTest extends TestCase */ public function testParse($name, $markdown, $html) { - $this->assertEquals($html, $this->removeLineEndings(Markdown::parse($markdown)), 'Markdown test case "' . $name . '" failed'); + $this->assertEquals($html, static::removeLineEndings(Markdown::parse($markdown)), 'Markdown test case "' . $name . '" failed'); } public function testParseSafe() { $markdown = file_get_contents(dirname(__DIR__) . '/fixtures/markdown/code_block.md'); - $html = $this->removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/code_block_disabled.html')); + $html = static::removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/code_block_disabled.html')); - $this->assertEquals($html, $this->removeLineEndings(Markdown::parseSafe($markdown))); + $this->assertEquals($html, static::removeLineEndings(Markdown::parseSafe($markdown))); } public function testDisableAndEnableMethods() @@ -36,16 +36,16 @@ public function testDisableAndEnableMethods() $parser = Markdown::disableAutolinking()->disableTables(); $markdown = file_get_contents(dirname(__DIR__) . '/fixtures/markdown/table_inline_markdown.md'); - $html = $this->removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/table_inline_markdown_disabled.html')); + $html = static::removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/table_inline_markdown_disabled.html')); - $this->assertEquals($html, $this->removeLineEndings($parser->parse($markdown))); + $this->assertEquals($html, static::removeLineEndings($parser->parse($markdown))); // Re-enable tables and autolinking $parser->enableAutolinking()->enableTables(); - $html = $this->removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/table_inline_markdown.html')); + $html = static::removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/table_inline_markdown.html')); - $this->assertEquals($html, $this->removeLineEndings($parser->parse($markdown))); + $this->assertEquals($html, static::removeLineEndings($parser->parse($markdown))); } public function testFrontMatter() @@ -53,9 +53,9 @@ public function testFrontMatter() $parser = Markdown::enableFrontMatter(); $markdown = file_get_contents(dirname(__DIR__) . '/fixtures/markdown/front_matter.md'); - $html = $this->removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/front_matter.html')); + $html = static::removeLineEndings(file_get_contents(dirname(__DIR__) . '/fixtures/markdown/front_matter.html')); - $this->assertEquals($html, $this->removeLineEndings($parser->parse($markdown))); + $this->assertEquals($html, static::removeLineEndings($parser->parse($markdown))); $this->assertEquals([ 'title' => 'My document', 'group' => 'Documents', @@ -75,7 +75,7 @@ public function testFrontMatter() * * @return array */ - public function markdownData() + public static function markdownData() { $dirName = dirname(__DIR__) . '/fixtures/markdown'; $dir = new DirectoryIterator($dirName); @@ -87,19 +87,19 @@ public function markdownData() if ($name === 'README') { continue; } - if (in_array($name, $this->specialTests)) { + if (in_array($name, static::$specialTests)) { continue; } $markdown = file_get_contents($dirName . DIRECTORY_SEPARATOR . $name . '.md'); - $html = $this->removeLineEndings(file_get_contents($dirName . DIRECTORY_SEPARATOR . $name . '.html')); + $html = static::removeLineEndings(file_get_contents($dirName . DIRECTORY_SEPARATOR . $name . '.html')); yield [$name, $markdown, $html]; } } } - protected function removeLineEndings(string $text) + protected static function removeLineEndings(string $text) { return str_replace(["\r\n", "\r", "\n"], '', $text); } diff --git a/tests/Router/RoutingUrlGeneratorTest.php b/tests/Router/RoutingUrlGeneratorTest.php index e3d55892d..b28f926c6 100644 --- a/tests/Router/RoutingUrlGeneratorTest.php +++ b/tests/Router/RoutingUrlGeneratorTest.php @@ -526,7 +526,7 @@ public function testRoutesWithDomainsThroughProxy() $this->assertSame('http://sub.foo.com/foo/bar', $url->route('foo')); } - public function providerRouteParameters() + public static function providerRouteParameters() { return [ [['test' => 123]], @@ -555,7 +555,7 @@ public function testUrlGenerationForControllersRequiresPassingOfRequiredParamete $this->assertSame('http://www.foo.com:8080/foo?test=123', $url->route('foo', $parameters)); } - public function provideParametersAndExpectedMeaningfulExceptionMessages() + public static function provideParametersAndExpectedMeaningfulExceptionMessages() { return [ 'Missing parameters "one", "two" and "three"' => [ diff --git a/tests/Support/EventFakeTest.php b/tests/Support/EventFakeTest.php index 5e74b6805..403551fdb 100644 --- a/tests/Support/EventFakeTest.php +++ b/tests/Support/EventFakeTest.php @@ -5,7 +5,7 @@ class EventFakeTest extends TestCase { - protected EventFake $faker; + protected ?EventFake $faker = null; public function setUp(): void { diff --git a/tests/Support/MailFakeTest.php b/tests/Support/MailFakeTest.php index 144a1452c..b777d2e83 100644 --- a/tests/Support/MailFakeTest.php +++ b/tests/Support/MailFakeTest.php @@ -1,17 +1,24 @@ andReturn('en/US'); - Mail::swap(new MailFake()); + $this->manager = m::mock(MailManager::class); + Mail::swap(new MailFake($this->manager)); $this->recipient = 'fake@localhost'; $this->subject = 'MailFake test'; diff --git a/tests/Translation/FileLoaderTest.php b/tests/Translation/FileLoaderTest.php index 6b9fa3491..0f1848db5 100644 --- a/tests/Translation/FileLoaderTest.php +++ b/tests/Translation/FileLoaderTest.php @@ -31,6 +31,7 @@ public function testLoadMethodWithNamespacesProperlyCallsLoader() $loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__); $files->shouldReceive('exists')->once()->with('bar/en/foo.php')->andReturn(true); $files->shouldReceive('exists')->once()->with(__DIR__.'/en/namespace/foo.php')->andReturn(false); + $files->shouldReceive('exists')->once()->with(__DIR__.'/vendor/namespace/en/foo.php')->andReturn(false); $files->shouldReceive('getRequire')->once()->with('bar/en/foo.php')->andReturn(['foo' => 'bar']); $loader->addNamespace('namespace', 'bar'); diff --git a/tests/Translation/TranslatorTest.php b/tests/Translation/TranslatorTest.php index 29b6f8cd5..e26745f60 100644 --- a/tests/Translation/TranslatorTest.php +++ b/tests/Translation/TranslatorTest.php @@ -221,8 +221,9 @@ public function testSetMethodCanOverwriteAnEntireGroupForALocale() public function testChoiceMethodProperlyLoadsAndRetrievesItem() { - $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); + $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get', 'localeForChoice'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); $t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->willReturn('line'); + $t->expects($this->once())->method('localeForChoice')->with($this->equalTo('foo'), $this->equalTo(null))->willReturn('en'); $t->setSelector($selector = m::mock(MessageSelector::class)); $selector->shouldReceive('choose')->once()->with('line', 10, 'en')->andReturn('choiced'); @@ -231,8 +232,9 @@ public function testChoiceMethodProperlyLoadsAndRetrievesItem() public function testChoiceMethodProperlyCountsCollectionsAndLoadsAndRetrievesItem() { - $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); + $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get', 'localeForChoice'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); $t->expects($this->exactly(2))->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->willReturn('line'); + $t->expects($this->exactly(2))->method('localeForChoice')->with($this->equalTo('foo'), $this->equalTo(null))->willReturn('en'); $t->setSelector($selector = m::mock(MessageSelector::class)); $selector->shouldReceive('choose')->twice()->with('line', 3, 'en')->andReturn('choiced'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a89265b24..6a1b420f6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,3 +2,4 @@ class_alias('Winter\Storm\Tests\TestCase', 'TestCase'); class_alias('Winter\Storm\Tests\DbTestCase', 'DbTestCase'); +class_alias('Winter\Storm\Tests\GrammarTestCase', 'GrammarTestCase'); diff --git a/tests/fixtures/events/EventTest.php b/tests/fixtures/events/EventTest.php index 91b016959..07de92141 100644 --- a/tests/fixtures/events/EventTest.php +++ b/tests/fixtures/events/EventTest.php @@ -1,5 +1,14 @@ assertTrue(true, true); + } }