chore: Setup PHPStan Level 5 (#3553)

This commit is contained in:
Sami Mazouz 2022-09-14 15:23:56 +01:00 committed by GitHub
parent b2fa28e4b5
commit fc4d5e3d43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
206 changed files with 932 additions and 10882 deletions

View File

@ -20,3 +20,6 @@ indent_size = 4
[tsconfig.json] [tsconfig.json]
indent_size = 2 indent_size = 2
[*.neon]
indent_style = tab

View File

@ -9,6 +9,12 @@ on:
default: true default: true
required: false required: false
enable_phpstan:
description: "Enable PHPStan Static Analysis?"
type: boolean
default: false
required: false
backend_directory: backend_directory:
description: The directory of the project where backend code is located. This should contain a `composer.json` file, and is generally the root directory of the repo. description: The directory of the project where backend code is located. This should contain a `composer.json` file, and is generally the root directory of the repo.
type: string type: string
@ -130,3 +136,35 @@ jobs:
working-directory: ${{ inputs.backend_directory }} working-directory: ${{ inputs.backend_directory }}
env: env:
COMPOSER_PROCESS_TIMEOUT: 600 COMPOSER_PROCESS_TIMEOUT: 600
phpstan:
runs-on: ubuntu-latest
strategy:
matrix:
php: ${{ fromJSON(inputs.php_versions) }}
name: 'PHPStan PHP ${{ matrix.php }}'
if: >-
inputs.enable_phpstan &&
((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || github.event_name != 'pull_request')
steps:
- uses: actions/checkout@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: phpunit, composer:v2
ini-values: ${{ inputs.php_ini_values }}
- name: Install Composer dependencies
run: composer install
working-directory: ${{ inputs.backend_directory }}
- name: Run PHPStan
run: composer analyse:phpstan

12
.github/workflows/phpstan.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Framework PHP
on: [workflow_dispatch, push, pull_request]
jobs:
run:
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false
enable_phpstan: true
backend_directory: .

View File

@ -140,8 +140,8 @@
"require-dev": { "require-dev": {
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",
"phpstan/phpstan-php-parser": "^1.0", "phpstan/phpstan": "^1.2",
"phpstan/phpstan": "^1.2" "nunomaduro/larastan": "^1.0"
}, },
"config": { "config": {
"sort-packages": true "sort-packages": true
@ -178,5 +178,11 @@
"extension.neon" "extension.neon"
] ]
} }
},
"scripts": {
"analyse:phpstan": "phpstan analyse"
},
"scripts-descriptions": {
"analyse:phpstan": "Run static analysis"
} }
} }

View File

