diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml index 2f945026..923c1a78 100644 --- a/.github/workflows/code-analysis.yaml +++ b/.github/workflows/code-analysis.yaml @@ -26,7 +26,7 @@ jobs: - name: Install PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' extensions: ${{ env.extensions }} coverage: none diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index 840f47e5..7e3db30d 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -23,7 +23,7 @@ jobs: - name: Install PHP and PHP Code Sniffer uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' tools: phpcs coverage: none diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c9ac21cb..47287852 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: max-parallel: 4 matrix: operatingSystem: [ubuntu-latest, windows-latest] - phpVersion: ['8.2', '8.3', '8.4'] + phpVersion: ['8.3', '8.4'] fail-fast: false runs-on: ${{ matrix.operatingSystem }} name: ${{ matrix.operatingSystem }} / PHP ${{ matrix.phpVersion }} diff --git a/composer.json b/composer.json index 77abb359..e1c0a65b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": "^8.2", + "php": "^8.3", "ext-ctype": "*", "ext-curl": "*", "ext-dom": "*", @@ -38,33 +38,34 @@ "assetic/framework": "^3.1", "doctrine/dbal": "^3.0", "enshrined/svg-sanitize": "~0.16", - "laravel/framework": "^12.30.1", - "laravel/tinker": "^2.8.1", + "laravel/framework": "^13.0", + "laravel/tinker": "^3.0", "league/csv": "~9.1", - "nesbot/carbon": "^3.0", + "nesbot/carbon": "^3.8.4", "scssphp/scssphp": "~1.0", - "symfony/console": "^7.2", - "symfony/process": "^7.1", - "symfony/yaml": "^6.4|^7.0", + "symfony/console": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", "twig/twig": "^3.14", "wikimedia/less.php": "^5.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.5.50", "squizlabs/php_codesniffer": "^3.2", "php-parallel-lint/php-parallel-lint": "^1.0", "meyfa/phpunit-assert-gd": "^4.2", "mockery/mockery": "^1.6|^2.0", "dms/phpunit-arraysubset-asserts": "dev-add-phpunit-11-support", - "orchestra/testbench": "^10.0", + "orchestra/testbench": "^11.0", "larastan/larastan": "^3.6.0" }, "repositories": [ { "type": "vcs", - "url": "https://github.com/pieterocp/phpunit-arraysubset-asserts" + "url": "https://github.com/pieterocp/phpunit-arraysubset-asserts", + "no-api": true } ], "suggest": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9f2e0407..93ad2d0d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -30,24 +30,6 @@ parameters: count: 1 path: src/Auth/Models/User.php - - - message: '#^Call to function method_exists\(\) with \$this\(Winter\\Storm\\Auth\\Models\\User\) and ''getHashableAttribut…'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: src/Auth/Models/User.php - - - - message: '#^Call to function property_exists\(\) with \$this\(Winter\\Storm\\Auth\\Models\\User\) and ''attributeNames'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: src/Auth/Models/User.php - - - - message: '#^Call to function property_exists\(\) with \$this\(Winter\\Storm\\Auth\\Models\\User\) and ''customMessages'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: src/Auth/Models/User.php - - message: '#^PHPDoc type array\ of property Winter\\Storm\\Auth\\Models\\User\:\:\$hidden is not covariant with PHPDoc type list\ of overridden property Illuminate\\Database\\Eloquent\\Model\:\:\$hidden\.$#' identifier: property.phpDocType @@ -642,12 +624,6 @@ parameters: count: 1 path: src/Database/Relations/HasOneThrough.php - - - message: '#^Instanceof between \*NEVER\* and Winter\\Storm\\Database\\Relations\\HasManyThrough will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 4 - path: src/Database/Relations/HasOneThrough.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\Relation\:\:withDefault\(\)\.$#' identifier: method.notFound diff --git a/src/Auth/Models/Preferences.php b/src/Auth/Models/Preferences.php index 79317d40..532173df 100644 --- a/src/Auth/Models/Preferences.php +++ b/src/Auth/Models/Preferences.php @@ -13,8 +13,8 @@ * @property string|null $item Represents the item name of the preference. * @property int|null $user_id Represents the user ID that this preference belongs to. * - * @method static \Winter\Storm\Database\QueryBuilder applyKeyAndUser($key, $user = null) Scope to find a setting record - * for the specified module (or plugin) name, setting name and user. + * @method static \Illuminate\Database\Eloquent\Builder applyKeyAndUser($key, $user = null) Scope to find a + * setting record for the specified module (or plugin) name, setting name and user. */ class Preferences extends Model { @@ -167,10 +167,10 @@ public static function findRecord($key, $user = null) /** * Scope to find a setting record for the specified module (or plugin) name, setting name and user. - * @param \Winter\Storm\Database\QueryBuilder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @param string $key Specifies the setting key value, for example 'backend:items.perpage' * @param mixed $user An optional user object. - * @return mixed Returns the found record or null. + * @return \Illuminate\Database\Eloquent\Builder Returns the scoped query builder. */ public function scopeApplyKeyAndUser($query, $key, $user = null) { diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 2373a01c..7f745864 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -913,6 +913,11 @@ protected function relationMethodDefinition(string $name, bool $ignoreResolved = /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) { @@ -926,6 +931,11 @@ protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localK /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { @@ -939,6 +949,11 @@ protected function newHasOneThrough(Builder $query, Model $farParent, Model $thr /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey) { @@ -968,6 +983,11 @@ public function guessBelongsToManyRelation() /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) { @@ -981,6 +1001,11 @@ protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $owne /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) { @@ -994,6 +1019,11 @@ protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $owner /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) { @@ -1007,6 +1037,11 @@ protected function newHasMany(Builder $query, Model $parent, $foreignKey, $local /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { @@ -1020,6 +1055,11 @@ protected function newHasManyThrough(Builder $query, Model $farParent, Model $th /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey) { @@ -1033,6 +1073,11 @@ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $loca /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newBelongsToMany( Builder $query, @@ -1054,6 +1099,11 @@ protected function newBelongsToMany( /** * {@inheritDoc} + * + * Storm's relation classes are non-generic and their constructors accept a + * `Builder`, so we override the inherited `Builder` param type here. + * + * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */ protected function newMorphToMany( Builder $query, diff --git a/src/Database/Traits/ArraySource.php b/src/Database/Traits/ArraySource.php index 1c9e09fe..adc39acd 100644 --- a/src/Database/Traits/ArraySource.php +++ b/src/Database/Traits/ArraySource.php @@ -39,7 +39,20 @@ public static function bootArraySource(): void if (!in_array('sqlite', \PDO::getAvailableDrivers())) { throw new ApplicationException('You must enable the SQLite PDO driver to use the ArraySource trait'); } + } + /** + * Boots the temporary SQLite datasource for this model. + * + * This is deferred out of the trait's boot method (`bootArraySource`) because Laravel 13 no + * longer permits a model to be instantiated while it is still booting -- doing so throws a + * LogicException. Building the datasource requires an instance to read the model's (overridable) + * array source configuration, as well as model inserts (which themselves instantiate the model), + * so the work is performed lazily the first time the connection is resolved, by which point the + * model has always finished booting. + */ + protected static function arraySourceBootConnection(): void + { $instance = new static; static::arraySourceSetDbConnection( @@ -79,6 +92,10 @@ public function getRecords() */ public static function resolveConnection($connection = null) { + if (!isset(static::$arraySourceConnection)) { + static::arraySourceBootConnection(); + } + return static::$arraySourceConnection; } diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index aba5b55a..9655ab4a 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -468,7 +468,7 @@ public function registerCoreContainerAliases() 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], - 'log' => [\Illuminate\Log\Logger::class, \Psr\Log\LoggerInterface::class], + 'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class], 'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], 'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class], diff --git a/src/Foundation/Console/DownCommand.php b/src/Foundation/Console/DownCommand.php index 226b5462..32445db1 100644 --- a/src/Foundation/Console/DownCommand.php +++ b/src/Foundation/Console/DownCommand.php @@ -20,8 +20,8 @@ protected function getDownFilePayload() 'redirect' => $this->redirectPath(), 'retry' => $this->getRetryTime(), 'refresh' => $this->option('refresh'), - 'secret' => $this->option('secret'), - 'status' => (int) $this->option('status', 503), + 'secret' => $this->getSecret(), + 'status' => (int) $this->option('status'), 'template' => $this->prerenderView(), ]; } diff --git a/tests/Scheduling/ScheduleListCommandTest.php b/tests/Scheduling/ScheduleListCommandTest.php index 7e5e3b04..02f67883 100644 --- a/tests/Scheduling/ScheduleListCommandTest.php +++ b/tests/Scheduling/ScheduleListCommandTest.php @@ -54,7 +54,7 @@ public function testDisplaySchedule() ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooJob Next Due: 1 minute from now') ->expectsOutput(' 0 9,17 * * * php artisan inspire ......... Next Due: 9 hours from now') ->expectsOutput(' 0 10 * * * php artisan inspire ........ Next Due: 10 hours from now') - ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall::fooFunction Next Due: 1 minute from now') ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now'); } @@ -78,7 +78,7 @@ public function testDisplayScheduleWithSort() ->assertSuccessful() ->expectsOutput(' * * * * * php artisan foobar a='.ProcessUtils::escapeArgument('b').' ... Next Due: 1 minute from now') ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooJob Next Due: 1 minute from now') - ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall::fooFunction Next Due: 1 minute from now') ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now') ->expectsOutput(' 0 9,17 * * * php artisan inspire ......... Next Due: 9 hours from now')