diff --git a/.github/workflows/REUSABLE_backend.yml b/.github/workflows/REUSABLE_backend.yml index 67203d3e3..e368261ff 100644 --- a/.github/workflows/REUSABLE_backend.yml +++ b/.github/workflows/REUSABLE_backend.yml @@ -94,24 +94,10 @@ jobs: prefix: flarum_ prefixStr: (prefix) - # @TODO: remove in 2.0 - # Include testing PHP 8.2 with deprecation warnings disabled. - - php: 8.2 - php_ini_values: error_reporting=E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED - # To reduce number of actions, we exclude some PHP versions from running with some DB versions. exclude: - php: ${{ fromJSON(inputs.php_versions)[1] }} service: 'mysql:8.0.30' - - php: ${{ fromJSON(inputs.php_versions)[2] }} - service: 'mysql:8.0.30' - - php: ${{ fromJSON(inputs.php_versions)[3] }} - service: 'mysql:8.0.30' - - # @TODO: remove in 2.0 - # Exclude testing PHP 8.2 with deprecation warnings enabled. - - php: 8.2 - php_ini_values: error_reporting=E_ALL services: mysql: @@ -137,14 +123,10 @@ jobs: tools: phpunit, composer:v2 ini-values: ${{ matrix.php_ini_values }} - # The authentication alter is necessary because newer mysql versions use the `caching_sha2_password` driver, - # which isn't supported prior to PHP7.4 - # When we drop support for PHP7.3, we should remove this from the setup. - name: Create MySQL Database run: | sudo systemctl start mysql mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306 - mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" --port 13306 - name: Install Composer dependencies run: composer install @@ -180,6 +162,12 @@ jobs: matrix: php: ${{ fromJSON(inputs.php_versions) }} + services: + mysql: + image: mysql:8.0.30 + ports: + - 33306:3306 + name: 'PHPStan PHP ${{ matrix.php }}' if: >- @@ -202,5 +190,15 @@ jobs: run: composer install working-directory: ${{ inputs.backend_directory }} + - name: Create MySQL Database + run: | + sudo systemctl start mysql + mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 33306 + - name: Run PHPStan run: composer analyse:phpstan + env: + DB_PORT: 33306 + DB_PASSWORD: root + COMPOSER_PROCESS_TIMEOUT: 600 + FLARUM_TEST_TMP_DIR_LOCAL: ./tmp diff --git a/composer.json b/composer.json index d6163bfb5..d3b980e77 100644 --- a/composer.json +++ b/composer.json @@ -156,6 +156,8 @@ "symfony/console": "^6.3", "symfony/event-dispatcher": "^6.3", "symfony/http-client": "^6.3", + "symfony/mailer": "^6.3", + "symfony/mailgun-mailer": "^6.3", "symfony/mime": "^6.3", "symfony/polyfill-intl-messageformatter": "^1.27", "symfony/postmark-mailer": "^6.3", @@ -164,7 +166,7 @@ "wikimedia/less.php": "^3.0" }, "require-dev": { - "mockery/mockery": "^1.4", + "mockery/mockery": "^1.5", "phpunit/phpunit": "^9.0", "phpstan/phpstan": "^1.10.0", "nunomaduro/larastan": "^2.6", diff --git a/extensions/mentions/src/ConfigureMentions.php b/extensions/mentions/src/ConfigureMentions.php index 2f7e076e8..de8ba797d 100644 --- a/extensions/mentions/src/ConfigureMentions.php +++ b/extensions/mentions/src/ConfigureMentions.php @@ -12,6 +12,7 @@ namespace Flarum\Mentions; use Flarum\Extension\ExtensionManager; use Flarum\Group\Group; use Flarum\Http\UrlGenerator; +use Flarum\Post\Post; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Tags\Tag; use Flarum\User\User; @@ -80,10 +81,10 @@ class ConfigureMentions /** * @param FormatterTag $tag - * @param array $mentions + * @param array{users: Collection} $mentions * @return bool|void */ - public static function addUserId($tag, array $mentions) + public static function addUserId(FormatterTag $tag, array $mentions) { $allow_username_format = (bool) resolve(SettingsRepositoryInterface::class)->get('flarum-mentions.allow_username_format'); @@ -139,10 +140,10 @@ class ConfigureMentions /** * @param FormatterTag $tag - * @param array $mentions + * @param array{posts: Collection} $mentions * @return bool|void */ - public static function addPostId($tag, array $mentions) + public static function addPostId(FormatterTag $tag, array $mentions) { $post = $mentions['posts']->where('id', $tag->getAttribute('id'))->first(); @@ -212,7 +213,7 @@ class ConfigureMentions /** * @param FormatterTag $tag * @param User $actor - * @param array $mentions + * @param array{groups: Collection} $mentions * @return bool|void */ public static function addGroupId(FormatterTag $tag, User $actor, array $mentions) @@ -228,7 +229,7 @@ class ConfigureMentions $group = $mentions['groups']->where('id', $id)->first(); if ($group) { - $tag->setAttribute('id', $group->id); + $tag->setAttribute('id', (string) $group->id); $tag->setAttribute('groupname', $group->name_plural); return true; @@ -301,7 +302,7 @@ class ConfigureMentions /** * @param FormatterTag $tag - * @param array $mentions + * @param array{tags: Collection} $mentions * @return true|void */ public static function addTagId(FormatterTag $tag, array $mentions) diff --git a/extensions/package-manager/src/Composer/ComposerAdapter.php b/extensions/package-manager/src/Composer/ComposerAdapter.php index 36f1bdc86..680a8e672 100644 --- a/extensions/package-manager/src/Composer/ComposerAdapter.php +++ b/extensions/package-manager/src/Composer/ComposerAdapter.php @@ -42,7 +42,6 @@ class ComposerAdapter $exitCode = $this->application->run($input, $this->output); chdir($currDir); - // @phpstan-ignore-next-line $command = $input->__toString(); $output = $this->output->fetch(); diff --git a/extensions/pusher/composer.json b/extensions/pusher/composer.json index 2cee7b1bc..61e97bda0 100644 --- a/extensions/pusher/composer.json +++ b/extensions/pusher/composer.json @@ -20,7 +20,7 @@ ], "require": { "flarum/core": "^2.0", - "pusher/pusher-php-server": "^2.2" + "pusher/pusher-php-server": "^7.2" }, "require-dev": { "flarum/tags": "^1.0" diff --git a/extensions/pusher/src/Api/Controller/AuthController.php b/extensions/pusher/src/Api/Controller/AuthController.php index e0c8b6bfd..ce1432a50 100644 --- a/extensions/pusher/src/Api/Controller/AuthController.php +++ b/extensions/pusher/src/Api/Controller/AuthController.php @@ -17,7 +17,7 @@ use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Pusher; +use Pusher\Pusher; class AuthController implements RequestHandlerInterface { @@ -36,7 +36,6 @@ class AuthController implements RequestHandlerInterface $this->settings->get('flarum-pusher.app_key'), $this->settings->get('flarum-pusher.app_secret'), $this->settings->get('flarum-pusher.app_id'), - // @phpstan-ignore-next-line ['cluster' => $this->settings->get('flarum-pusher.app_cluster')] ); diff --git a/extensions/pusher/src/Listener/PushNewPost.php b/extensions/pusher/src/Listener/PushNewPost.php index 725452526..605158ad5 100644 --- a/extensions/pusher/src/Listener/PushNewPost.php +++ b/extensions/pusher/src/Listener/PushNewPost.php @@ -14,7 +14,7 @@ use Flarum\Post\Event\Posted; use Flarum\User\Guest; use Flarum\User\User; use Illuminate\Support\Str; -use Pusher; +use Pusher\Pusher; class PushNewPost { @@ -32,15 +32,15 @@ class PushNewPost $channels[] = 'public'; } else { // Retrieve private channels, used for each user. - $response = $this->pusher->get_channels([ + $response = $this->pusher->getChannels([ 'filter_by_prefix' => 'private-user' ]); + // @phpstan-ignore-next-line if (! $response) { return; } - // @phpstan-ignore-next-line foreach ($response->channels as $name => $channel) { $userId = Str::after($name, 'private-user'); diff --git a/extensions/pusher/src/Provider/PusherProvider.php b/extensions/pusher/src/Provider/PusherProvider.php index 0c7f50976..60e5a51aa 100644 --- a/extensions/pusher/src/Provider/PusherProvider.php +++ b/extensions/pusher/src/Provider/PusherProvider.php @@ -11,7 +11,7 @@ namespace Flarum\Pusher\Provider; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Settings\SettingsRepositoryInterface; -use Pusher; +use Pusher\Pusher; class PusherProvider extends AbstractServiceProvider { @@ -30,7 +30,6 @@ class PusherProvider extends AbstractServiceProvider $settings->get('flarum-pusher.app_key'), $settings->get('flarum-pusher.app_secret'), $settings->get('flarum-pusher.app_id'), - // @phpstan-ignore-next-line $options ); }); diff --git a/extensions/pusher/src/SendPusherNotificationsJob.php b/extensions/pusher/src/SendPusherNotificationsJob.php index 31f81207f..6d03cce5d 100644 --- a/extensions/pusher/src/SendPusherNotificationsJob.php +++ b/extensions/pusher/src/SendPusherNotificationsJob.php @@ -12,7 +12,7 @@ namespace Flarum\Pusher; use Flarum\Notification\Blueprint\BlueprintInterface; use Flarum\Queue\AbstractJob; use Flarum\User\User; -use Pusher; +use Pusher\Pusher; class SendPusherNotificationsJob extends AbstractJob { diff --git a/extensions/tags/src/Listener/UpdateTagMetadata.php b/extensions/tags/src/Listener/UpdateTagMetadata.php index c2d6e2505..33f26a684 100755 --- a/extensions/tags/src/Listener/UpdateTagMetadata.php +++ b/extensions/tags/src/Listener/UpdateTagMetadata.php @@ -96,6 +96,7 @@ class UpdateTagMetadata } /** + * @param Collection|null $tags * @param Post|null $post This is only used when a post has been hidden */ protected function updateTags(Discussion $discussion, int $delta = 0, ?Collection $tags = null, ?Post $post = null): void diff --git a/extensions/tags/src/Tag.php b/extensions/tags/src/Tag.php index 553f12476..f9b54f3ed 100644 --- a/extensions/tags/src/Tag.php +++ b/extensions/tags/src/Tag.php @@ -43,8 +43,8 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * * @property TagState $state * @property Tag|null $parent - * @property-read Collection $children - * @property-read Collection $discussions + * @property-read Collection $children + * @property-read Collection $discussions * @property Discussion|null $lastPostedDiscussion * @property User|null $lastPostedUser */ diff --git a/extensions/tags/src/TagRepository.php b/extensions/tags/src/TagRepository.php index e6522d8b7..6c74d0d91 100644 --- a/extensions/tags/src/TagRepository.php +++ b/extensions/tags/src/TagRepository.php @@ -71,7 +71,7 @@ class TagRepository * Find all tags, optionally making sure they are visible to a * certain user. * - * @return Collection + * @return Collection */ public function all(User $user = null): Collection { diff --git a/framework/core/src/Api/ApiKey.php b/framework/core/src/Api/ApiKey.php index bd8f8c45e..930081243 100644 --- a/framework/core/src/Api/ApiKey.php +++ b/framework/core/src/Api/ApiKey.php @@ -21,13 +21,18 @@ use Illuminate\Support\Str; * @property string|null $allowed_ips * @property string|null $scopes * @property int|null $user_id - * @property \Flarum\User\User|null $user * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon|null $last_activity_at + * @property-read \Flarum\User\User|null $user */ class ApiKey extends AbstractModel { - protected $casts = ['last_activity_at' => 'datetime']; + protected $casts = [ + 'id' => 'integer', + 'user_id' => 'integer', + 'created_at' => 'datetime', + 'last_activity_at' => 'datetime' + ]; public static function generate(): static { diff --git a/framework/core/src/Api/Controller/ShowDiscussionController.php b/framework/core/src/Api/Controller/ShowDiscussionController.php index 4fb894a81..cabfe79cb 100644 --- a/framework/core/src/Api/Controller/ShowDiscussionController.php +++ b/framework/core/src/Api/Controller/ShowDiscussionController.php @@ -137,7 +137,7 @@ class ShowDiscussionController extends AbstractShowController /** @var Post $post */ foreach ($posts as $post) { - $post->discussion = $discussion; + $post->setRelation('discussion', $discussion); } $this->loadRelations($posts, $include, $request); diff --git a/framework/core/src/Api/Controller/TerminateAllOtherSessionsController.php b/framework/core/src/Api/Controller/TerminateAllOtherSessionsController.php index 559a73cdb..fadf6f384 100644 --- a/framework/core/src/Api/Controller/TerminateAllOtherSessionsController.php +++ b/framework/core/src/Api/Controller/TerminateAllOtherSessionsController.php @@ -12,7 +12,7 @@ namespace Flarum\Api\Controller; use Flarum\Http\RememberAccessToken; use Flarum\Http\RequestUtil; use Flarum\Http\SessionAccessToken; -use Illuminate\Database\Eloquent\Builder; +use Illuminate\Contracts\Database\Eloquent\Builder; use Psr\Http\Message\ServerRequestInterface; class TerminateAllOtherSessionsController extends AbstractDeleteController diff --git a/framework/core/src/Api/Controller/UpdateDiscussionController.php b/framework/core/src/Api/Controller/UpdateDiscussionController.php index 49305e980..7e6380217 100644 --- a/framework/core/src/Api/Controller/UpdateDiscussionController.php +++ b/framework/core/src/Api/Controller/UpdateDiscussionController.php @@ -14,6 +14,7 @@ use Flarum\Discussion\Command\EditDiscussion; use Flarum\Discussion\Command\ReadDiscussion; use Flarum\Discussion\Discussion; use Flarum\Http\RequestUtil; +use Flarum\Post\Post; use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; @@ -35,6 +36,7 @@ class UpdateDiscussionController extends AbstractShowController $discussionId = Arr::get($request->getQueryParams(), 'id'); $data = Arr::get($request->getParsedBody(), 'data', []); + /** @var Discussion $discussion */ $discussion = $this->bus->dispatch( new EditDiscussion($discussionId, $actor, $data) ); @@ -50,6 +52,7 @@ class UpdateDiscussionController extends AbstractShowController } if ($posts = $discussion->getModifiedPosts()) { + /** @var Collection $posts */ $posts = (new Collection($posts))->load('discussion', 'user'); $discussionPosts = $discussion->posts()->whereVisibleTo($actor)->oldest()->pluck('id')->all(); diff --git a/framework/core/src/Api/Serializer/AbstractSerializer.php b/framework/core/src/Api/Serializer/AbstractSerializer.php index dce3a7c69..a14209453 100644 --- a/framework/core/src/Api/Serializer/AbstractSerializer.php +++ b/framework/core/src/Api/Serializer/AbstractSerializer.php @@ -164,11 +164,9 @@ abstract class AbstractSerializer extends BaseAbstractSerializer { if (is_object($model)) { return $model->$relation; - } elseif (is_array($model)) { - return $model[$relation]; } - return null; + return $model[$relation]; } /** diff --git a/framework/core/src/Console/Schedule.php b/framework/core/src/Console/Schedule.php index 858dc6a44..37d105e29 100644 --- a/framework/core/src/Console/Schedule.php +++ b/framework/core/src/Console/Schedule.php @@ -17,6 +17,7 @@ class Schedule extends LaravelSchedule { public function dueEvents($app) { + /** @phpstan-ignore-next-line */ return (new Collection($this->events))->filter->isDue(new class($app) { protected Config $config; diff --git a/framework/core/src/Database/Eloquent/Collection.php b/framework/core/src/Database/Eloquent/Collection.php index 6fc32a410..ec96d0d79 100644 --- a/framework/core/src/Database/Eloquent/Collection.php +++ b/framework/core/src/Database/Eloquent/Collection.php @@ -9,8 +9,15 @@ namespace Flarum\Database\Eloquent; +use Flarum\Database\AbstractModel; use Illuminate\Database\Eloquent\Collection as BaseCollection; +/** + * @template TKey of array-key + * @template TModel of AbstractModel + * + * @extends BaseCollection + */ class Collection extends BaseCollection { /** diff --git a/framework/core/src/Discussion/Discussion.php b/framework/core/src/Discussion/Discussion.php index 313472451..7cd4a1feb 100644 --- a/framework/core/src/Discussion/Discussion.php +++ b/framework/core/src/Discussion/Discussion.php @@ -46,16 +46,16 @@ use Illuminate\Support\Str; * @property int|null $last_post_number * @property \Carbon\Carbon|null $hidden_at * @property int|null $hidden_user_id - * @property UserState|null $state - * @property \Illuminate\Database\Eloquent\Collection $posts - * @property \Illuminate\Database\Eloquent\Collection $comments - * @property \Illuminate\Database\Eloquent\Collection $participants - * @property Post|null $firstPost - * @property User|null $user - * @property Post|null $lastPost - * @property User|null $lastPostedUser - * @property \Illuminate\Database\Eloquent\Collection $readers * @property bool $is_private + * @property-read UserState|null $state + * @property-read \Illuminate\Database\Eloquent\Collection $posts + * @property-read \Illuminate\Database\Eloquent\Collection $comments + * @property-read \Illuminate\Database\Eloquent\Collection $participants + * @property-read Post|null $firstPost + * @property-read User|null $user + * @property-read Post|null $lastPost + * @property-read User|null $lastPostedUser + * @property-read \Illuminate\Database\Eloquent\Collection $readers */ class Discussion extends AbstractModel { @@ -69,12 +69,16 @@ class Discussion extends AbstractModel */ protected array $modifiedPosts = []; - /** - * The attributes that should be cast to native types. - * - * @var array - */ protected $casts = [ + 'id' => 'integer', + 'comment_count' => 'integer', + 'participant_count' => 'integer', + 'user_id' => 'integer', + 'first_post_id' => 'integer', + 'last_posted_user_id' => 'integer', + 'last_post_id' => 'integer', + 'last_post_number' => 'integer', + 'hidden_user_id' => 'integer', 'is_private' => 'boolean', 'created_at' => 'datetime', 'last_posted_at' => 'datetime', @@ -224,6 +228,8 @@ class Discussion extends AbstractModel /** * Get the posts that have been modified during this request. + * + * @return Post[] */ public function getModifiedPosts(): array { diff --git a/framework/core/src/Discussion/DiscussionRepository.php b/framework/core/src/Discussion/DiscussionRepository.php index 434c65c77..ace69e743 100644 --- a/framework/core/src/Discussion/DiscussionRepository.php +++ b/framework/core/src/Discussion/DiscussionRepository.php @@ -38,7 +38,7 @@ class DiscussionRepository * Get the IDs of discussions which a user has read completely. * * @param User $user - * @return Collection + * @return Collection * @deprecated 1.3 Use `getReadIdsQuery` instead */ public function getReadIds(User $user): Collection diff --git a/framework/core/src/Discussion/UserState.php b/framework/core/src/Discussion/UserState.php index 9369a7e9d..fc1363c3b 100644 --- a/framework/core/src/Discussion/UserState.php +++ b/framework/core/src/Discussion/UserState.php @@ -28,8 +28,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property int $discussion_id * @property Carbon|null $last_read_at * @property int|null $last_read_post_number - * @property Discussion $discussion - * @property User $user + * @property-read Discussion $discussion + * @property-read User $user */ class UserState extends AbstractModel { @@ -37,12 +37,12 @@ class UserState extends AbstractModel protected $table = 'discussion_user'; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $casts = ['last_read_at' => 'datetime']; + protected $casts = [ + 'user_id' => 'integer', + 'discussion_id' => 'integer', + 'last_read_post_number' => 'integer', + 'last_read_at' => 'datetime' + ]; /** * The attributes that are mass assignable. diff --git a/framework/core/src/Extension/ExtensionManager.php b/framework/core/src/Extension/ExtensionManager.php index 01613643d..8d54d09df 100644 --- a/framework/core/src/Extension/ExtensionManager.php +++ b/framework/core/src/Extension/ExtensionManager.php @@ -95,7 +95,7 @@ class ExtensionManager $extension = $extensions->get($enabledKey); if (is_null($extension)) { $needsReset = true; - } else { + } else { // @phpstan-ignore-line $enabledExtensions[] = $extension; } } diff --git a/framework/core/src/Foundation/Application.php b/framework/core/src/Foundation/Application.php index 4ed1dcdfe..eb51c40ab 100644 --- a/framework/core/src/Foundation/Application.php +++ b/framework/core/src/Foundation/Application.php @@ -236,4 +236,17 @@ class Application } } } + + public function version(): string + { + return static::VERSION; + } + + public function terminating(): void + { + } + + public function terminate(): void + { + } } diff --git a/framework/core/src/Foundation/Container.php b/framework/core/src/Foundation/Container.php index 590ea1b89..20998c8cf 100644 --- a/framework/core/src/Foundation/Container.php +++ b/framework/core/src/Foundation/Container.php @@ -9,13 +9,19 @@ namespace Flarum\Foundation; -class Container extends \Illuminate\Container\Container -{ - public function terminating(): void - { - } +use Illuminate\Container\Container as LaravelContainer; - public function terminate(): void +class Container extends LaravelContainer +{ + /** + * Laravel's application is the container itself. + * So as we upgrade Laravel versions, some of its internals that we rely on + * make calls to methods that don't exist in our container, but do in Laravel's Application. + * + * @TODO: Implement the Application contract and merge the container into it. + */ + public function __call(string $name, array $arguments) { + return $this->get('flarum')->$name(...$arguments); } } diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php index a8a9f72a4..2cdb50759 100644 --- a/framework/core/src/Foundation/InstalledSite.php +++ b/framework/core/src/Foundation/InstalledSite.php @@ -39,6 +39,7 @@ use Illuminate\Cache\Repository as CacheRepository; use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Contracts\Cache\Repository; use Illuminate\Contracts\Cache\Store; +use Illuminate\Contracts\Container\Container as LaravelContainer; use Illuminate\Filesystem\Filesystem; use Illuminate\Hashing\HashServiceProvider; use Illuminate\Validation\ValidationServiceProvider; @@ -86,7 +87,7 @@ class InstalledSite implements SiteInterface return $this; } - protected function bootLaravel(): Container + protected function bootLaravel(): LaravelContainer { $container = new Container; $laravel = new Application($container, $this->paths); @@ -95,7 +96,7 @@ class InstalledSite implements SiteInterface $container->instance('flarum.config', $this->config); $container->alias('flarum.config', Config::class); $container->instance('flarum.debug', $this->config->inDebugMode()); - $container->instance('config', $config = $this->getIlluminateConfig()); + $container->instance('config', $this->getIlluminateConfig()); $container->instance('flarum.maintenance.handler', new MaintenanceModeHandler); $this->registerLogger($container); diff --git a/framework/core/src/Group/Group.php b/framework/core/src/Group/Group.php index 8a809f22c..991323813 100644 --- a/framework/core/src/Group/Group.php +++ b/framework/core/src/Group/Group.php @@ -26,8 +26,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property string|null $color * @property string|null $icon * @property bool $is_hidden - * @property \Illuminate\Database\Eloquent\Collection $users - * @property \Illuminate\Database\Eloquent\Collection $permissions + * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read \Illuminate\Database\Eloquent\Collection $permissions */ class Group extends AbstractModel { @@ -39,12 +39,9 @@ class Group extends AbstractModel const MEMBER_ID = 3; const MODERATOR_ID = 4; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ protected $casts = [ + 'id' => 'integer', + 'is_hidden' => 'boolean', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; diff --git a/framework/core/src/Group/Permission.php b/framework/core/src/Group/Permission.php index 28ebd8768..e6413c650 100644 --- a/framework/core/src/Group/Permission.php +++ b/framework/core/src/Group/Permission.php @@ -21,12 +21,10 @@ class Permission extends AbstractModel { protected $table = 'group_permission'; - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $casts = ['created_at' => 'datetime']; + protected $casts = [ + 'group_id' => 'integer', + 'created_at' => 'datetime' + ]; public function group(): BelongsTo { diff --git a/framework/core/src/Http/AccessToken.php b/framework/core/src/Http/AccessToken.php index faf492806..41f57c97d 100644 --- a/framework/core/src/Http/AccessToken.php +++ b/framework/core/src/Http/AccessToken.php @@ -29,7 +29,7 @@ use Psr\Http\Message\ServerRequestInterface; * @property string $title * @property string|null $last_ip_address * @property string|null $last_user_agent - * @property \Flarum\User\User|null $user + * @property-read \Flarum\User\User|null $user */ class AccessToken extends AbstractModel { @@ -38,6 +38,8 @@ class AccessToken extends AbstractModel protected $table = 'access_tokens'; protected $casts = [ + 'id' => 'integer', + 'user_id' => 'integer', 'created_at' => 'datetime', 'last_activity_at' => 'datetime', ]; diff --git a/framework/core/src/Install/Steps/EnableBundledExtensions.php b/framework/core/src/Install/Steps/EnableBundledExtensions.php index e8e11af19..6ce5ca206 100644 --- a/framework/core/src/Install/Steps/EnableBundledExtensions.php +++ b/framework/core/src/Install/Steps/EnableBundledExtensions.php @@ -19,8 +19,8 @@ use Illuminate\Database\ConnectionInterface; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use League\Flysystem\Adapter\Local; use League\Flysystem\Filesystem; +use League\Flysystem\Local\LocalFilesystemAdapter; class EnableBundledExtensions implements Step { @@ -68,8 +68,9 @@ class EnableBundledExtensions implements Step foreach ($extensions as $extension) { $extension->migrate($this->getMigrator()); + $adapter = new LocalFilesystemAdapter($this->assetPath); $extension->copyAssetsTo( - new FilesystemAdapter(new Filesystem(new Local($this->assetPath))) + new FilesystemAdapter(new Filesystem($adapter), $adapter) ); } @@ -81,7 +82,7 @@ class EnableBundledExtensions implements Step } /** - * @return Collection + * @return Collection */ private function loadExtensions(): Collection { diff --git a/framework/core/src/Locale/TranslatorInterface.php b/framework/core/src/Locale/TranslatorInterface.php index 0453dab52..24b028953 100644 --- a/framework/core/src/Locale/TranslatorInterface.php +++ b/framework/core/src/Locale/TranslatorInterface.php @@ -14,4 +14,5 @@ use Symfony\Contracts\Translation\TranslatorInterface as SymfonyTranslatorInterf interface TranslatorInterface extends Translator, SymfonyTranslatorInterface { + public function getLocale(): string; } diff --git a/framework/core/src/Notification/Notification.php b/framework/core/src/Notification/Notification.php index b3ba44d5d..183d13337 100644 --- a/framework/core/src/Notification/Notification.php +++ b/framework/core/src/Notification/Notification.php @@ -37,22 +37,27 @@ use Illuminate\Support\Arr; * @property int|null $from_user_id * @property string $type * @property int|null $subject_id - * @property mixed|null $data + * @property array|null $data * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $read_at * @property \Carbon\Carbon $deleted_at - * @property \Flarum\User\User|null $user - * @property \Flarum\User\User|null $fromUser - * @property \Flarum\Database\AbstractModel|\Flarum\Post\Post|\Flarum\Discussion\Discussion|null $subject + * @property-read \Flarum\User\User|null $user + * @property-read \Flarum\User\User|null $fromUser + * @property-read \Flarum\Database\AbstractModel|\Flarum\Post\Post|\Flarum\Discussion\Discussion|null $subject + * @method static \Illuminate\Database\Eloquent\Builder matchingBlueprint(BlueprintInterface $blueprint) */ class Notification extends AbstractModel { - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $casts = ['created_at' => 'datetime', 'read_at' => 'datetime']; + protected $casts = [ + 'id' => 'integer', + 'user_id' => 'integer', + 'from_user_id' => 'integer', + 'subject_id' => 'integer', + 'data' => 'array', + 'created_at' => 'datetime', + 'read_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; /** * A map of notification types and the model classes to use for their @@ -69,26 +74,6 @@ class Notification extends AbstractModel $this->read_at = Carbon::now(); } - /** - * When getting the data attribute, unserialize the JSON stored in the - * database into a plain array. - */ - public function getDataAttribute(?string $value): mixed - { - return $value !== null - ? json_decode($value, true) - : null; - } - - /** - * When setting the data attribute, serialize it into JSON for storage in - * the database. - */ - public function setDataAttribute(mixed $value): void - { - $this->attributes['data'] = json_encode($value); - } - /** * Get the subject model for this notification record by looking up its * type in our subject model map. diff --git a/framework/core/src/Notification/NotificationRepository.php b/framework/core/src/Notification/NotificationRepository.php index 7de4a59b0..496c88e05 100644 --- a/framework/core/src/Notification/NotificationRepository.php +++ b/framework/core/src/Notification/NotificationRepository.php @@ -15,6 +15,9 @@ use Illuminate\Database\Eloquent\Collection; class NotificationRepository { + /** + * @return Collection + */ public function findByUser(User $user, ?int $limit = null, int $offset = 0): Collection { $primaries = Notification::query() diff --git a/framework/core/src/Notification/NotificationSyncer.php b/framework/core/src/Notification/NotificationSyncer.php index 3c6aaa5d3..25b4847bd 100644 --- a/framework/core/src/Notification/NotificationSyncer.php +++ b/framework/core/src/Notification/NotificationSyncer.php @@ -70,7 +70,8 @@ class NotificationSyncer continue; } - $existing = $toDelete->first(function ($notification) use ($user) { + /** @var Notification|null $existing */ + $existing = $toDelete->first(function (Notification $notification) use ($user) { return $notification->user_id === $user->id; }); diff --git a/framework/core/src/Post/Post.php b/framework/core/src/Post/Post.php index 4b875fbe0..09ad65352 100644 --- a/framework/core/src/Post/Post.php +++ b/framework/core/src/Post/Post.php @@ -33,12 +33,12 @@ use Staudenmeir\EloquentEagerLimit\HasEagerLimit; * @property int|null $edited_user_id * @property \Carbon\Carbon|null $hidden_at * @property int|null $hidden_user_id - * @property \Flarum\Discussion\Discussion|null $discussion - * @property User|null $user - * @property User|null $editedUser - * @property User|null $hiddenUser * @property string $ip_address * @property bool $is_private + * @property-read Discussion|null $discussion + * @property-read User|null $user + * @property-read User|null $editedUser + * @property-read User|null $hiddenUser */ class Post extends AbstractModel { @@ -48,12 +48,13 @@ class Post extends AbstractModel protected $table = 'posts'; - /** - * The attributes that should be cast to native types. - * - * @var array - */ protected $casts = [ + 'id' => 'integer', + 'discussion_id' => 'integer', + 'number' => 'integer', + 'user_id' => 'integer', + 'edited_user_id' => 'integer', + 'hidden_user_id' => 'integer', 'is_private' => 'boolean', 'created_at' => 'datetime', 'edited_at' => 'datetime', diff --git a/framework/core/src/User/EmailToken.php b/framework/core/src/User/EmailToken.php index 6fda5433a..ed91dbdbe 100644 --- a/framework/core/src/User/EmailToken.php +++ b/framework/core/src/User/EmailToken.php @@ -24,12 +24,10 @@ use Illuminate\Support\Str; */ class EmailToken extends AbstractModel { - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $casts = ['created_at' => 'datetime']; + protected $casts = [ + 'user_id' => 'integer', + 'created_at' => 'datetime', + ]; /** * Use a custom primary key for this model. diff --git a/framework/core/src/User/PasswordToken.php b/framework/core/src/User/PasswordToken.php index 4806cb3c1..a0f0eaf90 100644 --- a/framework/core/src/User/PasswordToken.php +++ b/framework/core/src/User/PasswordToken.php @@ -21,18 +21,11 @@ use Illuminate\Support\Str; */ class PasswordToken extends AbstractModel { - /** - * The attributes that should be mutated to dates. - * - * @var array - */ - protected $casts = ['created_at' => 'datetime']; + protected $casts = [ + 'created_at' => 'datetime', + 'user_id' => 'integer', + ]; - /** - * Use a custom primary key for this model. - * - * @var bool - */ public $incrementing = false; protected $primaryKey = 'token'; diff --git a/framework/core/src/User/User.php b/framework/core/src/User/User.php index e0a92f2a2..a96f63adb 100644 --- a/framework/core/src/User/User.php +++ b/framework/core/src/User/User.php @@ -63,6 +63,8 @@ class User extends AbstractModel use HasEagerLimit; protected $casts = [ + 'id' => 'integer', + 'is_email_confirmed' => 'boolean', 'joined_at' => 'datetime', 'last_seen_at' => 'datetime', 'marked_all_as_read_at' => 'datetime', diff --git a/php-packages/phpstan/bootstrap.php b/php-packages/phpstan/bootstrap.php new file mode 100644 index 000000000..dcc4d8a2d --- /dev/null +++ b/php-packages/phpstan/bootstrap.php @@ -0,0 +1,22 @@ +run(); +$site->bootApp(); diff --git a/php-packages/phpstan/composer.json b/php-packages/phpstan/composer.json index 74a8cfe17..59c78c459 100644 --- a/php-packages/phpstan/composer.json +++ b/php-packages/phpstan/composer.json @@ -4,8 +4,9 @@ "minimum-stability": "dev", "license": "MIT", "require": { - "phpstan/phpstan": ">=1.8.11 < 1.9.0", - "nunomaduro/larastan": "^1.0" + "flarum/testing": "^2.0", + "phpstan/phpstan": "^1.10.0", + "nunomaduro/larastan": "^2.6" }, "autoload": { "psr-4": { diff --git a/php-packages/phpstan/larastan-extension.neon b/php-packages/phpstan/larastan-extension.neon index 3a2d8462e..f9ec2b9f3 100644 --- a/php-packages/phpstan/larastan-extension.neon +++ b/php-packages/phpstan/larastan-extension.neon @@ -1,38 +1,7 @@ parameters: - stubFiles: - - %rootDir%/../../nunomaduro/larastan/stubs/Enumerable.stub - - %rootDir%/../../nunomaduro/larastan/stubs/EloquentBuilder.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Collection.stub - - %rootDir%/../../nunomaduro/larastan/stubs/EloquentCollection.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Factory.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Model.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Gate.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Relation.stub - - %rootDir%/../../nunomaduro/larastan/stubs/BelongsTo.stub - - %rootDir%/../../nunomaduro/larastan/stubs/BelongsToMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HasOneOrMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HasMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HasOne.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HasOneThrough.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HasManyThrough.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Mailable.stub - - %rootDir%/../../nunomaduro/larastan/stubs/MorphOne.stub - - %rootDir%/../../nunomaduro/larastan/stubs/MorphOneOrMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/MorphTo.stub - - %rootDir%/../../nunomaduro/larastan/stubs/MorphToMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/MorphMany.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Helpers.stub - - %rootDir%/../../nunomaduro/larastan/stubs/HigherOrderProxies.stub - - %rootDir%/../../nunomaduro/larastan/stubs/QueryBuilder.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Facades.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Pagination.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Contracts/Pagination.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Contracts/Support.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Redis/Connection.stub - - %rootDir%/../../nunomaduro/larastan/stubs/Logger.stub - - %rootDir%/../../nunomaduro/larastan/stubs/EnumeratesValues.stub universalObjectCratesClasses: - Illuminate\Http\Request + - Illuminate\Support\Optional earlyTerminatingFunctionCalls: - abort - dd @@ -40,17 +9,21 @@ parameters: - *.blade.php mixinExcludeClasses: - Eloquent -# bootstrapFiles: -# - bootstrap.php - checkGenericClassInNonGenericObjectType: false + bootstrapFiles: + - bootstrap.php checkOctaneCompatibility: false noModelMake: true noUnnecessaryCollectionCall: true noUnnecessaryCollectionCallOnly: [] noUnnecessaryCollectionCallExcept: [] + squashedMigrationsPath: [] databaseMigrationsPath: [] + disableMigrationScan: false + disableSchemaScan: false + viewDirectories: [] checkModelProperties: false checkPhpDocMissingReturn: false + checkUnusedViews: false parametersSchema: checkOctaneCompatibility: bool() @@ -59,7 +32,12 @@ parametersSchema: noUnnecessaryCollectionCallOnly: listOf(string()) noUnnecessaryCollectionCallExcept: listOf(string()) databaseMigrationsPath: listOf(string()) + disableMigrationScan: bool() + viewDirectories: listOf(string()) + squashedMigrationsPath: listOf(string()) + disableSchemaScan: bool() checkModelProperties: bool() + checkUnusedViews: bool() conditionalTags: NunoMaduro\Larastan\Rules\NoModelMakeRule: @@ -72,6 +50,8 @@ conditionalTags: phpstan.rules.rule: %checkModelProperties% NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyStaticCallRule: phpstan.rules.rule: %checkModelProperties% + NunoMaduro\Larastan\Rules\UnusedViewsRule: + phpstan.rules.rule: %checkUnusedViews% services: - @@ -116,6 +96,14 @@ services: class: NunoMaduro\Larastan\Methods\RedirectResponseMethodsClassReflectionExtension tags: - phpstan.broker.methodsClassReflectionExtension + - + class: NunoMaduro\Larastan\Methods\MacroMethodsClassReflectionExtension + tags: + - phpstan.broker.methodsClassReflectionExtension + - + class: NunoMaduro\Larastan\Methods\ViewWithMethodsClassReflectionExtension + tags: + - phpstan.broker.methodsClassReflectionExtension - class: NunoMaduro\Larastan\Properties\ModelAccessorExtension @@ -182,7 +170,12 @@ services: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\ModelExtension + class: NunoMaduro\Larastan\ReturnTypes\ModelDynamicStaticMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\AppMakeDynamicReturnTypeExtension tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension @@ -201,13 +194,28 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\DateExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - class: NunoMaduro\Larastan\ReturnTypes\GuardExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\RequestExtension + class: NunoMaduro\Larastan\ReturnTypes\RequestFileExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\RequestRouteExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\RequestUserExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension @@ -241,11 +249,6 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: NunoMaduro\Larastan\ReturnTypes\CollectionMakeDynamicStaticMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: NunoMaduro\Larastan\Support\CollectionHelper @@ -260,7 +263,7 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\CookieExtension + class: NunoMaduro\Larastan\ReturnTypes\Helpers\NowAndTodayExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension @@ -269,31 +272,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\RequestExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\RedirectExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\UrlExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\ViewExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\TransExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\ValidatorExtension tags: @@ -303,6 +281,30 @@ services: class: NunoMaduro\Larastan\ReturnTypes\CollectionFilterDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\CollectionWhereNotNullDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\CollectionGenericStaticMethodDynamicMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\NewModelQueryDynamicMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\FactoryDynamicMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\CollectionGenericStaticMethodDynamicStaticMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension - class: NunoMaduro\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension @@ -388,6 +390,10 @@ services: class: NunoMaduro\Larastan\Types\GenericEloquentBuilderTypeNodeResolverExtension tags: - phpstan.phpDoc.typeNodeResolverExtension + - + class: NunoMaduro\Larastan\ReturnTypes\AppEnvironmentReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension - class: NunoMaduro\Larastan\Types\ModelProperty\ModelPropertyTypeNodeResolverExtension @@ -405,8 +411,18 @@ services: class: NunoMaduro\Larastan\Properties\MigrationHelper arguments: databaseMigrationPath: %databaseMigrationsPath% + disableMigrationScan: %disableMigrationScan% parser: @currentPhpVersionSimpleDirectParser + - + class: NunoMaduro\Larastan\Properties\SquashedMigrationHelper + arguments: + schemaPaths: %squashedMigrationsPath% + disableSchemaScan: %disableSchemaScan% + + - + class: NunoMaduro\Larastan\Properties\ModelCastHelper + - class: NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertiesRuleHelper @@ -421,7 +437,7 @@ services: - class: NunoMaduro\Larastan\Rules\RelationExistenceRule tags: - - phpstan.rule + - phpstan.rules.rule - class: NunoMaduro\Larastan\Rules\CheckDispatchArgumentTypesCompatibleWithClassConstructorRule @@ -435,5 +451,94 @@ services: dispatchableClass: Illuminate\Foundation\Events\Dispatchable tags: - phpstan.rules.rule + - NunoMaduro\Larastan\Properties\Schema\PhpMyAdminDataTypeToPhpTypeConverter + + - + class: NunoMaduro\Larastan\LarastanStubFilesExtension + tags: [phpstan.stubFilesExtension] + + - + class: NunoMaduro\Larastan\Rules\UnusedViewsRule + + - + class: NunoMaduro\Larastan\Collectors\UsedViewFunctionCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedEmailViewCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedViewMakeCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedViewFacadeMakeCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedRouteFacadeViewCollector + tags: + - phpstan.collector + - + class: NunoMaduro\Larastan\Collectors\UsedViewInAnotherViewCollector + arguments: + parser: @currentPhpVersionSimpleDirectParser + - + class: NunoMaduro\Larastan\Support\ViewFileHelper + arguments: + viewDirectories: %viewDirectories% + +# - +# class: NunoMaduro\Larastan\ReturnTypes\ApplicationMakeDynamicReturnTypeExtension +# tags: +# - phpstan.broker.dynamicMethodReturnTypeExtension +# +# - +# class: NunoMaduro\Larastan\ReturnTypes\ContainerMakeDynamicReturnTypeExtension +# tags: +# - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\ConsoleCommand\ArgumentDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\ConsoleCommand\HasArgumentDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\ConsoleCommand\OptionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\ConsoleCommand\HasOptionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\TranslatorGetReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\TransHelperReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: NunoMaduro\Larastan\ReturnTypes\DoubleUnderscoreHelperReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + + - NunoMaduro\Larastan\ReturnTypes\AppMakeHelper + - NunoMaduro\Larastan\Internal\ConsoleApplicationResolver + - NunoMaduro\Larastan\Internal\ConsoleApplicationHelper + - NunoMaduro\Larastan\Support\HigherOrderCollectionProxyHelper + rules: - - NunoMaduro\Larastan\Rules\RelationExistenceRule + - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessWithFunctionCallsRule + - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessValueFunctionCallsRule + - NunoMaduro\Larastan\Rules\DeferrableServiceProviderMissingProvidesRule + - NunoMaduro\Larastan\Rules\ConsoleCommand\UndefinedArgumentOrOptionRule diff --git a/php-packages/phpstan/phpstan-baseline.neon b/php-packages/phpstan/phpstan-baseline.neon index 3a50f8b4f..be8117205 100644 --- a/php-packages/phpstan/phpstan-baseline.neon +++ b/php-packages/phpstan/phpstan-baseline.neon @@ -16,6 +16,8 @@ parameters: # @TODO: needs discussion. - message: "#^Unsafe usage of new static[()]{2}.$#" reportUnmatched: false + - message: "#^Static access to instance property .*#" + reportUnmatched: false # ConnectionInterface lacks methods that exist in the implementation, # yet we don't want to inject the implementation. diff --git a/php-packages/phpstan/src/Relations/RelationProperty.php b/php-packages/phpstan/src/Relations/RelationProperty.php index 108ceee57..a57245110 100644 --- a/php-packages/phpstan/src/Relations/RelationProperty.php +++ b/php-packages/phpstan/src/Relations/RelationProperty.php @@ -16,6 +16,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\IntegerType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -59,18 +60,14 @@ class RelationProperty implements PropertyReflection public function getReadableType(): Type { - switch ($this->methodCall->methodName) { - case 'hasMany': - case 'belongsToMany': - return new GenericObjectType(Collection::class, [new ObjectType($this->methodCall->arguments[1]->class->toString())]); - - case 'hasOne': - case 'belongsTo': - return new ObjectType($this->methodCall->arguments[1]->class->toString()); - - default: - throw new Exception('Unknown relationship type for relation: '.$this->methodCall->methodName); - } + return match ($this->methodCall->methodName) { + 'hasMany', 'belongsToMany' => new GenericObjectType(Collection::class, [ + new IntegerType(), + new ObjectType($this->methodCall->arguments[1]->class->toString()) + ]), + 'hasOne', 'belongsTo' => new ObjectType($this->methodCall->arguments[1]->class->toString()), + default => throw new Exception('Unknown relationship type for relation: '.$this->methodCall->methodName), + }; } public function getWritableType(): Type diff --git a/php-packages/testing/src/integration/Setup/Bootstrapper.php b/php-packages/testing/src/integration/Setup/Bootstrapper.php new file mode 100644 index 000000000..7e8b5be13 --- /dev/null +++ b/php-packages/testing/src/integration/Setup/Bootstrapper.php @@ -0,0 +1,80 @@ +tmpDir(); + + if (! file_exists("$tmp/config.php")) { + $setup = new SetupScript(); + $setup->run(); + } + } + + public function run(): InstalledSite + { + $this->setupOnce(); + + $tmp = $this->tmpDir(); + + $config = include "$tmp/config.php"; + + foreach ($this->config as $key => $value) { + Arr::set($config, $key, $value); + } + + $site = new InstalledSite( + new Paths([ + 'base' => $tmp, + 'public' => "$tmp/public", + 'storage' => "$tmp/storage", + 'vendor' => getenv('FLARUM_TEST_VENDOR_PATH') ?: getcwd().'/vendor', + ]), + new Config($config) + ); + + $extenders = array_merge([ + new OverrideExtensionManagerForTests($this->extensions), + new BeginTransactionAndSetDatabase(function (ConnectionInterface $db) { + $this->database = $db; + }), + new SetSettingsBeforeBoot($this->settings), + ], $this->extenders); + + $site->extendWith($extenders); + + return $site; + } +} diff --git a/php-packages/testing/src/integration/TestCase.php b/php-packages/testing/src/integration/TestCase.php index a0913ab2f..f2bdfb64a 100644 --- a/php-packages/testing/src/integration/TestCase.php +++ b/php-packages/testing/src/integration/TestCase.php @@ -10,12 +10,7 @@ namespace Flarum\Testing\integration; use Flarum\Extend\ExtenderInterface; -use Flarum\Foundation\Config; -use Flarum\Foundation\InstalledSite; -use Flarum\Foundation\Paths; -use Flarum\Testing\integration\Extend\BeginTransactionAndSetDatabase; -use Flarum\Testing\integration\Extend\OverrideExtensionManagerForTests; -use Flarum\Testing\integration\Extend\SetSettingsBeforeBoot; +use Flarum\Testing\integration\Setup\Bootstrapper; use Illuminate\Contracts\Cache\Store; use Illuminate\Database\ConnectionInterface; use Illuminate\Support\Arr; @@ -51,35 +46,16 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase protected function app() { if (is_null($this->app)) { - $tmp = $this->tmpDir(); - - $config = include "$tmp/config.php"; - - foreach ($this->config as $key => $value) { - Arr::set($config, $key, $value); - } - - $site = new InstalledSite( - new Paths([ - 'base' => $tmp, - 'public' => "$tmp/public", - 'storage' => "$tmp/storage", - 'vendor' => getenv('FLARUM_TEST_VENDOR_PATH') ?: getcwd().'/vendor', - ]), - new Config($config) + $bootstrapper = new Bootstrapper( + $this->config, + $this->extensions, + $this->settings, + $this->extenders ); - $extenders = array_merge([ - new OverrideExtensionManagerForTests($this->extensions), - new BeginTransactionAndSetDatabase(function (ConnectionInterface $db) { - $this->database = $db; - }), - new SetSettingsBeforeBoot($this->settings), - ], $this->extenders); + $this->app = $bootstrapper->run()->bootApp(); - $site->extendWith($extenders); - - $this->app = $site->bootApp(); + $this->database = $bootstrapper->database; $this->populateDatabase(); }