@ -11,7 +11,6 @@ namespace Flarum\Api;
use Flarum\Http\RequestUtil; use Flarum\Http\RequestUtil;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Contracts\Container\Container;
use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Uri; use Laminas\Diactoros\Uri;
use Laminas\Stratigility\MiddlewarePipeInterface; use Laminas\Stratigility\MiddlewarePipeInterface;
@ -26,12 +25,12 @@ class Client
protected $pipe; protected $pipe;
/** /**
* @var User * @var User|null
*/ */
protected $actor; protected $actor;
/** /**
* @var ServerRequestInterface * @var ServerRequestInterface|null
*/ */
protected $parent; protected $parent;
@ -45,9 +44,6 @@ class Client
*/ */
protected $body = []; protected $body = [];
/**
* @param Container $container
*/
public function __construct(MiddlewarePipeInterface $pipe) public function __construct(MiddlewarePipeInterface $pipe)
{ {
$this->pipe = $pipe; $this->pipe = $pipe;

View File

@ -88,7 +88,7 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
protected static $beforeSerializationCallbacks = []; protected static $beforeSerializationCallbacks = [];
/** /**
* @var string[] * @var string[][]
*/ */
protected static $loadRelations = []; protected static $loadRelations = [];

View File

@ -113,15 +113,15 @@ class ListNotificationsController extends AbstractListController
$ids = []; $ids = [];
foreach ($notifications as $notification) { foreach ($notifications as $notification) {
if ($notification->subject && $notification->subject->discussion_id) { if ($notification->subject && isset($notification->subject->discussion_id)) {
$ids[] = $notification->subject->discussion_id; $ids[] = $notification->subject->discussion_id;
} }
} }
$discussions = Discussion::find(array_unique($ids)); $discussions = Discussion::query()->find(array_unique($ids));
foreach ($notifications as $notification) { foreach ($notifications as $notification) {
if ($notification->subject && $notification->subject->discussion_id) { if ($notification->subject && isset($notification->subject->discussion_id)) {
$notification->subject->setRelation('discussion', $discussions->find($notification->subject->discussion_id)); $notification->subject->setRelation('discussion', $discussions->find($notification->subject->discussion_id));
} }
} }

View File

@ -42,12 +42,12 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
protected static $container; protected static $container;
/** /**
* @var callable[] * @var array<string, callable[]>
*/ */
protected static $attributeMutators = []; protected static $attributeMutators = [];
/** /**
* @var array * @var array<string, array<string, callable>>
*/ */
protected static $customRelations = []; protected static $customRelations = [];
@ -189,12 +189,12 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
* @param string|Closure|\Tobscure\JsonApi\SerializerInterface $serializer * @param string|Closure|\Tobscure\JsonApi\SerializerInterface $serializer
* @param string|null $relation * @param string|null $relation
* @param bool $many * @param bool $many
* @return Relationship * @return Relationship|null
*/ */
protected function buildRelationship($model, $serializer, $relation = null, $many = false) protected function buildRelationship($model, $serializer, $relation = null, $many = false)
{ {
if (is_null($relation)) { if (is_null($relation)) {
list(, , $caller) = debug_backtrace(false, 3); list(, , $caller) = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3);
$relation = $caller['function']; $relation = $caller['function'];
} }
@ -210,6 +210,8 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
return new Relationship($element); return new Relationship($element);
} }
return null;
} }
/** /**

View File

@ -12,7 +12,8 @@ namespace Flarum\Api\Serializer;
class ExtensionReadmeSerializer extends AbstractSerializer class ExtensionReadmeSerializer extends AbstractSerializer
{ {
/** /**
* {@inheritdoc} * @param \Flarum\Extension\Extension $extension
* @return array
*/ */
protected function getDefaultAttributes($extension) protected function getDefaultAttributes($extension)
{ {

View File

@ -62,7 +62,7 @@ class ForumSerializer extends AbstractSerializer
*/ */
public function getId($model) public function getId($model)
{ {
return 1; return '1';
} }
/** /**

View File

@ -18,17 +18,20 @@ class Schedule extends LaravelSchedule
public function dueEvents($container) public function dueEvents($container)
{ {
return (new Collection($this->events))->filter->isDue(new class($container) { return (new Collection($this->events))->filter->isDue(new class($container) {
/** @var Config */
protected $config;
public function __construct($container) public function __construct($container)
{ {
$this->config = $container->make(Config::class); $this->config = $container->make(Config::class);
} }
public function isDownForMaintenance() public function isDownForMaintenance(): bool
{ {
return $this->config->inMaintenanceMode(); return $this->config->inMaintenanceMode();
} }
public function environment() public function environment(): string
{ {
return ''; return '';
} }

View File

@ -20,6 +20,8 @@ use LogicException;
* Adds the ability for custom relations to be added to a model during runtime. * Adds the ability for custom relations to be added to a model during runtime.
* These relations behave in the same way that you would expect; they can be * These relations behave in the same way that you would expect; they can be
* queried, eager loaded, and accessed as an attribute. * queried, eager loaded, and accessed as an attribute.
*
* @property-read int|null $id
*/ */
abstract class AbstractModel extends Eloquent abstract class AbstractModel extends Eloquent
{ {

View File

@ -12,6 +12,7 @@ namespace Flarum\Database\Console;
use Flarum\Console\AbstractCommand; use Flarum\Console\AbstractCommand;
use Flarum\Foundation\Paths; use Flarum\Foundation\Paths;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use Illuminate\Database\MySqlConnection;
class GenerateDumpCommand extends AbstractCommand class GenerateDumpCommand extends AbstractCommand
{ {
@ -53,7 +54,7 @@ class GenerateDumpCommand extends AbstractCommand
protected function fire() protected function fire()
{ {
$dumpPath = __DIR__.'/../../../migrations/install.dump'; $dumpPath = __DIR__.'/../../../migrations/install.dump';
/** @var Connection */ /** @var Connection&MySqlConnection */
$connection = resolve('db.connection'); $connection = resolve('db.connection');
$connection $connection

View File

@ -30,8 +30,8 @@ class DatabaseMigrationRepository implements MigrationRepositoryInterface
/** /**
* Create a new database migration repository instance. * Create a new database migration repository instance.
* *
* @param \Illuminate\Database\ConnectionInterface $connection * @param ConnectionInterface $connection
* @param string $table * @param string $table
*/ */
public function __construct(ConnectionInterface $connection, $table) public function __construct(ConnectionInterface $connection, $table)
{ {

View File

@ -10,6 +10,7 @@
namespace Flarum\Database; namespace Flarum\Database;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Container\Container as ContainerImplementation;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Capsule\Manager; use Illuminate\Database\Capsule\Manager;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
@ -22,7 +23,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/ */
public function register() public function register()
{ {
$this->container->singleton(Manager::class, function (Container $container) { $this->container->singleton(Manager::class, function (ContainerImplementation $container) {
$manager = new Manager($container); $manager = new Manager($container);
$config = $container['flarum']->config('database'); $config = $container['flarum']->config('database');

View File

@ -39,20 +39,17 @@ class Migrator
/** /**
* The output interface implementation. * The output interface implementation.
* *
* @var OutputInterface * @var OutputInterface|null
*/ */
protected $output; protected $output;
/** /**
* @var ConnectionInterface|MySqlConnection * @var ConnectionInterface
*/ */
protected $connection; protected $connection;
/** /**
* Create a new migrator instance. * Create a new migrator instance.
*
* @param MigrationRepositoryInterface $repository
* @param ConnectionInterface $connection
* @param Filesystem $files
*/ */
public function __construct( public function __construct(
MigrationRepositoryInterface $repository, MigrationRepositoryInterface $repository,
@ -76,7 +73,7 @@ class Migrator
* Run the outstanding migrations at a given path. * Run the outstanding migrations at a given path.
* *
* @param string $path * @param string $path
* @param Extension $extension * @param Extension|null $extension
* @return void * @return void
*/ */
public function run($path, Extension $extension = null) public function run($path, Extension $extension = null)
@ -95,7 +92,7 @@ class Migrator
* *
* @param string $path * @param string $path
* @param array $migrations * @param array $migrations
* @param Extension $extension * @param Extension|null $extension
* @return void * @return void
*/ */
public function runMigrationList($path, $migrations, Extension $extension = null) public function runMigrationList($path, $migrations, Extension $extension = null)
@ -122,8 +119,7 @@ class Migrator
* *
* @param string $path * @param string $path
* @param string $file * @param string $file
* @param string $path * @param Extension|null $extension
* @param Extension $extension
* @return void * @return void
*/ */
protected function runUp($path, $file, Extension $extension = null) protected function runUp($path, $file, Extension $extension = null)
@ -144,7 +140,7 @@ class Migrator
* Rolls all of the currently applied migrations back. * Rolls all of the currently applied migrations back.
* *
* @param string $path * @param string $path
* @param Extension $extension * @param Extension|null $extension
* @return int * @return int
*/ */
public function reset($path, Extension $extension = null) public function reset($path, Extension $extension = null)
@ -245,6 +241,8 @@ class Migrator
if ($this->files->exists($migration)) { if ($this->files->exists($migration)) {
return $this->files->getRequire($migration); return $this->files->getRequire($migration);
} }
return [];
} }
/** /**

View File

@ -13,7 +13,6 @@ use Flarum\Discussion\Discussion;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Access\AbstractPolicy; use Flarum\User\Access\AbstractPolicy;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Contracts\Events\Dispatcher;
class DiscussionPolicy extends AbstractPolicy class DiscussionPolicy extends AbstractPolicy
{ {
@ -22,10 +21,6 @@ class DiscussionPolicy extends AbstractPolicy
*/ */
protected $settings; protected $settings;
/**
* @param SettingsRepositoryInterface $settings
* @param Dispatcher $events
*/
public function __construct(SettingsRepositoryInterface $settings) public function __construct(SettingsRepositoryInterface $settings)
{ {
$this->settings = $settings; $this->settings = $settings;
@ -34,7 +29,7 @@ class DiscussionPolicy extends AbstractPolicy
/** /**
* @param User $actor * @param User $actor
* @param string $ability * @param string $ability
* @return bool|null * @return string|void
*/ */
public function can(User $actor, $ability) public function can(User $actor, $ability)
{ {

View File

@ -23,6 +23,7 @@ use Flarum\Post\MergeableInterface;
use Flarum\Post\Post; use Flarum\Post\Post;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
@ -83,7 +84,7 @@ class Discussion extends AbstractModel
/** /**
* The user for which the state relationship should be loaded. * The user for which the state relationship should be loaded.
* *
* @var User * @var User|null
*/ */
protected static $stateUser; protected static $stateUser;
@ -224,8 +225,8 @@ class Discussion extends AbstractModel
*/ */
public function refreshLastPost() public function refreshLastPost()
{ {
/** @var Post $lastPost */
if ($lastPost = $this->comments()->latest()->first()) { if ($lastPost = $this->comments()->latest()->first()) {
/** @var Post $lastPost */
$this->setLastPost($lastPost); $this->setLastPost($lastPost);
} }
@ -264,8 +265,9 @@ class Discussion extends AbstractModel
* DiscussionRenamedPost, and delete if the title has been reverted * DiscussionRenamedPost, and delete if the title has been reverted
* completely.) * completely.)
* *
* @param \Flarum\Post\MergeableInterface $post The post to save. * @template T of \Flarum\Post\MergeableInterface
* @return Post The resulting post. It may or may not be the same post as * @param T $post The post to save.
* @return T The resulting post. It may or may not be the same post as
* was originally intended to be saved. It also may not exist, if the * was originally intended to be saved. It also may not exist, if the
* merge logic resulted in deletion. * merge logic resulted in deletion.
*/ */
@ -301,7 +303,7 @@ class Discussion extends AbstractModel
/** /**
* Define the relationship with the discussion's publicly-visible comments. * Define the relationship with the discussion's publicly-visible comments.
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return \Illuminate\Database\Eloquent\Relations\HasMany<Post>
*/ */
public function comments() public function comments()
{ {
@ -394,12 +396,12 @@ class Discussion extends AbstractModel
* If no user is passed (i.e. in the case of eager loading the 'state' * If no user is passed (i.e. in the case of eager loading the 'state'
* relation), then the static `$stateUser` property is used. * relation), then the static `$stateUser` property is used.
* *
* @see Discussion::setStateUser()
*
* @param User|null $user * @param User|null $user
* @return \Illuminate\Database\Eloquent\Relations\HasOne * @return HasOne
*
* @see Discussion::setStateUser()
*/ */
public function state(User $user = null) public function state(User $user = null): HasOne
{ {
$user = $user ?: static::$stateUser; $user = $user ?: static::$stateUser;
@ -409,12 +411,10 @@ class Discussion extends AbstractModel
/** /**
* Get the state model for a user, or instantiate a new one if it does not * Get the state model for a user, or instantiate a new one if it does not
* exist. * exist.
*
* @param User $user
* @return \Flarum\Discussion\UserState
*/ */
public function stateFor(User $user) public function stateFor(User $user): UserState
{ {
/** @var UserState|null $state */
$state = $this->state($user)->first(); $state = $this->state($user)->first();
if (! $state) { if (! $state) {
@ -428,8 +428,6 @@ class Discussion extends AbstractModel
/** /**
* Set the user for which the state relationship should be loaded. * Set the user for which the state relationship should be loaded.
*
* @param User $user
*/ */
public static function setStateUser(User $user) public static function setStateUser(User $user)
{ {
@ -440,11 +438,8 @@ class Discussion extends AbstractModel
* Set the discussion title. * Set the discussion title.
* *
* This automatically creates a matching slug for the discussion. * This automatically creates a matching slug for the discussion.
*
* @todo slug should be set by the slugger, drop slug column entirely?
* @param string $title
*/ */
protected function setTitleAttribute($title) protected function setTitleAttribute(string $title)
{ {
$this->attributes['title'] = $title; $this->attributes['title'] = $title;
$this->slug = Str::slug( $this->slug = Str::slug(

View File

@ -17,7 +17,7 @@ class DiscussionRepository
/** /**
* Get a new query builder for the discussions table. * Get a new query builder for the discussions table.
* *
* @return Builder * @return Builder<Discussion>
*/ */
public function query() public function query()
{ {
@ -28,13 +28,13 @@ class DiscussionRepository
* Find a discussion by ID, optionally making sure it is visible to a * Find a discussion by ID, optionally making sure it is visible to a
* certain user, or throw an exception. * certain user, or throw an exception.
* *
* @param int $id * @param int|string $id
* @param User $user * @param User|null $user
* @return \Flarum\Discussion\Discussion * @return \Flarum\Discussion\Discussion
*/ */
public function findOrFail($id, User $user = null) public function findOrFail($id, User $user = null)
{ {
$query = Discussion::where('id', $id); $query = $this->query()->where('id', $id);
return $this->scopeVisibleTo($query, $user)->firstOrFail(); return $this->scopeVisibleTo($query, $user)->firstOrFail();
} }
@ -42,26 +42,25 @@ class DiscussionRepository
/** /**
* Get the IDs of discussions which a user has read completely. * Get the IDs of discussions which a user has read completely.
* *
* @deprecated 1.3 Use `getReadIdsQuery` instead
*
* @param User $user * @param User $user
* @return array * @return \Illuminate\Database\Eloquent\Collection<Discussion>
* @deprecated 1.3 Use `getReadIdsQuery` instead
*/ */
public function getReadIds(User $user) public function getReadIds(User $user)
{ {
return $this->getReadIdsQuery($user) return $this->getReadIdsQuery($user)->get();
->all();
} }
/** /**
* Get a query containing the IDs of discussions which a user has read completely. * Get a query containing the IDs of discussions which a user has read completely.
* *
* @param User $user * @param User $user
* @return Builder * @return Builder<Discussion>
*/ */
public function getReadIdsQuery(User $user): Builder public function getReadIdsQuery(User $user): Builder
{ {
return Discussion::leftJoin('discussion_user', 'discussion_user.discussion_id', '=', 'discussions.id') return $this->query()
->leftJoin('discussion_user', 'discussion_user.discussion_id', '=', 'discussions.id')
->where('discussion_user.user_id', $user->id) ->where('discussion_user.user_id', $user->id)
->whereColumn('last_read_post_number', '>=', 'last_post_number') ->whereColumn('last_read_post_number', '>=', 'last_post_number')
->select('id'); ->select('id');
@ -70,9 +69,9 @@ class DiscussionRepository
/** /**
* Scope a query to only include records that are visible to a user. * Scope a query to only include records that are visible to a user.
* *
* @param Builder $query * @param Builder<Discussion> $query
* @param User $user * @param User|null $user
* @return Builder * @return Builder<Discussion>
*/ */
protected function scopeVisibleTo(Builder $query, User $user = null) protected function scopeVisibleTo(Builder $query, User $user = null)
{ {

View File

@ -13,6 +13,9 @@ use Flarum\Database\AbstractModel;
use Flarum\Http\SlugDriverInterface; use Flarum\Http\SlugDriverInterface;
use Flarum\User\User; use Flarum\User\User;
/**
* @implements SlugDriverInterface<Discussion>
*/
class IdWithTransliteratedSlugDriver implements SlugDriverInterface class IdWithTransliteratedSlugDriver implements SlugDriverInterface
{ {
/** /**
@ -25,6 +28,9 @@ class IdWithTransliteratedSlugDriver implements SlugDriverInterface
$this->discussions = $discussions; $this->discussions = $discussions;
} }
/**
* @param Discussion $instance
*/
public function toSlug(AbstractModel $instance): string public function toSlug(AbstractModel $instance): string
{ {
return $instance->id.(trim($instance->slug) ? '-'.$instance->slug : ''); return $instance->id.(trim($instance->slug) ? '-'.$instance->slug : '');

View File

@ -65,5 +65,7 @@ class FulltextGambit implements GambitInterface
$query->orderByRaw('MATCH('.$grammar->wrap('discussions.title').') AGAINST (?) desc', [$bit]); $query->orderByRaw('MATCH('.$grammar->wrap('discussions.title').') AGAINST (?) desc', [$bit]);
$query->orderBy('posts_ft.score', 'desc'); $query->orderBy('posts_ft.score', 'desc');
}); });
return true;
} }
} }

View File

@ -13,6 +13,9 @@ use Flarum\Database\AbstractModel;
use Flarum\Http\SlugDriverInterface; use Flarum\Http\SlugDriverInterface;
use Flarum\User\User; use Flarum\User\User;
/**
* @implements SlugDriverInterface<Discussion>
*/
class Utf8SlugDriver implements SlugDriverInterface class Utf8SlugDriver implements SlugDriverInterface
{ {
/** /**
@ -25,6 +28,10 @@ class Utf8SlugDriver implements SlugDriverInterface
$this->discussions = $discussions; $this->discussions = $discussions;
} }
/**
* @param Discussion $instance
* @return string
*/
public function toSlug(AbstractModel $instance): string public function toSlug(AbstractModel $instance): string
{ {
$slug = preg_replace('/[-\s]+/u', '-', $instance->title); $slug = preg_replace('/[-\s]+/u', '-', $instance->title);
@ -34,6 +41,11 @@ class Utf8SlugDriver implements SlugDriverInterface
return $instance->id.(trim($slug) ? '-'.$slug : ''); return $instance->id.(trim($slug) ? '-'.$slug : '');
} }
/**
* @param string $slug
* @param User $actor
* @return Discussion
*/
public function fromSlug(string $slug, User $actor): AbstractModel public function fromSlug(string $slug, User $actor): AbstractModel
{ {
if (strpos($slug, '-')) { if (strpos($slug, '-')) {

View File

@ -76,7 +76,7 @@ class ErrorHandling implements ExtenderInterface
* contain "details" - arbitrary data with more context for to the error. * contain "details" - arbitrary data with more context for to the error.
* *
* @param string $exceptionClass: The ::class attribute of the exception class. * @param string $exceptionClass: The ::class attribute of the exception class.
* @param string $errorType: The ::class attribute of the handler class. * @param string $handlerClass: The ::class attribute of the handler class.
* @return self * @return self
*/ */
public function handler(string $exceptionClass, string $handlerClass): self public function handler(string $exceptionClass, string $handlerClass): self

View File

@ -110,7 +110,7 @@ class Frontend implements ExtenderInterface
/** /**
* Modify the content of the frontend. * Modify the content of the frontend.
* *
* @param callable|string|null $content * @param callable|string|null $callback
* *
* The content can be a closure or an invokable class, and should accept: * The content can be a closure or an invokable class, and should accept:
* - \Flarum\Frontend\Document $document * - \Flarum\Frontend\Document $document

View File

@ -31,7 +31,7 @@ class LanguagePack implements ExtenderInterface, LifecycleInterface
/** /**
* LanguagePack constructor. * LanguagePack constructor.
* *
* @param string|null $path: Path to yaml language files. * @param string $path: Path to yaml language files.
*/ */
public function __construct(string $path = '/locale') public function __construct(string $path = '/locale')
{ {
@ -115,7 +115,7 @@ class LanguagePack implements ExtenderInterface, LifecycleInterface
return true; return true;
} }
/** @var ExtensionManager $extensions */ /** @var ExtensionManager|null $extensions */
static $extensions; static $extensions;
$extensions = $extensions ?? $container->make(ExtensionManager::class); $extensions = $extensions ?? $container->make(ExtensionManager::class);

View File

@ -48,7 +48,7 @@ class Middleware implements ExtenderInterface
* *
* @param string $originalMiddleware: ::class attribute of the original middleware class. * @param string $originalMiddleware: ::class attribute of the original middleware class.
* Or container binding name. * Or container binding name.
* @param string $middleware: ::class attribute of the middleware class. * @param string $newMiddleware: ::class attribute of the middleware class.
* Must implement \Psr\Http\Server\MiddlewareInterface. * Must implement \Psr\Http\Server\MiddlewareInterface.
* @return self * @return self
*/ */
@ -77,7 +77,7 @@ class Middleware implements ExtenderInterface
* *
* @param string $originalMiddleware: ::class attribute of the original middleware class. * @param string $originalMiddleware: ::class attribute of the original middleware class.
* Or container binding name. * Or container binding name.
* @param string $middleware: ::class attribute of the middleware class. * @param string $newMiddleware: ::class attribute of the middleware class.
* Must implement \Psr\Http\Server\MiddlewareInterface. * Must implement \Psr\Http\Server\MiddlewareInterface.
* @return self * @return self
*/ */
@ -93,7 +93,7 @@ class Middleware implements ExtenderInterface
* *
* @param string $originalMiddleware: ::class attribute of the original middleware class. * @param string $originalMiddleware: ::class attribute of the original middleware class.
* Or container binding name. * Or container binding name.
* @param string $middleware: ::class attribute of the middleware class. * @param string $newMiddleware: ::class attribute of the middleware class.
* Must implement \Psr\Http\Server\MiddlewareInterface. * Must implement \Psr\Http\Server\MiddlewareInterface.
* @return self * @return self
*/ */

View File

@ -13,6 +13,7 @@ use Flarum\Extension\Extension;
use Flarum\Foundation\Paths; use Flarum\Foundation\Paths;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\View\Factory as FactoryImplementation;
/** /**
* Views are PHP files that use the Laravel Blade syntax for creation of server-side generated HTML. * Views are PHP files that use the Laravel Blade syntax for creation of server-side generated HTML.
@ -65,7 +66,7 @@ class View implements ExtenderInterface, LifecycleInterface
public function extend(Container $container, Extension $extension = null) public function extend(Container $container, Extension $extension = null)
{ {
$container->resolving(Factory::class, function (Factory $view) { $container->resolving(Factory::class, function (FactoryImplementation $view) {
foreach ($this->namespaces as $namespace => $hints) { foreach ($this->namespaces as $namespace => $hints) {
$view->addNamespace($namespace, $hints); $view->addNamespace($namespace, $hints);
} }

View File

@ -25,6 +25,6 @@ class ExtensionBootError extends Exception
$extenderClass = get_class($extender); $extenderClass = get_class($extender);
parent::__construct("Experienced an error while booting extension: {$extension->getTitle()}.\n\nError occurred while applying an extender of type: $extenderClass.", null, $previous); parent::__construct("Experienced an error while booting extension: {$extension->getTitle()}.\n\nError occurred while applying an extender of type: $extenderClass.", 0, $previous);
} }
} }

View File

@ -214,9 +214,6 @@ class Extension implements Arrayable
* @param array $extensionSet: An associative array where keys are the composer package names * @param array $extensionSet: An associative array where keys are the composer package names
* of installed extensions. Used to figure out which dependencies * of installed extensions. Used to figure out which dependencies
* are flarum extensions. * are flarum extensions.
* @param array $enabledIds: An associative array where keys are the composer package names
* of enabled extensions. Used to figure out optional dependencies.
*
* @internal * @internal
*/ */
public function calculateDependencies($extensionSet) public function calculateDependencies($extensionSet)
@ -489,6 +486,7 @@ class Extension implements Arrayable
} }
/** /**
* @return int|void
* @internal * @internal
*/ */
public function migrate(Migrator $migrator, $direction = 'up') public function migrate(Migrator $migrator, $direction = 'up')
@ -498,7 +496,7 @@ class Extension implements Arrayable
} }
if ($direction == 'up') { if ($direction == 'up') {
return $migrator->run($this->getPath().'/migrations', $this); $migrator->run($this->getPath().'/migrations', $this);
} else { } else {
return $migrator->reset($this->getPath().'/migrations', $this); return $migrator->reset($this->getPath().'/migrations', $this);
} }

View File

@ -316,7 +316,7 @@ class ExtensionManager
* *
* @param Extension $extension * @param Extension $extension
* @param string $direction * @param string $direction
* @return void * @return int
* *
* @internal * @internal
*/ */
@ -326,20 +326,20 @@ class ExtensionManager
return $container->make(ConnectionInterface::class)->getSchemaBuilder(); return $container->make(ConnectionInterface::class)->getSchemaBuilder();
}); });
$extension->migrate($this->migrator, $direction); return $extension->migrate($this->migrator, $direction);
} }
/** /**
* Runs the database migrations to reset the database to its old state. * Runs the database migrations to reset the database to its old state.
* *
* @param Extension $extension * @param Extension $extension
* @return array Notes from the migrator. * @return void
* *
* @internal * @internal
*/ */
public function migrateDown(Extension $extension) public function migrateDown(Extension $extension)
{ {
return $this->migrate($extension, 'down'); $this->migrate($extension, 'down');
} }
/** /**

View File

@ -39,7 +39,7 @@ abstract class AbstractFilterer
/** /**
* @param QueryCriteria $criteria * @param QueryCriteria $criteria
* @param mixed|null $limit * @param int|null $limit
* @param int $offset * @param int $offset
* *
* @return QueryResults * @return QueryResults

View File

@ -18,9 +18,6 @@ interface FilterInterface
/** /**
* Filters a query. * Filters a query.
*
* @param FilterState $filter
* @param string $value The value of the requested filter
*/ */
public function filter(FilterState $filterState, string $filterValue, bool $negate); public function filter(FilterState $filterState, string $filterValue, bool $negate);
} }

View File

@ -9,8 +9,6 @@
namespace Flarum\Formatter; namespace Flarum\Formatter;
use DOMDocument;
use DOMElement;
use Illuminate\Contracts\Cache\Repository; use Illuminate\Contracts\Cache\Repository;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use s9e\TextFormatter\Configurator; use s9e\TextFormatter\Configurator;
@ -152,8 +150,8 @@ class Formatter
$configurator->rootRules->enableAutoLineBreaks(); $configurator->rootRules->enableAutoLineBreaks();
$configurator->rendering->engine = 'PHP'; $configurator->rendering->setEngine('PHP');
$configurator->rendering->engine->cacheDir = $this->cacheDir; $configurator->rendering->getEngine()->cacheDir = $this->cacheDir; // @phpstan-ignore-line
$configurator->enableJavaScript(); $configurator->enableJavaScript();
$configurator->javascript->exports = ['preview']; $configurator->javascript->exports = ['preview'];
@ -161,9 +159,9 @@ class Formatter
$configurator->javascript->setMinifier('MatthiasMullieMinify') $configurator->javascript->setMinifier('MatthiasMullieMinify')
->keepGoing = true; ->keepGoing = true;
$configurator->Escaper; $configurator->Escaper; /** @phpstan-ignore-line */
$configurator->Autoemail; $configurator->Autoemail; /** @phpstan-ignore-line */
$configurator->Autolink; $configurator->Autolink; /** @phpstan-ignore-line */
$configurator->tags->onDuplicate('replace'); $configurator->tags->onDuplicate('replace');
foreach ($this->configurationCallbacks as $callback) { foreach ($this->configurationCallbacks as $callback) {
@ -180,11 +178,13 @@ class Formatter
*/ */
protected function configureExternalLinks(Configurator $configurator) protected function configureExternalLinks(Configurator $configurator)
{ {
/** @var DOMDocument $dom */ /**
* @var Configurator\Items\TemplateDocument $dom
*/
$dom = $configurator->tags['URL']->template->asDOM(); $dom = $configurator->tags['URL']->template->asDOM();
/** @var DOMElement $a */
foreach ($dom->getElementsByTagName('a') as $a) { foreach ($dom->getElementsByTagName('a') as $a) {
/** @var \s9e\SweetDOM\Element $a */
$a->prependXslCopyOf('@target'); $a->prependXslCopyOf('@target');
$a->prependXslCopyOf('@rel'); $a->prependXslCopyOf('@rel');
} }

View File

@ -140,10 +140,10 @@ class Application
* Use container inside flarum instead. * Use container inside flarum instead.
*/ */
$this->container->instance('app', $this->container); $this->container->instance('app', $this->container);
$this->container->alias('app', \Illluminate\Container\Container::class); $this->container->alias('app', \Illuminate\Container\Container::class);
$this->container->instance('container', $this->container); $this->container->instance('container', $this->container);
$this->container->alias('container', \Illluminate\Container\Container::class); $this->container->alias('container', \Illuminate\Container\Container::class);
$this->container->instance('flarum', $this); $this->container->instance('flarum', $this);
$this->container->alias('flarum', self::class); $this->container->alias('flarum', self::class);
@ -170,7 +170,7 @@ class Application
*/ */
public function register($provider, $options = [], $force = false) public function register($provider, $options = [], $force = false)
{ {
if ($registered = $this->getProvider($provider) && ! $force) { if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered; return $registered;
} }

View File

@ -42,6 +42,7 @@ class InfoCommand extends AbstractCommand
* @var ConnectionInterface * @var ConnectionInterface
*/ */
protected $db; protected $db;
/** /**
* @var Queue * @var Queue
*/ */

View File

@ -104,7 +104,7 @@ class InstalledSite implements SiteInterface
$container->instance('flarum.config', $this->config); $container->instance('flarum.config', $this->config);
$container->alias('flarum.config', Config::class); $container->alias('flarum.config', Config::class);
$container->instance('flarum.debug', $this->config->inDebugMode()); $container->instance('flarum.debug', $this->config->inDebugMode());
$container->instance('config', $config = $this->getIlluminateConfig($laravel)); $container->instance('config', $config = $this->getIlluminateConfig());
$container->instance('flarum.maintenance.handler', new MaintenanceModeHandler); $container->instance('flarum.maintenance.handler', new MaintenanceModeHandler);
$this->registerLogger($container); $this->registerLogger($container);

View File

@ -12,10 +12,10 @@ namespace Flarum\Foundation;
use InvalidArgumentException; use InvalidArgumentException;
/** /**
* @property-read string base * @property-read string $base
* @property-read string public * @property-read string $public
* @property-read string storage * @property-read string $storage
* @property-read string vendor * @property-read string $vendor
*/ */
class Paths class Paths
{ {

View File

@ -13,7 +13,7 @@ use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Compiler\JsCompiler; use Flarum\Frontend\Compiler\JsCompiler;
use Flarum\Frontend\Compiler\LessCompiler; use Flarum\Frontend\Compiler\LessCompiler;
use Flarum\Frontend\Compiler\Source\SourceCollector; use Flarum\Frontend\Compiler\Source\SourceCollector;
use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Cloud;
/** /**
* A factory class for creating frontend asset compilers. * A factory class for creating frontend asset compilers.
@ -38,7 +38,7 @@ class Assets
protected $name; protected $name;
/** /**
* @var Filesystem * @var Cloud
*/ */
protected $assetsDir; protected $assetsDir;
@ -67,7 +67,7 @@ class Assets
*/ */
protected $customFunctions = []; protected $customFunctions = [];
public function __construct(string $name, Filesystem $assetsDir, string $cacheDir = null, array $lessImportDirs = null, array $customFunctions = []) public function __construct(string $name, Cloud $assetsDir, string $cacheDir = null, array $lessImportDirs = null, array $customFunctions = [])
{ {
$this->name = $name; $this->name = $name;
$this->assetsDir = $assetsDir; $this->assetsDir = $assetsDir;
@ -200,12 +200,12 @@ class Assets
$this->name = $name; $this->name = $name;
} }
public function getAssetsDir(): Filesystem public function getAssetsDir(): Cloud
{ {
return $this->assetsDir; return $this->assetsDir;
} }
public function setAssetsDir(Filesystem $assetsDir) public function setAssetsDir(Cloud $assetsDir)
{ {
$this->assetsDir = $assetsDir; $this->assetsDir = $assetsDir;
} }

View File

@ -27,8 +27,8 @@ class FileVersioner implements VersionerInterface
public function putRevision(string $file, ?string $revision) public function putRevision(string $file, ?string $revision)
{ {
if ($this->filesystem->has(static::REV_MANIFEST)) { if ($this->filesystem->exists(static::REV_MANIFEST)) {
$manifest = json_decode($this->filesystem->read(static::REV_MANIFEST), true); $manifest = json_decode($this->filesystem->get(static::REV_MANIFEST), true);
} else { } else {
$manifest = []; $manifest = [];
} }
@ -44,8 +44,8 @@ class FileVersioner implements VersionerInterface
public function getRevision(string $file): ?string public function getRevision(string $file): ?string
{ {
if ($this->filesystem->has(static::REV_MANIFEST)) { if ($this->filesystem->exists(static::REV_MANIFEST)) {
$manifest = json_decode($this->filesystem->read(static::REV_MANIFEST), true); $manifest = json_decode($this->filesystem->get(static::REV_MANIFEST), true);
return Arr::get($manifest, $file); return Arr::get($manifest, $file);
} }

View File

@ -72,7 +72,7 @@ class JsCompiler extends RevisionCompiler
{ {
parent::delete($file); parent::delete($file);
if ($this->assetsDir->has($mapFile = $file.'.map')) { if ($this->assetsDir->exists($mapFile = $file.'.map')) {
$this->assetsDir->delete($mapFile); $this->assetsDir->delete($mapFile);
} }
} }

View File

@ -35,12 +35,12 @@ class LessCompiler extends RevisionCompiler
protected $customFunctions = []; protected $customFunctions = [];
/** /**
* @var Collection * @var Collection|null
*/ */
protected $lessImportOverrides; protected $lessImportOverrides;
/** /**
* @var Collection * @var Collection|null
*/ */
protected $fileSourceOverrides; protected $fileSourceOverrides;
@ -88,7 +88,7 @@ class LessCompiler extends RevisionCompiler
return ''; return '';
} }
ini_set('xdebug.max_nesting_level', 200); ini_set('xdebug.max_nesting_level', '200');
$parser = new Less_Parser([ $parser = new Less_Parser([
'compress' => true, 'compress' => true,

View File

@ -11,7 +11,7 @@ namespace Flarum\Frontend\Compiler;
use Flarum\Frontend\Compiler\Source\SourceCollector; use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\Compiler\Source\SourceInterface; use Flarum\Frontend\Compiler\Source\SourceInterface;
use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Cloud;
/** /**
* @internal * @internal
@ -21,9 +21,10 @@ class RevisionCompiler implements CompilerInterface
const EMPTY_REVISION = 'empty'; const EMPTY_REVISION = 'empty';
/** /**
* @var Filesystem * @var Cloud
*/ */
protected $assetsDir; protected $assetsDir;
/** /**
* @var VersionerInterface * @var VersionerInterface
*/ */
@ -40,11 +41,11 @@ class RevisionCompiler implements CompilerInterface
protected $sourcesCallbacks = []; protected $sourcesCallbacks = [];
/** /**
* @param Filesystem $assetsDir * @param Cloud $assetsDir
* @param string $filename * @param string $filename
* @param VersionerInterface|null $versioner @deprecated nullable will be removed at v2.0 * @param VersionerInterface|null $versioner @deprecated nullable will be removed at v2.0
*/ */
public function __construct(Filesystem $assetsDir, string $filename, VersionerInterface $versioner = null) public function __construct(Cloud $assetsDir, string $filename, VersionerInterface $versioner = null)
{ {
$this->assetsDir = $assetsDir; $this->assetsDir = $assetsDir;
$this->filename = $filename; $this->filename = $filename;
@ -71,7 +72,7 @@ class RevisionCompiler implements CompilerInterface
// In case the previous and current revisions do not match // In case the previous and current revisions do not match
// Or no file was written yet, let's save the file to disk. // Or no file was written yet, let's save the file to disk.
if ($force || $oldRevision !== $newRevision || ! $this->assetsDir->has($this->filename)) { if ($force || $oldRevision !== $newRevision || ! $this->assetsDir->exists($this->filename)) {
if (! $this->save($this->filename, $sources)) { if (! $this->save($this->filename, $sources)) {
// If no file was written (because the sources were empty), we // If no file was written (because the sources were empty), we
// will set the revision to a special value so that we can tell // will set the revision to a special value so that we can tell
@ -145,7 +146,6 @@ class RevisionCompiler implements CompilerInterface
/** /**
* @param SourceInterface[] $sources * @param SourceInterface[] $sources
* @return string
*/ */
protected function compile(array $sources): string protected function compile(array $sources): string
{ {
@ -158,10 +158,6 @@ class RevisionCompiler implements CompilerInterface
return $output; return $output;
} }
/**
* @param string $string
* @return string
*/
protected function format(string $string): string protected function format(string $string): string
{ {
return $string; return $string;
@ -169,7 +165,6 @@ class RevisionCompiler implements CompilerInterface
/** /**
* @param SourceInterface[] $sources * @param SourceInterface[] $sources
* @return string
*/ */
protected function calculateRevision(array $sources): string protected function calculateRevision(array $sources): string
{ {
@ -196,12 +191,9 @@ class RevisionCompiler implements CompilerInterface
} }
} }
/**
* @param string $file
*/
protected function delete(string $file) protected function delete(string $file)
{ {
if ($this->assetsDir->has($file)) { if ($this->assetsDir->exists($file)) {
$this->assetsDir->delete($file); $this->assetsDir->delete($file);
} }
} }

View File

@ -221,14 +221,13 @@ class Document implements Renderable
return resolve(TitleDriverInterface::class)->makeTitle($this, $this->request, $this->forumApiDocument); return resolve(TitleDriverInterface::class)->makeTitle($this, $this->request, $this->forumApiDocument);
} }
/** protected function makeLayout(): ?View
* @return View
*/
protected function makeLayout(): View
{ {
if ($this->layoutView) { if ($this->layoutView) {
return $this->view->make($this->layoutView)->with('content', $this->makeContent()); return $this->view->make($this->layoutView)->with('content', $this->makeContent());
} }
return null;
} }
/** /**

View File

@ -17,7 +17,7 @@ class GroupRepository
/** /**
* Get a new query builder for the groups table. * Get a new query builder for the groups table.
* *
* @return Builder * @return Builder<Group>
*/ */
public function query() public function query()
{ {
@ -29,14 +29,14 @@ class GroupRepository
* user, or throw an exception. * user, or throw an exception.
* *
* @param int $id * @param int $id
* @param User $actor * @param User|null $actor
* @return \Flarum\Group\Group * @return Group
* *
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/ */
public function findOrFail($id, User $actor = null) public function findOrFail($id, User $actor = null)
{ {
$query = Group::where('id', $id); $query = $this->query()->where('id', $id);
return $this->scopeVisibleTo($query, $actor)->firstOrFail(); return $this->scopeVisibleTo($query, $actor)->firstOrFail();
} }
@ -44,9 +44,9 @@ class GroupRepository
/** /**
* Scope a query to only include records that are visible to a user. * Scope a query to only include records that are visible to a user.
* *
* @param Builder $query * @param Builder<Group> $query
* @param User $actor * @param User|null $actor
* @return Builder * @return Builder<Group>
*/ */
protected function scopeVisibleTo(Builder $query, User $actor = null) protected function scopeVisibleTo(Builder $query, User $actor = null)
{ {

View File

@ -25,8 +25,8 @@ use Psr\Http\Message\ServerRequestInterface;
* @property Carbon|null $last_activity_at * @property Carbon|null $last_activity_at
* @property string $type * @property string $type
* @property string $title * @property string $title
* @property string $last_ip_address * @property string|null $last_ip_address
* @property string $last_user_agent * @property string|null $last_user_agent
* @property \Flarum\User\User|null $user * @property \Flarum\User\User|null $user
*/ */
class AccessToken extends AbstractModel class AccessToken extends AbstractModel

View File

@ -46,7 +46,7 @@ class CookieFactory
/** /**
* Same Site cookie value. * Same Site cookie value.
* *
* @var string * @var string|null
*/ */
protected $samesite; protected $samesite;

View File

@ -26,7 +26,7 @@ class ResolveRoute implements Middleware
protected $routes; protected $routes;
/** /**
* @var Dispatcher * @var Dispatcher|null
*/ */
protected $dispatcher; protected $dispatcher;
@ -43,6 +43,8 @@ class ResolveRoute implements Middleware
/** /**
* Resolve the given request from our route collection. * Resolve the given request from our route collection.
* *
* @return Response
*
* @throws MethodNotAllowedException * @throws MethodNotAllowedException
* @throws RouteNotFoundException * @throws RouteNotFoundException
*/ */
@ -58,7 +60,7 @@ class ResolveRoute implements Middleware
throw new RouteNotFoundException($uri); throw new RouteNotFoundException($uri);
case Dispatcher::METHOD_NOT_ALLOWED: case Dispatcher::METHOD_NOT_ALLOWED:
throw new MethodNotAllowedException($method); throw new MethodNotAllowedException($method);
case Dispatcher::FOUND: default:
$request = $request $request = $request
->withAttribute('routeName', $routeInfo[1]['name']) ->withAttribute('routeName', $routeInfo[1]['name'])
->withAttribute('routeHandler', $routeInfo[1]['handler']) ->withAttribute('routeHandler', $routeInfo[1]['handler'])

View File

@ -11,6 +11,7 @@ namespace Flarum\Http;
use Flarum\Foundation\ErrorHandling\LogReporter; use Flarum\Foundation\ErrorHandling\LogReporter;
use Flarum\Foundation\SiteInterface; use Flarum\Foundation\SiteInterface;
use Illuminate\Contracts\Container\Container;
use Laminas\Diactoros\Response; use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\ServerRequestFactory;
@ -50,7 +51,7 @@ class Server
* We catch all exceptions happening during this process and format them to * We catch all exceptions happening during this process and format them to
* prevent exposure of sensitive information. * prevent exposure of sensitive information.
* *
* @return \Psr\Http\Server\RequestHandlerInterface * @return \Psr\Http\Server\RequestHandlerInterface|void
*/ */
private function safelyBootAndGetHandler() private function safelyBootAndGetHandler()
{ {
@ -80,7 +81,9 @@ class Server
*/ */
private function cleanBootExceptionLog(Throwable $error) private function cleanBootExceptionLog(Throwable $error)
{ {
if (app()->has('flarum.config') && app('flarum.config')->inDebugMode()) { $container = resolve(Container::class);
if ($container->has('flarum.config') && resolve('flarum.config')->inDebugMode()) {
// If the application booted far enough for the config to be available, we will check for debug mode // If the application booted far enough for the config to be available, we will check for debug mode
// Since the config is loaded very early, it is very likely to be available from the container // Since the config is loaded very early, it is very likely to be available from the container
$message = $error->getMessage(); $message = $error->getMessage();
@ -96,12 +99,12 @@ class Server
<pre>$error</pre> <pre>$error</pre>
ERROR; ERROR;
exit(1); exit(1);
} elseif (app()->has(LoggerInterface::class)) { } elseif ($container->has(LoggerInterface::class)) {
// If the application booted far enough for the logger to be available, we will log the error there // If the application booted far enough for the logger to be available, we will log the error there
// Considering most boot errors are related to database or extensions, the logger should already be loaded // Considering most boot errors are related to database or extensions, the logger should already be loaded
// We check for LoggerInterface binding because it's a constructor dependency of LogReporter, // We check for LoggerInterface binding because it's a constructor dependency of LogReporter,
// then instantiate LogReporter through the container for automatic dependency injection // then instantiate LogReporter through the container for automatic dependency injection
app(LogReporter::class)->report($error); resolve(LogReporter::class)->report($error);
echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.'; echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.';
exit(1); exit(1);

View File

@ -12,9 +12,18 @@ namespace Flarum\Http;
use Flarum\Database\AbstractModel; use Flarum\Database\AbstractModel;
use Flarum\User\User; use Flarum\User\User;
/**
* @template T of AbstractModel
*/
interface SlugDriverInterface interface SlugDriverInterface
{ {
/**
* @param T $instance
*/
public function toSlug(AbstractModel $instance): string; public function toSlug(AbstractModel $instance): string;
/**
* @return T
*/
public function fromSlug(string $slug, User $actor): AbstractModel; public function fromSlug(string $slug, User $actor): AbstractModel;
} }

View File

@ -20,6 +20,11 @@ class SlugManager
$this->drivers = $drivers; $this->drivers = $drivers;
} }
/**
* @template T of \Flarum\Database\AbstractModel
* @param class-string<T> $resourceName
* @return SlugDriverInterface<T>
*/
public function forResource(string $resourceName): SlugDriverInterface public function forResource(string $resourceName): SlugDriverInterface
{ {
return Arr::get($this->drivers, $resourceName, null); return Arr::get($this->drivers, $resourceName, null);

View File

@ -13,6 +13,7 @@ use Flarum\Console\AbstractCommand;
use Flarum\Install\Installation; use Flarum\Install\Installation;
use Flarum\Install\Pipeline; use Flarum\Install\Pipeline;
use Flarum\Install\Step; use Flarum\Install\Step;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends AbstractCommand class InstallCommand extends AbstractCommand
@ -83,7 +84,9 @@ class InstallCommand extends AbstractCommand
if ($this->input->getOption('file')) { if ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input); $this->dataSource = new FileDataProvider($this->input);
} else { } else {
$this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question')); /** @var QuestionHelper $questionHelper */
$questionHelper = $this->getHelperSet()->get('question');
$this->dataSource = new UserDataProvider($this->input, $this->output, $questionHelper);
} }
} }

View File

@ -26,10 +26,6 @@ class IndexController extends AbstractHtmlController
*/ */
protected $installation; protected $installation;
/**
* @param Factory $view
* @param Installation $installation
*/
public function __construct(Factory $view, Installation $installation) public function __construct(Factory $view, Installation $installation)
{ {
$this->view = $view; $this->view = $view;
@ -37,7 +33,6 @@ class IndexController extends AbstractHtmlController
} }
/** /**
* @param Request $request
* @return \Illuminate\Contracts\Support\Renderable * @return \Illuminate\Contracts\Support\Renderable
*/ */
public function render(Request $request) public function render(Request $request)

View File

@ -9,7 +9,7 @@
namespace Flarum\Install; namespace Flarum\Install;
interface ReversibleStep interface ReversibleStep extends Step
{ {
public function revert(); public function revert();
} }

View File

@ -60,6 +60,11 @@ class EnableBundledExtensions implements Step
*/ */
private $enabledExtensions; private $enabledExtensions;
/**
* @var Migrator|null
*/
private $migrator;
public function __construct(ConnectionInterface $database, $vendorPath, $assetPath, $enabledExtensions = null) public function __construct(ConnectionInterface $database, $vendorPath, $assetPath, $enabledExtensions = null)
{ {
$this->database = $database; $this->database = $database;
@ -124,7 +129,7 @@ class EnableBundledExtensions implements Step
}); });
} }
private function getMigrator() private function getMigrator(): Migrator
{ {
return $this->migrator = $this->migrator ?? new Migrator( return $this->migrator = $this->migrator ?? new Migrator(
new DatabaseMigrationRepository($this->database, 'migrations'), new DatabaseMigrationRepository($this->database, 'migrations'),

View File

@ -10,10 +10,9 @@
namespace Flarum\Install\Steps; namespace Flarum\Install\Steps;
use Flarum\Install\ReversibleStep; use Flarum\Install\ReversibleStep;
use Flarum\Install\Step;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
class PublishAssets implements Step, ReversibleStep class PublishAssets implements ReversibleStep
{ {
/** /**
* @var string * @var string

View File

@ -12,9 +12,8 @@ namespace Flarum\Install\Steps;
use Flarum\Install\BaseUrl; use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig; use Flarum\Install\DatabaseConfig;
use Flarum\Install\ReversibleStep; use Flarum\Install\ReversibleStep;
use Flarum\Install\Step;
class StoreConfig implements Step, ReversibleStep class StoreConfig implements ReversibleStep
{ {
private $debugMode; private $debugMode;

View File

@ -41,7 +41,7 @@ class EmailNotificationDriver implements NotificationDriverInterface
/** /**
* Mail a notification to a list of users. * Mail a notification to a list of users.
* *
* @param MailableInterface $blueprint * @param MailableInterface&BlueprintInterface $blueprint
* @param User[] $recipients * @param User[] $recipients
*/ */
protected function mailNotifications(MailableInterface $blueprint, array $recipients) protected function mailNotifications(MailableInterface $blueprint, array $recipients)

View File

@ -26,7 +26,7 @@ class DeletedAll
public function __construct(User $user, DateTime $timestamp) public function __construct(User $user, DateTime $timestamp)
{ {
$this->user = $user; $this->actor = $user;
$this->timestamp = $timestamp; $this->timestamp = $timestamp;
} }
} }

View File

@ -32,7 +32,7 @@ class Read
public function __construct(User $user, Notification $notification, DateTime $timestamp) public function __construct(User $user, Notification $notification, DateTime $timestamp)
{ {
$this->user = $user; $this->actor = $user;
$this->notification = $notification; $this->notification = $notification;
$this->timestamp = $timestamp; $this->timestamp = $timestamp;
} }

View File

@ -26,7 +26,7 @@ class ReadAll
public function __construct(User $user, DateTime $timestamp) public function __construct(User $user, DateTime $timestamp)
{ {
$this->user = $user; $this->actor = $user;
$this->timestamp = $timestamp; $this->timestamp = $timestamp;
} }
} }

View File

@ -41,7 +41,7 @@ use Illuminate\Support\Arr;
* @property \Carbon\Carbon $deleted_at * @property \Carbon\Carbon $deleted_at
* @property \Flarum\User\User|null $user * @property \Flarum\User\User|null $user
* @property \Flarum\User\User|null $fromUser * @property \Flarum\User\User|null $fromUser
* @property \Flarum\Database\AbstractModel|null $subject * @property \Flarum\Database\AbstractModel|\Flarum\Post\Post|\Flarum\Discussion\Discussion|null $subject
*/ */
class Notification extends AbstractModel class Notification extends AbstractModel
{ {
@ -76,7 +76,7 @@ class Notification extends AbstractModel
* When getting the data attribute, unserialize the JSON stored in the * When getting the data attribute, unserialize the JSON stored in the
* database into a plain array. * database into a plain array.
* *
* @param string $value * @param string|null $value
* @return mixed * @return mixed
*/ */
public function getDataAttribute($value) public function getDataAttribute($value)
@ -258,7 +258,7 @@ class Notification extends AbstractModel
static::$subjectModels[$type] = $subjectModel; static::$subjectModels[$type] = $subjectModel;
} }
private static function getBlueprintAttributes(BlueprintInterface $blueprint): array protected static function getBlueprintAttributes(BlueprintInterface $blueprint): array
{ {
return [ return [
'type' => $blueprint::getType(), 'type' => $blueprint::getType(),

View File

@ -12,6 +12,7 @@ namespace Flarum\Notification;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Contracts\Mail\Mailer; use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -23,7 +24,7 @@ class NotificationMailer
protected $mailer; protected $mailer;
/** /**
* @var TranslatorInterface * @var TranslatorInterface&Translator
*/ */
protected $translator; protected $translator;
@ -33,9 +34,7 @@ class NotificationMailer
protected $settings; protected $settings;
/** /**
* @param Mailer $mailer * @param TranslatorInterface&Translator $translator
* @param TranslatorInterface $translator
* @param SettingsRepositoryInterface $settings
*/ */
public function __construct(Mailer $mailer, TranslatorInterface $translator, SettingsRepositoryInterface $settings) public function __construct(Mailer $mailer, TranslatorInterface $translator, SettingsRepositoryInterface $settings)
{ {

View File

@ -24,7 +24,8 @@ class NotificationRepository
*/ */
public function findByUser(User $user, $limit = null, $offset = 0) public function findByUser(User $user, $limit = null, $offset = 0)
{ {
$primaries = Notification::selectRaw('MAX(id) AS id') $primaries = Notification::query()
->selectRaw('MAX(id) AS id')
->selectRaw('SUM(read_at IS NULL) AS unread_count') ->selectRaw('SUM(read_at IS NULL) AS unread_count')
->where('user_id', $user->id) ->where('user_id', $user->id)
->whereIn('type', $user->getAlertableNotificationTypes()) ->whereIn('type', $user->getAlertableNotificationTypes())
@ -35,7 +36,8 @@ class NotificationRepository
->skip($offset) ->skip($offset)
->take($limit); ->take($limit);
return Notification::select('notifications.*', 'p.unread_count') return Notification::query()
->select('notifications.*', 'p.unread_count')
->joinSub($primaries, 'p', 'notifications.id', '=', 'p.id') ->joinSub($primaries, 'p', 'notifications.id', '=', 'p.id')
->latest() ->latest()
->get(); ->get();

View File

@ -23,7 +23,7 @@ interface MergeableInterface
* passed model. * passed model.
* *
* @param \Flarum\Post\Post|null $previous * @param \Flarum\Post\Post|null $previous
* @return Post The model resulting after the merge. If the merge is * @return static The model resulting after the merge. If the merge is
* unsuccessful, this should be the current model instance. Otherwise, * unsuccessful, this should be the current model instance. Otherwise,
* it should be the model that was merged into. * it should be the model that was merged into.
*/ */

View File

@ -16,14 +16,13 @@ use Flarum\Foundation\EventGeneratorTrait;
use Flarum\Notification\Notification; use Flarum\Notification\Notification;
use Flarum\Post\Event\Deleted; use Flarum\Post\Event\Deleted;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Expression;
/** /**
* @property int $id * @property int $id
* @property int $discussion_id * @property int $discussion_id
* @property int $number * @property int|Expression $number
* @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $created_at
* @property int|null $user_id * @property int|null $user_id
* @property string|null $type * @property string|null $type
@ -94,8 +93,8 @@ class Post extends AbstractModel
static::creating(function (self $post) { static::creating(function (self $post) {
$post->type = $post::$type; $post->type = $post::$type;
/** @var ConnectionInterface $db */ $db = static::getConnectionResolver()->connection();
$db = static::getConnectionResolver();
$post->number = new Expression('('. $post->number = new Expression('('.
$db->table('posts', 'pn') $db->table('posts', 'pn')
->whereRaw($db->getTablePrefix().'pn.discussion_id = '.intval($post->discussion_id)) ->whereRaw($db->getTablePrefix().'pn.discussion_id = '.intval($post->discussion_id))

View File

@ -18,7 +18,7 @@ class PostRepository
/** /**
* Get a new query builder for the posts table. * Get a new query builder for the posts table.
* *
* @return Builder * @return Builder<Post>
*/ */
public function query() public function query()
{ {
@ -27,7 +27,7 @@ class PostRepository
/** /**
* @param User|null $user * @param User|null $user
* @return Builder * @return Builder<Post>
*/ */
protected function queryVisibleTo(User $user = null) protected function queryVisibleTo(User $user = null)
{ {
@ -45,8 +45,8 @@ class PostRepository
* user, or throw an exception. * user, or throw an exception.
* *
* @param int $id * @param int $id
* @param \Flarum\User\User $actor * @param \Flarum\User\User|null $actor
* @return \Flarum\Post\Post * @return Post
* *
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/ */
@ -126,7 +126,7 @@ class PostRepository
/** /**
* @param array $ids * @param array $ids
* @param User|null $actor * @param User|null $actor
* @return Builder * @return Builder<Post>
*/ */
protected function queryIds(array $ids, User $actor = null) protected function queryIds(array $ids, User $actor = null)
{ {

View File

@ -42,9 +42,9 @@ class ExceptionHandler implements ExceptionHandling
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param Throwable $e * @param Throwable $e
* @return \Symfony\Component\HttpFoundation\Response * @return void
*/ */
public function render($request, Throwable $e) public function render($request, Throwable $e) /** @phpstan-ignore-line */
{ {
// TODO: Implement render() method. // TODO: Implement render() method.
} }

View File

@ -21,7 +21,7 @@ class QueueFactory implements Factory
/** /**
* The cached queue instance. * The cached queue instance.
* *
* @var \Illuminate\Contracts\Queue\Queue * @var \Illuminate\Contracts\Queue\Queue|null
*/ */
private $queue; private $queue;

View File

@ -14,6 +14,7 @@ use Flarum\Foundation\Config;
use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\Paths; use Flarum\Foundation\Paths;
use Illuminate\Container\Container as ContainerImplementation;
use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Cache\Factory as CacheFactory;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling; use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
@ -52,7 +53,7 @@ class QueueServiceProvider extends AbstractServiceProvider
// Extensions can override this binding if they want to make Flarum use // Extensions can override this binding if they want to make Flarum use
// a different queuing backend. // a different queuing backend.
$this->container->singleton('flarum.queue.connection', function (Container $container) { $this->container->singleton('flarum.queue.connection', function (ContainerImplementation $container) {
$queue = new SyncQueue; $queue = new SyncQueue;
$queue->setContainer($container); $queue->setContainer($container);
@ -90,7 +91,12 @@ class QueueServiceProvider extends AbstractServiceProvider
// Bind a simple cache manager that returns the cache store. // Bind a simple cache manager that returns the cache store.
$this->container->singleton('cache', function (Container $container) { $this->container->singleton('cache', function (Container $container) {
return new class($container) implements CacheFactory { return new class($container) implements CacheFactory {
public function __construct($container) /**
* @var Container
*/
private $container;
public function __construct(Container $container)
{ {
$this->container = $container; $this->container = $container;
} }

View File

@ -34,13 +34,15 @@ abstract class AbstractRegexGambit implements GambitInterface
* Match the bit against this gambit. * Match the bit against this gambit.
* *
* @param string $bit * @param string $bit
* @return array * @return array|null
*/ */
protected function match($bit) protected function match($bit)
{ {
if (preg_match('/^(-?)'.$this->getGambitPattern().'$/i', $bit, $matches)) { if (preg_match('/^(-?)'.$this->getGambitPattern().'$/i', $bit, $matches)) {
return $matches; return $matches;
} }
return null;
} }
/** /**

View File

@ -43,7 +43,6 @@ abstract class AbstractSearcher
* @param int $offset * @param int $offset
* *
* @return QueryResults * @return QueryResults
* @throws InvalidArgumentException
*/ */
public function search(QueryCriteria $criteria, $limit = null, $offset = 0): QueryResults public function search(QueryCriteria $criteria, $limit = null, $offset = 0): QueryResults
{ {

View File

@ -26,9 +26,6 @@ class GambitManager
*/ */
protected $fulltextGambit; protected $fulltextGambit;
/**
* @param GambitInterface $gambit
*/
public function __construct(GambitInterface $fulltextGambit) public function __construct(GambitInterface $fulltextGambit)
{ {
$this->fulltextGambit = $fulltextGambit; $this->fulltextGambit = $fulltextGambit;

View File

@ -28,10 +28,6 @@ class IndexController extends AbstractHtmlController
$this->view = $view; $this->view = $view;
} }
/**
* @param Request $request
* @return \Psr\Http\Message\ResponseInterface
*/
public function render(Request $request) public function render(Request $request)
{ {
$view = $this->view->make('flarum.update::app')->with('title', 'Update Flarum'); $view = $this->view->make('flarum.update::app')->with('title', 'Update Flarum');

View File

@ -40,13 +40,11 @@ abstract class AbstractPolicy
} }
/** /**
* @param User $user
* @param string $ability
* @param $instance
* @return string|void * @return string|void
*/ */
public function checkAbility(User $actor, string $ability, $instance) public function checkAbility(User $actor, string $ability, $instance)
{ // If a specific method for this ability is defined, {
// If a specific method for this ability is defined,
// call that and return any non-null results // call that and return any non-null results
if (method_exists($this, $ability)) { if (method_exists($this, $ability)) {
$result = $this->sanitizeResult(call_user_func_array([$this, $ability], [$actor, $instance])); $result = $this->sanitizeResult(call_user_func_array([$this, $ability], [$actor, $instance]));
@ -73,7 +71,7 @@ abstract class AbstractPolicy
* `return SOME_BOOLEAN_LOGIC; * `return SOME_BOOLEAN_LOGIC;
* *
* @param mixed $result * @param mixed $result
* @return string|void * @return string|void|null
*/ */
public function sanitizeResult($result) public function sanitizeResult($result)
{ {

View File

@ -54,12 +54,6 @@ class RegisterUserHandler
*/ */
protected $imageManager; protected $imageManager;
/**
* @param Dispatcher $events
* @param SettingsRepositoryInterface $settings
* @param UserValidator $validator
* @param AvatarUploader $avatarUploader
*/
public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $userValidator, AvatarUploader $avatarUploader, Factory $validator, ImageManager $imageManager) public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $userValidator, AvatarUploader $avatarUploader, Factory $validator, ImageManager $imageManager)
{ {
$this->events = $events; $this->events = $events;
@ -91,6 +85,7 @@ class RegisterUserHandler
// If a valid authentication token was provided as an attribute, // If a valid authentication token was provided as an attribute,
// then we won't require the user to choose a password. // then we won't require the user to choose a password.
if (isset($data['attributes']['token'])) { if (isset($data['attributes']['token'])) {
/** @var RegistrationToken $token */
$token = RegistrationToken::validOrFail($data['attributes']['token']); $token = RegistrationToken::validOrFail($data['attributes']['token']);
$password = $password ?: Str::random(20); $password = $password ?: Str::random(20);

View File

@ -81,7 +81,7 @@ class EmailToken extends AbstractModel
*/ */
public function scopeValidOrFail($query, $id) public function scopeValidOrFail($query, $id)
{ {
/** @var EmailToken $token */ /** @var static|null $token */
$token = $query->find($id); $token = $query->find($id);
if (! $token || $token->created_at->diffInDays() >= 1) { if (! $token || $token->created_at->diffInDays() >= 1) {

View File

@ -12,6 +12,9 @@ namespace Flarum\User;
use Flarum\Database\AbstractModel; use Flarum\Database\AbstractModel;
use Flarum\Http\SlugDriverInterface; use Flarum\Http\SlugDriverInterface;
/**
* @implements SlugDriverInterface<User>
*/
class IdSlugDriver implements SlugDriverInterface class IdSlugDriver implements SlugDriverInterface
{ {
/** /**
@ -24,11 +27,17 @@ class IdSlugDriver implements SlugDriverInterface
$this->users = $users; $this->users = $users;
} }
/**
* @param User $instance
*/
public function toSlug(AbstractModel $instance): string public function toSlug(AbstractModel $instance): string
{ {
return $instance->id; return (string) $instance->id;
} }
/**
* @return User
*/
public function fromSlug(string $slug, User $actor): AbstractModel public function fromSlug(string $slug, User $actor): AbstractModel
{ {
return $this->users->findOrFail($slug, $actor); return $this->users->findOrFail($slug, $actor);

View File

@ -21,6 +21,8 @@ use Illuminate\Support\Str;
* @property array $user_attributes * @property array $user_attributes
* @property array $payload * @property array $payload
* @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $created_at
*
* @method static self validOrFail(string $token)
*/ */
class RegistrationToken extends AbstractModel class RegistrationToken extends AbstractModel
{ {
@ -74,7 +76,7 @@ class RegistrationToken extends AbstractModel
/** /**
* Find the token with the given ID, and assert that it has not expired. * Find the token with the given ID, and assert that it has not expired.
* *
* @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder<self> $query
* @param string $token * @param string $token
* *
* @throws InvalidConfirmationTokenException * @throws InvalidConfirmationTokenException
@ -83,7 +85,7 @@ class RegistrationToken extends AbstractModel
*/ */
public function scopeValidOrFail($query, string $token) public function scopeValidOrFail($query, string $token)
{ {
/** @var RegistrationToken $token */ /** @var RegistrationToken|null $token */
$token = $query->find($token); $token = $query->find($token);
if (! $token || $token->created_at->lessThan(Carbon::now()->subDay())) { if (! $token || $token->created_at->lessThan(Carbon::now()->subDay())) {

View File

@ -50,5 +50,7 @@ class FulltextGambit implements GambitInterface
'id', 'id',
$this->getUserSearchSubQuery($searchValue) $this->getUserSearchSubQuery($searchValue)
); );
return true;
} }
} }

View File

@ -109,7 +109,7 @@ class User extends AbstractModel
/** /**
* The access gate. * The access gate.
* *
* @var Gate * @var Access\Gate
*/ */
protected static $gate; protected static $gate;
@ -172,7 +172,7 @@ class User extends AbstractModel
} }
/** /**
* @param Gate $gate * @param Access\Gate $gate
*/ */
public static function setGate($gate) public static function setGate($gate)
{ {
@ -296,7 +296,7 @@ class User extends AbstractModel
/** /**
* Change the path of the user avatar. * Change the path of the user avatar.
* *
* @param string $path * @param string|null $path
* @return $this * @return $this
*/ */
public function changeAvatarPath($path) public function changeAvatarPath($path)
@ -311,7 +311,6 @@ class User extends AbstractModel
/** /**
* Get the URL of the user's avatar. * Get the URL of the user's avatar.
* *
* @todo Allow different storage locations to be used
* @param string|null $value * @param string|null $value
* @return string * @return string
*/ */
@ -410,15 +409,6 @@ class User extends AbstractModel
return false; return false;
} }
private function checkForDeprecatedPermissions($permission)
{
foreach (['viewDiscussions', 'viewUserList'] as $deprecated) {
if (strpos($permission, $deprecated) !== false) {
trigger_error('The `viewDiscussions` and `viewUserList` permissions have been renamed to `viewForum` and `searchUsers` respectively. Please use those instead.', E_USER_DEPRECATED);
}
}
}
/** /**
* Get the notification types that should be alerted to this user, according * Get the notification types that should be alerted to this user, according
* to their preferences. * to their preferences.
@ -482,7 +472,7 @@ class User extends AbstractModel
* Get the values of all registered preferences for this user, by * Get the values of all registered preferences for this user, by
* transforming their stored preferences and merging them with the defaults. * transforming their stored preferences and merging them with the defaults.
* *
* @param string $value * @param string|null $value
* @return array * @return array
*/ */
public function getPreferencesAttribute($value) public function getPreferencesAttribute($value)
@ -679,7 +669,7 @@ class User extends AbstractModel
/** /**
* Define the relationship with the user's read discussions. * Define the relationship with the user's read discussions.
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<Discussion>
*/ */
public function read() public function read()
{ {
@ -838,7 +828,7 @@ class User extends AbstractModel
* Register a callback that processes a user's list of groups. * Register a callback that processes a user's list of groups.
* *
* @param callable $callback * @param callable $callback
* @return array $groupIds * @return void
* *
* @internal * @internal
*/ */

View File

@ -16,7 +16,7 @@ class UserRepository
/** /**
* Get a new query builder for the users table. * Get a new query builder for the users table.
* *
* @return \Illuminate\Database\Eloquent\Builder * @return Builder<User>
*/ */
public function query() public function query()
{ {
@ -27,15 +27,15 @@ class UserRepository
* Find a user by ID, optionally making sure it is visible to a certain * Find a user by ID, optionally making sure it is visible to a certain
* user, or throw an exception. * user, or throw an exception.
* *
* @param int $id * @param int|string $id
* @param User $actor * @param User|null $actor
* @return User * @return User
* *
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/ */
public function findOrFail($id, User $actor = null) public function findOrFail($id, User $actor = null)
{ {
$query = User::where('id', $id); $query = $this->query()->where('id', $id);
return $this->scopeVisibleTo($query, $actor)->firstOrFail(); return $this->scopeVisibleTo($query, $actor)->firstOrFail();
} }
@ -44,15 +44,15 @@ class UserRepository
* Find a user by username, optionally making sure it is visible to a certain * Find a user by username, optionally making sure it is visible to a certain
* user, or throw an exception. * user, or throw an exception.
* *
* @param int $id * @param string $username
* @param User $actor * @param User|null $actor
* @return User * @return User
* *
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/ */
public function findOrFailByUsername($username, User $actor = null) public function findOrFailByUsername($username, User $actor = null)
{ {
$query = User::where('username', $username); $query = $this->query()->where('username', $username);
return $this->scopeVisibleTo($query, $actor)->firstOrFail(); return $this->scopeVisibleTo($query, $actor)->firstOrFail();
} }
@ -67,7 +67,7 @@ class UserRepository
{ {
$field = filter_var($identification, FILTER_VALIDATE_EMAIL) ? 'email' : 'username'; $field = filter_var($identification, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
return User::where($field, $identification)->first(); return $this->query()->where($field, $identification)->first();
} }
/** /**
@ -78,7 +78,7 @@ class UserRepository
*/ */
public function findByEmail($email) public function findByEmail($email)
{ {
return User::where('email', $email)->first(); return $this->query()->where('email', $email)->first();
} }
/** /**
@ -90,7 +90,7 @@ class UserRepository
*/ */
public function getIdForUsername($username, User $actor = null) public function getIdForUsername($username, User $actor = null)
{ {
$query = User::where('username', $username); $query = $this->query()->where('username', $username);
return $this->scopeVisibleTo($query, $actor)->value('id'); return $this->scopeVisibleTo($query, $actor)->value('id');
} }
@ -107,7 +107,7 @@ class UserRepository
{ {
$string = $this->escapeLikeString($string); $string = $this->escapeLikeString($string);
$query = User::where('username', 'like', '%'.$string.'%') $query = $this->query()->where('username', 'like', '%'.$string.'%')
->orderByRaw('username = ? desc', [$string]) ->orderByRaw('username = ? desc', [$string])
->orderByRaw('username like ? desc', [$string.'%']); ->orderByRaw('username like ? desc', [$string.'%']);
@ -117,9 +117,9 @@ class UserRepository
/** /**
* Scope a query to only include records that are visible to a user. * Scope a query to only include records that are visible to a user.
* *
* @param Builder $query * @param Builder<User> $query
* @param User $actor * @param User|null $actor
* @return Builder * @return Builder<User>
*/ */
protected function scopeVisibleTo(Builder $query, User $actor = null) protected function scopeVisibleTo(Builder $query, User $actor = null)
{ {

View File

@ -109,6 +109,9 @@ class UserServiceProvider extends AbstractServiceProvider
User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $container)); User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $container));
} }
/**
* @var \Illuminate\Container\Container $container
*/
User::setHasher($container->make('hash')); User::setHasher($container->make('hash'));
User::setPasswordCheckers($container->make('flarum.user.password_checkers')); User::setPasswordCheckers($container->make('flarum.user.password_checkers'));
User::setGate($container->makeWith(Access\Gate::class, ['policyClasses' => $container->make('flarum.policies')])); User::setGate($container->makeWith(Access\Gate::class, ['policyClasses' => $container->make('flarum.policies')]));

View File

@ -14,7 +14,7 @@ use Flarum\Foundation\AbstractValidator;
class UserValidator extends AbstractValidator class UserValidator extends AbstractValidator
{ {
/** /**
* @var User * @var User|null
*/ */
protected $user; protected $user;

View File

@ -12,6 +12,9 @@ namespace Flarum\User;
use Flarum\Database\AbstractModel; use Flarum\Database\AbstractModel;
use Flarum\Http\SlugDriverInterface; use Flarum\Http\SlugDriverInterface;
/**
* @implements SlugDriverInterface<User>
*/
class UsernameSlugDriver implements SlugDriverInterface class UsernameSlugDriver implements SlugDriverInterface
{ {
/** /**
@ -24,11 +27,17 @@ class UsernameSlugDriver implements SlugDriverInterface
$this->users = $users; $this->users = $users;
} }
/**
* @param User $instance
*/
public function toSlug(AbstractModel $instance): string public function toSlug(AbstractModel $instance): string
{ {
return $instance->username; return $instance->username;
} }
/**
* @return User
*/
public function fromSlug(string $slug, User $actor): AbstractModel public function fromSlug(string $slug, User $actor): AbstractModel
{ {
return $this->users->findOrFailByUsername($slug, $actor); return $this->users->findOrFailByUsername($slug, $actor);

View File

@ -16,11 +16,11 @@ if (! function_exists('resolve')) {
* Resolve a service from the container. * Resolve a service from the container.
* *
* @template T * @template T
* @param class-string<T>|string $name * @param string|class-string<T> $name
* @param array $parameters * @param array $parameters
* @return T|mixed * @return T|mixed
*/ */
function resolve($name, $parameters = []) function resolve(string $name, array $parameters = [])
{ {
return Container::getInstance()->make($name, $parameters); return Container::getInstance()->make($name, $parameters);
} }

View File

@ -4,8 +4,8 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"license": "MIT", "license": "MIT",
"require": { "require": {
"phpstan/phpstan-php-parser": "^1.0", "phpstan/phpstan": "^1.2",
"phpstan/phpstan": "^1.2" "nunomaduro/larastan": "^1.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -1,291 +1,17 @@
includes: includes:
- vendor/phpstan/phpstan-php-parser/extension.neon - larastan-extension.neon
- phpstan-baseline.neon
parameters: parameters:
stubFiles: stubFiles:
- stubs/Illuminate/Enumerable.stub - stubs/Illuminate/Contracts/Container/Container.stub
- stubs/Illuminate/Database/EloquentBuilder.stub - stubs/Illuminate/Queue/ListenerOptions.stub
- stubs/Illuminate/Collection.stub - stubs/Illuminate/Support/ServiceProvider.stub
- stubs/Illuminate/Database/EloquentCollection.stub - stubs/Illuminate/Filesystem/Filesystem.stub
- stubs/Illuminate/Database/Factory.stub - stubs/Illuminate/Filesystem/FilesystemManager.stub
- stubs/Illuminate/Database/Model.stub
- stubs/Illuminate/Database/Gate.stub
- stubs/Illuminate/Database/Relation.stub
- stubs/Illuminate/Database/BelongsTo.stub
- stubs/Illuminate/Database/BelongsToMany.stub
- stubs/Illuminate/Database/HasOneOrMany.stub
- stubs/Illuminate/Database/HasMany.stub
- stubs/Illuminate/Database/HasOne.stub
- stubs/Illuminate/Database/HasOneThrough.stub
- stubs/Illuminate/Database/HasManyThrough.stub
- stubs/Illuminate/Database/MorphTo.stub
- stubs/Illuminate/Database/MorphToMany.stub
- stubs/Illuminate/Database/MorphMany.stub
- stubs/Illuminate/Database/MorphOne.stub
- stubs/Illuminate/Database/MorphOneOrMany.stub
- stubs/Illuminate/HigherOrderProxies.stub
- stubs/Illuminate/Database/QueryBuilder.stub
- stubs/Illuminate/EnumeratesValues.stub
- stubs/Contracts/Support.stub
universalObjectCratesClasses:
- Illuminate\Http\Request
mixinExcludeClasses:
- Eloquent
earlyTerminatingFunctionCalls:
- abort
- dd
excludePaths:
- *.blade.php
checkGenericClassInNonGenericObjectType: false
checkModelProperties: false
databaseMigrationsPath: []
parametersSchema: # We're changing the disk return type from Filesystem to Cloud,
databaseMigrationsPath: listOf(string()) # rather than hacking every bit of the codebase with a phpdoc @var.
checkModelProperties: bool() - stubs/Illuminate/Contracts/Filesystem/Factory.stub
- stubs/Illuminate/Contracts/Filesystem/Cloud.stub
services: - stubs/Illuminate/Contracts/Filesystem/Filesystem.stub
-
class: Flarum\PHPStan\Methods\RelationForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\ModelForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\EloquentBuilderForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\HigherOrderTapProxyExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\HigherOrderCollectionProxyExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\StorageMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\Extension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Methods\ModelFactoryMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: Flarum\PHPStan\Properties\ModelAccessorExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: Flarum\PHPStan\Properties\ModelPropertyExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: Flarum\PHPStan\Properties\HigherOrderCollectionProxyPropertyExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: Flarum\PHPStan\Types\RelationDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\Types\ModelRelationsDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\HigherOrderTapProxyExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Contracts\Container\Container
-
class: Flarum\PHPStan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Container\Container
-
class: Flarum\PHPStan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Foundation\Application
-
class: Flarum\PHPStan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Contracts\Foundation\Application
-
class: Flarum\PHPStan\Properties\ModelRelationsExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: Flarum\PHPStan\ReturnTypes\ModelFactoryDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\ModelExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\RequestExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\EloquentBuilderExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\RelationFindExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\RelationCollectionExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\ModelFindExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\BuilderModelFindExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\TestCaseExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\CollectionMakeDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: Flarum\PHPStan\Support\CollectionHelper
-
class: Flarum\PHPStan\ReturnTypes\Helpers\CollectExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\Helpers\TransExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\Helpers\ValidatorExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\CollectionFilterDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: Flarum\PHPStan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: 'abort'
negate: false
-
class: Flarum\PHPStan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: 'abort'
negate: true
-
class: Flarum\PHPStan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: throw
negate: false
-
class: Flarum\PHPStan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: throw
negate: true
-
class: Flarum\PHPStan\ReturnTypes\Helpers\AppExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\Helpers\ValueExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\Helpers\TapExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: Flarum\PHPStan\ReturnTypes\StorageDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: Flarum\PHPStan\Types\GenericEloquentCollectionTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
-
class: Flarum\PHPStan\Types\ViewStringTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
-
class: Flarum\PHPStan\Methods\BuilderHelper
arguments:
checkProperties: %checkModelProperties%
-
class: Flarum\PHPStan\Properties\MigrationHelper
arguments:
databaseMigrationPath: %databaseMigrationsPath%
parser: @currentPhpVersionSimpleDirectParser
-
class: Flarum\PHPStan\Types\RelationParserHelper
arguments:
parser: @currentPhpVersionSimpleDirectParser

View File

@ -0,0 +1,439 @@
parameters:
stubFiles:
- ../../vendor/nunomaduro/larastan/stubs/Enumerable.stub
- ../../vendor/nunomaduro/larastan/stubs/EloquentBuilder.stub
- ../../vendor/nunomaduro/larastan/stubs/Collection.stub
- ../../vendor/nunomaduro/larastan/stubs/EloquentCollection.stub
- ../../vendor/nunomaduro/larastan/stubs/Factory.stub
- ../../vendor/nunomaduro/larastan/stubs/Model.stub
- ../../vendor/nunomaduro/larastan/stubs/Gate.stub
- ../../vendor/nunomaduro/larastan/stubs/Relation.stub
- ../../vendor/nunomaduro/larastan/stubs/BelongsTo.stub
- ../../vendor/nunomaduro/larastan/stubs/BelongsToMany.stub
- ../../vendor/nunomaduro/larastan/stubs/HasOneOrMany.stub
- ../../vendor/nunomaduro/larastan/stubs/HasMany.stub
- ../../vendor/nunomaduro/larastan/stubs/HasOne.stub
- ../../vendor/nunomaduro/larastan/stubs/HasOneThrough.stub
- ../../vendor/nunomaduro/larastan/stubs/HasManyThrough.stub
- ../../vendor/nunomaduro/larastan/stubs/Mailable.stub
- ../../vendor/nunomaduro/larastan/stubs/MorphOne.stub
- ../../vendor/nunomaduro/larastan/stubs/MorphOneOrMany.stub
- ../../vendor/nunomaduro/larastan/stubs/MorphTo.stub
- ../../vendor/nunomaduro/larastan/stubs/MorphToMany.stub
- ../../vendor/nunomaduro/larastan/stubs/MorphMany.stub
- ../../vendor/nunomaduro/larastan/stubs/Helpers.stub
- ../../vendor/nunomaduro/larastan/stubs/HigherOrderProxies.stub
- ../../vendor/nunomaduro/larastan/stubs/QueryBuilder.stub
- ../../vendor/nunomaduro/larastan/stubs/Facades.stub
- ../../vendor/nunomaduro/larastan/stubs/Pagination.stub
- ../../vendor/nunomaduro/larastan/stubs/Contracts/Pagination.stub
- ../../vendor/nunomaduro/larastan/stubs/Contracts/Support.stub
- ../../vendor/nunomaduro/larastan/stubs/Redis/Connection.stub
- ../../vendor/nunomaduro/larastan/stubs/Logger.stub
- ../../vendor/nunomaduro/larastan/stubs/EnumeratesValues.stub
universalObjectCratesClasses:
- Illuminate\Http\Request
earlyTerminatingFunctionCalls:
- abort
- dd
excludePaths:
- *.blade.php
mixinExcludeClasses:
- Eloquent
# bootstrapFiles:
# - bootstrap.php
checkGenericClassInNonGenericObjectType: false
checkOctaneCompatibility: false
noModelMake: true
noUnnecessaryCollectionCall: true
noUnnecessaryCollectionCallOnly: []
noUnnecessaryCollectionCallExcept: []
databaseMigrationsPath: []
checkModelProperties: false
checkPhpDocMissingReturn: false
parametersSchema:
checkOctaneCompatibility: bool()
noModelMake: bool()
noUnnecessaryCollectionCall: bool()
noUnnecessaryCollectionCallOnly: listOf(string())
noUnnecessaryCollectionCallExcept: listOf(string())
databaseMigrationsPath: listOf(string())
checkModelProperties: bool()
conditionalTags:
NunoMaduro\Larastan\Rules\NoModelMakeRule:
phpstan.rules.rule: %noModelMake%
NunoMaduro\Larastan\Rules\NoUnnecessaryCollectionCallRule:
phpstan.rules.rule: %noUnnecessaryCollectionCall%
NunoMaduro\Larastan\Rules\OctaneCompatibilityRule:
phpstan.rules.rule: %checkOctaneCompatibility%
NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyRule:
phpstan.rules.rule: %checkModelProperties%
NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyStaticCallRule:
phpstan.rules.rule: %checkModelProperties%
services:
-
class: NunoMaduro\Larastan\Methods\RelationForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\ModelForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\EloquentBuilderForwardsCallsExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\HigherOrderTapProxyExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\HigherOrderCollectionProxyExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\StorageMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\Extension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\ModelFactoryMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Methods\RedirectResponseMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: NunoMaduro\Larastan\Properties\ModelAccessorExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: NunoMaduro\Larastan\Properties\ModelPropertyExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: NunoMaduro\Larastan\Properties\HigherOrderCollectionProxyPropertyExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: NunoMaduro\Larastan\Types\RelationDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\Types\ModelRelationsDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\HigherOrderTapProxyExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Contracts\Container\Container
-
class: NunoMaduro\Larastan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Container\Container
-
class: NunoMaduro\Larastan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Foundation\Application
-
class: NunoMaduro\Larastan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
arguments:
className: Illuminate\Contracts\Foundation\Application
-
class: NunoMaduro\Larastan\Properties\ModelRelationsExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
-
class: NunoMaduro\Larastan\ReturnTypes\ModelFactoryDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\ModelExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\AuthExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\GuardDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\AuthManagerExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\GuardExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\RequestExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\EloquentBuilderExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\RelationFindExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\RelationCollectionExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\ModelFindExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\BuilderModelFindExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\TestCaseExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\CollectionMakeDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\Support\CollectionHelper
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\AuthExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\CollectExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\CookieExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\ResponseExtension
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:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\CollectionFilterDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: 'abort'
negate: false
-
class: NunoMaduro\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: 'abort'
negate: true
-
class: NunoMaduro\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: throw
negate: false
-
class: NunoMaduro\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
arguments:
methodName: throw
negate: true
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\AppExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\ValueExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\Helpers\TapExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\StorageDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\Types\GenericEloquentCollectionTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
-
class: NunoMaduro\Larastan\Types\ViewStringTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
-
class: NunoMaduro\Larastan\Rules\OctaneCompatibilityRule
-
class: NunoMaduro\Larastan\Rules\NoModelMakeRule
-
class: NunoMaduro\Larastan\Rules\NoUnnecessaryCollectionCallRule
arguments:
onlyMethods: %noUnnecessaryCollectionCallOnly%
excludeMethods: %noUnnecessaryCollectionCallExcept%
-
class: NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyRule
-
class: NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyStaticCallRule
-
class: NunoMaduro\Larastan\Types\GenericEloquentBuilderTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
-
class: NunoMaduro\Larastan\Types\ModelProperty\ModelPropertyTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension
arguments:
active: %checkModelProperties%
-
class: NunoMaduro\Larastan\Types\RelationParserHelper
arguments:
parser: @currentPhpVersionSimpleDirectParser
-
class: NunoMaduro\Larastan\Properties\MigrationHelper
arguments:
databaseMigrationPath: %databaseMigrationsPath%
parser: @currentPhpVersionSimpleDirectParser
-
class: NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertiesRuleHelper
-
class: NunoMaduro\Larastan\Rules\ModelRuleHelper
-
class: NunoMaduro\Larastan\Methods\BuilderHelper
arguments:
checkProperties: %checkModelProperties%
-
class: NunoMaduro\Larastan\Rules\RelationExistenceRule
tags:
- phpstan.rule
-
class: NunoMaduro\Larastan\Rules\CheckDispatchArgumentTypesCompatibleWithClassConstructorRule
arguments:
dispatchableClass: Illuminate\Foundation\Bus\Dispatchable
tags:
- phpstan.rules.rule
-
class: NunoMaduro\Larastan\Rules\CheckDispatchArgumentTypesCompatibleWithClassConstructorRule
arguments:
dispatchableClass: Illuminate\Foundation\Events\Dispatchable
tags:
- phpstan.rules.rule
rules:
- NunoMaduro\Larastan\Rules\RelationExistenceRule

View File

@ -0,0 +1,17 @@
parameters:
ignoreErrors:
# Remove this group below with larastan 2.0 (i.e Flarum 2.0)
- "#Relation '[A-z_-]+' is not found in [A-z\_]+ model.#"
- '#^Parameter \#1 \$query of method [A-z_<>\\]+\:\:union\(\) expects [A-z_<> .,|\\]+ given\.$#'
- '#^Parameter \#1 \$query of method [A-z_<>\\]+\:\:joinSub\(\) expects [A-z_<> .,|\\]+ given\.$#'
# We ignore this because resolve can either take a class name as the generic return type or just a binding name.
- "#Template type T of function resolve[()]{2} is not referenced in a parameter.#"
# We ignore new static errors because we want extensibility.
# @TODO: needs discussion.
- "#^Unsafe usage of new static[()]{2}.$#"
# ConnectionInterface lacks methods that exist in the implementation,
# yet we don't want to inject the implementation.
- '#^Call to an undefined method Illuminate\\Database\\ConnectionInterface\:\:[A-z0-9_]+\(\)\.$#'

View File

@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Concerns;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionException;
/**
* @internal
*/
trait HasContainer
{
/**
* @var ?\Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* @param \Illuminate\Contracts\Container\Container $container
* @return void
*/
public function setContainer(ContainerContract $container): void
{
$this->container = $container;
}
/**
* Returns the current broker.
*
* @return \Illuminate\Contracts\Container\Container
*/
public function getContainer(): ContainerContract
{
return $this->container ?? Container::getInstance();
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @return mixed
*/
public function resolve(string $abstract)
{
$concrete = null;
try {
$concrete = $this->getContainer()
->make($abstract);
} catch (ReflectionException $exception) {
// ..
} catch (BindingResolutionException $exception) {
// ..
} catch (NotFoundExceptionInterface $exception) {
// ..
}
return $concrete;
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Concerns;
use Illuminate\Config\Repository as ConfigRepository;
trait LoadsAuthModel
{
/** @phpstan-return class-string|null */
private function getAuthModel(ConfigRepository $config, ?string $guard = null): ?string
{
if (
($guard === null && ! ($guard = $config->get('auth.defaults.guard'))) ||
! ($provider = $config->get('auth.guards.'.$guard.'.provider')) ||
! ($authModel = $config->get('auth.providers.'.$provider.'.model'))
) {
return null;
}
return $authModel;
}
}

View File

@ -1,96 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Contracts\Methods;
use Illuminate\Contracts\Container\Container as ContainerContract;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
use PHPStan\Reflection\ReflectionProvider;
/**
* @internal
*/
interface PassableContract
{
/**
* @param \Illuminate\Contracts\Container\Container $container
* @return void
*/
public function setContainer(ContainerContract $container): void;
/**
* @return \PHPStan\Reflection\ClassReflection
*/
public function getClassReflection(): ClassReflection;
/**
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @return PassableContract
*/
public function setClassReflection(ClassReflection $classReflection): PassableContract;
/**
* @return string
*/
public function getMethodName(): string;
/**
* @return bool
*/
public function hasFound(): bool;
/**
* @param string $class
* @return bool
*/
public function searchOn(string $class): bool;
/**
* @return \PHPStan\Reflection\MethodReflection
*
* @throws \LogicException
*/
public function getMethodReflection(): MethodReflection;
/**
* @param \PHPStan\Reflection\MethodReflection $methodReflection
*/
public function setMethodReflection(MethodReflection $methodReflection): void;
/**
* Declares that the provided method can be called statically.
*
* @param bool $staticAllowed
* @return void
*/
public function setStaticAllowed(bool $staticAllowed): void;
/**
* Returns whether the method can be called statically.
*
* @return bool
*/
public function isStaticAllowed(): bool;
/**
* @param class-string $class
* @param bool $staticAllowed
* @return bool
*/
public function sendToPipeline(string $class, $staticAllowed = false): bool;
public function getReflectionProvider(): ReflectionProvider;
/**
* @return \PHPStan\Reflection\Php\PhpMethodReflectionFactory
*/
public function getMethodReflectionFactory(): PhpMethodReflectionFactory;
}

View File

@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Contracts\Methods\Pipes;
use Closure;
use Flarum\PHPStan\Contracts\Methods\PassableContract;
/**
* @internal
*/
interface PipeContract
{
/**
* @param \Flarum\PHPStan\Contracts\Methods\PassableContract $passable
* @param \Closure $next
* @return void
*/
public function handle(PassableContract $passable, Closure $next): void;
}

View File

@ -1,29 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Contracts\Types;
use PHPStan\Type\Type;
/**
* @internal
*/
interface PassableContract
{
/**
* @return \PHPStan\Type\Type
*/
public function getType(): Type;
/**
* @param \PHPStan\Type\Type $type
* @return void
*/
public function setType(Type $type): void;
}

View File

@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Contracts\Types\Pipes;
use Closure;
use Flarum\PHPStan\Contracts\Types\PassableContract;
/**
* @internal
*/
interface PipeContract
{
/**
* @param \Flarum\PHPStan\Contracts\Types\PassableContract $passable
* @param \Closure $next
* @return void
*/
public function handle(PassableContract $passable, Closure $next): void;
}

View File

@ -1,241 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Methods;
use Flarum\PHPStan\Reflection\AnnotationScopeMethodParameterReflection;
use Flarum\PHPStan\Reflection\AnnotationScopeMethodReflection;
use Flarum\PHPStan\Reflection\DynamicWhereParameterReflection;
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Str;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MissingMethodFromReflectionException;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
class BuilderHelper
{
/** @var string[] */
public const MODEL_RETRIEVAL_METHODS = ['first', 'find', 'findMany', 'findOrFail', 'firstOrFail', 'sole'];
/** @var string[] */
public const MODEL_CREATION_METHODS = ['make', 'create', 'forceCreate', 'findOrNew', 'firstOrNew', 'updateOrCreate', 'firstOrCreate'];
/**
* The methods that should be returned from query builder.
*
* @var string[]
*/
public $passthru = [
'average', 'avg',
'count',
'dd', 'dump',
'doesntExist', 'exists',
'getBindings', 'getConnection', 'getGrammar',
'insert', 'insertGetId', 'insertOrIgnore', 'insertUsing',
'max', 'min',
'raw',
'sum',
'toSql',
];
/** @var ReflectionProvider */
private $reflectionProvider;
/** @var bool */
private $checkProperties;
public function __construct(ReflectionProvider $reflectionProvider, bool $checkProperties)
{
$this->reflectionProvider = $reflectionProvider;
$this->checkProperties = $checkProperties;
}
public function dynamicWhere(
string $methodName,
Type $returnObject
): ?EloquentBuilderMethodReflection {
if (! Str::startsWith($methodName, 'where')) {
return null;
}
if ($returnObject instanceof GenericObjectType && $this->checkProperties) {
$returnClassReflection = $returnObject->getClassReflection();
if ($returnClassReflection !== null) {
$modelType = $returnClassReflection->getActiveTemplateTypeMap()->getType('TModelClass');
if ($modelType === null) {
$modelType = $returnClassReflection->getActiveTemplateTypeMap()->getType('TRelatedModel');
}
if ($modelType !== null) {
$finder = substr($methodName, 5);
$segments = preg_split(
'/(And|Or)(?=[A-Z])/',
$finder,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
if ($segments !== false) {
$trinaryLogic = TrinaryLogic::createYes();
foreach ($segments as $segment) {
if ($segment !== 'And' && $segment !== 'Or') {
$trinaryLogic = $trinaryLogic->and($modelType->hasProperty(Str::snake($segment)));
}
}
if (! $trinaryLogic->yes()) {
return null;
}
}
}
}
}
$classReflection = $this->reflectionProvider->getClass(QueryBuilder::class);
$methodReflection = $classReflection->getNativeMethod('dynamicWhere');
return new EloquentBuilderMethodReflection(
$methodName,
$classReflection,
$methodReflection,
[new DynamicWhereParameterReflection],
$returnObject,
true
);
}
/**
* This method mimics the `EloquentBuilder::__call` method.
* Does not handle the case where $methodName exists in `EloquentBuilder`,
* that should be checked by caller before calling this method.
*
* @param ClassReflection $eloquentBuilder Can be `EloquentBuilder` or a custom builder extending it.
* @param string $methodName
* @param ClassReflection $model
* @return MethodReflection|null
*
* @throws MissingMethodFromReflectionException
* @throws ShouldNotHappenException
*/
public function searchOnEloquentBuilder(ClassReflection $eloquentBuilder, string $methodName, ClassReflection $model): ?MethodReflection
{
// Check for local query scopes
if (array_key_exists('scope'.ucfirst($methodName), $model->getMethodTags())) {
$methodTag = $model->getMethodTags()['scope'.ucfirst($methodName)];
$parameters = [];
foreach ($methodTag->getParameters() as $parameterName => $parameterTag) {
$parameters[] = new AnnotationScopeMethodParameterReflection($parameterName, $parameterTag->getType(), $parameterTag->passedByReference(), $parameterTag->isOptional(), $parameterTag->isVariadic(), $parameterTag->getDefaultValue());
}
// We shift the parameters,
// because first parameter is the Builder
array_shift($parameters);
return new EloquentBuilderMethodReflection(
'scope'.ucfirst($methodName),
$model,
new AnnotationScopeMethodReflection('scope'.ucfirst($methodName), $model, $methodTag->getReturnType(), $parameters, $methodTag->isStatic(), false),
$parameters,
$methodTag->getReturnType()
);
}
if ($model->hasNativeMethod('scope'.ucfirst($methodName))) {
$methodReflection = $model->getNativeMethod('scope'.ucfirst($methodName));
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
$parameters = $parametersAcceptor->getParameters();
// We shift the parameters,
// because first parameter is the Builder
array_shift($parameters);
$returnType = $parametersAcceptor->getReturnType();
return new EloquentBuilderMethodReflection(
'scope'.ucfirst($methodName),
$methodReflection->getDeclaringClass(),
$methodReflection,
$parameters,
$returnType,
$parametersAcceptor->isVariadic()
);
}
$queryBuilderReflection = $this->reflectionProvider->getClass(QueryBuilder::class);
if (in_array($methodName, $this->passthru, true)) {
return $queryBuilderReflection->getNativeMethod($methodName);
}
if ($queryBuilderReflection->hasNativeMethod($methodName)) {
return $queryBuilderReflection->getNativeMethod($methodName);
}
return $this->dynamicWhere($methodName, new GenericObjectType($eloquentBuilder->getName(), [new ObjectType($model->getName())]));
}
/**
* @param string $modelClassName
* @return string
*
* @throws MissingMethodFromReflectionException
* @throws ShouldNotHappenException
*/
public function determineBuilderName(string $modelClassName): string
{
$method = $this->reflectionProvider->getClass($modelClassName)->getNativeMethod('newEloquentBuilder');
$returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType();
if (in_array(EloquentBuilder::class, $returnType->getReferencedClasses(), true)) {
return EloquentBuilder::class;
}
if ($returnType instanceof ObjectType) {
return $returnType->getClassName();
}
return $returnType->describe(VerbosityLevel::value());
}
/**
* @throws MissingMethodFromReflectionException
* @throws ShouldNotHappenException
*/
public function determineCollectionClassName(string $modelClassName): string
{
$newCollectionMethod = $this->reflectionProvider->getClass($modelClassName)->getNativeMethod('newCollection');
$returnType = ParametersAcceptorSelector::selectSingle($newCollectionMethod->getVariants())->getReturnType();
if ($returnType instanceof ObjectType) {
return $returnType->getClassName();
}
return $returnType->describe(VerbosityLevel::value());
}
}

View File

@ -1,161 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Methods;
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Reflection\MissingMethodFromReflectionException;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateMixedType;
use PHPStan\Type\Generic\TemplateObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
final class EloquentBuilderForwardsCallsExtension implements MethodsClassReflectionExtension
{
/** @var array<string, MethodReflection> */
private $cache = [];
/** @var BuilderHelper */
private $builderHelper;
/** @var ReflectionProvider */
private $reflectionProvider;
public function __construct(BuilderHelper $builderHelper, ReflectionProvider $reflectionProvider)
{
$this->builderHelper = $builderHelper;
$this->reflectionProvider = $reflectionProvider;
}
/**
* @throws ShouldNotHappenException
* @throws MissingMethodFromReflectionException
*/
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if (array_key_exists($classReflection->getCacheKey().'-'.$methodName, $this->cache)) {
return true;
}
$methodReflection = $this->findMethod($classReflection, $methodName);
if ($methodReflection !== null && $classReflection->isGeneric()) {
$this->cache[$classReflection->getCacheKey().'-'.$methodName] = $methodReflection;
return true;
}
return false;
}
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
return $this->cache[$classReflection->getCacheKey().'-'.$methodName];
}
/**
* @throws MissingMethodFromReflectionException
* @throws ShouldNotHappenException
*/
private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection
{
if ($classReflection->getName() !== EloquentBuilder::class && ! $classReflection->isSubclassOf(EloquentBuilder::class)) {
return null;
}
/** @var Type|TemplateMixedType|null $modelType */
$modelType = $classReflection->getActiveTemplateTypeMap()->getType('TModelClass');
// Generic type is not specified
if ($modelType === null) {
return null;
}
if ($modelType instanceof TemplateObjectType) {
$modelType = $modelType->getBound();
if ($modelType->equals(new ObjectType(Model::class))) {
return null;
}
}
if ($modelType instanceof TypeWithClassName) {
$modelReflection = $modelType->getClassReflection();
} else {
$modelReflection = $this->reflectionProvider->getClass(Model::class);
}
if ($modelReflection === null) {
return null;
}
$ref = $this->builderHelper->searchOnEloquentBuilder($classReflection, $methodName, $modelReflection);
if ($ref === null) {
// Special case for `SoftDeletes` trait
if (
in_array($methodName, ['withTrashed', 'onlyTrashed', 'withoutTrashed'], true) &&
in_array(SoftDeletes::class, array_keys($modelReflection->getTraits(true)))
) {
$ref = $this->reflectionProvider->getClass(SoftDeletes::class)->getMethod($methodName, new OutOfClassScope());
return new EloquentBuilderMethodReflection(
$methodName,
$classReflection,
$ref,
ParametersAcceptorSelector::selectSingle($ref->getVariants())->getParameters(),
new GenericObjectType($classReflection->getName(), [$modelType]),
ParametersAcceptorSelector::selectSingle($ref->getVariants())->isVariadic()
);
}
return null;
}
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($ref->getVariants());
if (in_array($methodName, $this->builderHelper->passthru, true)) {
$returnType = $parametersAcceptor->getReturnType();
return new EloquentBuilderMethodReflection(
$methodName,
$classReflection,
$ref,
$parametersAcceptor->getParameters(),
$returnType,
$parametersAcceptor->isVariadic()
);
}
// Returning custom reflection
// to ensure return type is always `EloquentBuilder<Model>`
return new EloquentBuilderMethodReflection(
$methodName,
$classReflection,
$ref,
$parametersAcceptor->getParameters(),
new GenericObjectType($classReflection->getName(), [$modelType]),
$parametersAcceptor->isVariadic()
);
}
}

View File

@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Methods;
use Illuminate\Database\Eloquent\Model;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
use PHPStan\Reflection\ReflectionProvider;
/**
* @internal
*/
final class Extension implements MethodsClassReflectionExtension
{
/**
* @var Kernel
*/
private $kernel;
/** @var MethodReflection[] */
private $methodReflections = [];
public function __construct(PhpMethodReflectionFactory $methodReflectionFactory, ReflectionProvider $reflectionProvider, Kernel $kernel = null)
{
$this->kernel = $kernel ?? new Kernel($methodReflectionFactory, $reflectionProvider);
}
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if ($classReflection->getName() === Model::class) {
return false;
}
if (array_key_exists($methodName.'-'.$classReflection->getName(), $this->methodReflections)) {
return true;
}
$passable = $this->kernel->handle($classReflection, $methodName);
$found = $passable->hasFound();
if ($found) {
$this->methodReflections[$methodName.'-'.$classReflection->getName()] = $passable->getMethodReflection();
}
return $found;
}
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
return $this->methodReflections[$methodName.'-'.$classReflection->getName()];
}
}

View File

@ -1,149 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Methods;
use Flarum\PHPStan\Support\HigherOrderCollectionProxyHelper;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\TrinaryLogic;
use PHPStan\Type;
final class HigherOrderCollectionProxyExtension implements MethodsClassReflectionExtension
{
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
return HigherOrderCollectionProxyHelper::hasPropertyOrMethod($classReflection, $methodName, 'method');
}
public function getMethod(
ClassReflection $classReflection,
string $methodName
): MethodReflection {
$activeTemplateTypeMap = $classReflection->getActiveTemplateTypeMap();
/** @var Type\Constant\ConstantStringType $methodType */
$methodType = $activeTemplateTypeMap->getType('T');
/** @var Type\ObjectType $valueType */
$valueType = $activeTemplateTypeMap->getType('TValue');
$modelMethodReflection = $valueType->getMethod($methodName, new OutOfClassScope());
$modelMethodReturnType = ParametersAcceptorSelector::selectSingle($modelMethodReflection->getVariants())->getReturnType();
$returnType = HigherOrderCollectionProxyHelper::determineReturnType($methodType->getValue(), $valueType, $modelMethodReturnType);
return new class($classReflection, $methodName, $modelMethodReflection, $returnType) implements MethodReflection {
/** @var ClassReflection */
private $classReflection;
/** @var string */
private $methodName;
/** @var MethodReflection */
private $modelMethodReflection;
/** @var Type\Type */
private $returnType;
public function __construct(ClassReflection $classReflection, string $methodName, MethodReflection $modelMethodReflection, Type\Type $returnType)
{
$this->classReflection = $classReflection;
$this->methodName = $methodName;
$this->modelMethodReflection = $modelMethodReflection;
$this->returnType = $returnType;
}
public function getDeclaringClass(): \PHPStan\Reflection\ClassReflection
{
return $this->classReflection;
}
public function isStatic(): bool
{
return false;
}
public function isPrivate(): bool
{
return false;
}
public function isPublic(): bool
{
return true;
}
public function getDocComment(): ?string
{
return null;
}
public function getName(): string
{
return $this->methodName;
}
public function getPrototype(): \PHPStan\Reflection\ClassMemberReflection
{
return $this;
}
public function getVariants(): array
{
return [
new FunctionVariant(
ParametersAcceptorSelector::selectSingle($this->modelMethodReflection->getVariants())->getTemplateTypeMap(),
ParametersAcceptorSelector::selectSingle($this->modelMethodReflection->getVariants())->getResolvedTemplateTypeMap(),
ParametersAcceptorSelector::selectSingle($this->modelMethodReflection->getVariants())->getParameters(),
ParametersAcceptorSelector::selectSingle($this->modelMethodReflection->getVariants())->isVariadic(),
$this->returnType
),
];
}
public function isDeprecated(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getDeprecatedDescription(): ?string
{
return null;
}
public function isFinal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function isInternal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getThrowType(): ?\PHPStan\Type\Type
{
return null;
}
public function hasSideEffects(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}
};
}
}

View File

@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\PHPStan\Methods;
use Illuminate\Support\HigherOrderTapProxy;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Type\ObjectType;
final class HigherOrderTapProxyExtension implements MethodsClassReflectionExtension
{
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if ($classReflection->getName() !== HigherOrderTapProxy::class) {
return false;
}
$templateTypeMap = $classReflection->getActiveTemplateTypeMap();
$templateType = $templateTypeMap->getType('TClass');
if (! $templateType instanceof ObjectType) {
return false;
}
if ($templateType->getClassReflection() === null) {
return false;
}
return $templateType->hasMethod($methodName)->yes();
}
public function getMethod(
ClassReflection $classReflection,
string $methodName
): MethodReflection {
/** @var ObjectType $templateType */
$templateType = $classReflection->getActiveTemplateTypeMap()->getType('TClass');
/** @var ClassReflection $reflection */
$reflection = $templateType->getClassReflection();
return $reflection->getMethod($methodName, new OutOfClassScope());
}
}

Some files were not shown because too many files have changed in this diff Show More