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]
indent_size = 2
[*.neon]
indent_style = tab

View File

@ -9,6 +9,12 @@ on:
default: true
required: false
enable_phpstan:
description: "Enable PHPStan Static Analysis?"
type: boolean
default: false
required: false
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.
type: string
@ -130,3 +136,35 @@ jobs:
working-directory: ${{ inputs.backend_directory }}
env:
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": {
"mockery/mockery": "^1.4",
"phpunit/phpunit": "^9.0",
"phpstan/phpstan-php-parser": "^1.0",
"phpstan/phpstan": "^1.2"
"phpstan/phpstan": "^1.2",
"nunomaduro/larastan": "^1.0"
},
"config": {
"sort-packages": true
@ -178,5 +178,11 @@
"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\User\User;
use Illuminate\Contracts\Container\Container;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Uri;
use Laminas\Stratigility\MiddlewarePipeInterface;
@ -26,12 +25,12 @@ class Client
protected $pipe;
/**
* @var User
* @var User|null
*/
protected $actor;
/**
* @var ServerRequestInterface
* @var ServerRequestInterface|null
*/
protected $parent;
@ -45,9 +44,6 @@ class Client
*/
protected $body = [];
/**
* @param Container $container
*/
public function __construct(MiddlewarePipeInterface $pipe)
{
$this->pipe = $pipe;

View File

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

View File

@ -113,15 +113,15 @@ class ListNotificationsController extends AbstractListController
$ids = [];
foreach ($notifications as $notification) {
if ($notification->subject && $notification->subject->discussion_id) {
if ($notification->subject && isset($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) {
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));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,8 @@ use LogicException;
* 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
* queried, eager loaded, and accessed as an attribute.
*
* @property-read int|null $id
*/
abstract class AbstractModel extends Eloquent
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,9 @@ use Flarum\Database\AbstractModel;
use Flarum\Http\SlugDriverInterface;
use Flarum\User\User;
/**
* @implements SlugDriverInterface<Discussion>
*/
class IdWithTransliteratedSlugDriver implements SlugDriverInterface
{
/**
@ -25,6 +28,9 @@ class IdWithTransliteratedSlugDriver implements SlugDriverInterface
$this->discussions = $discussions;
}
/**
* @param Discussion $instance
*/
public function toSlug(AbstractModel $instance): string
{
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->orderBy('posts_ft.score', 'desc');
});
return true;
}
}

View File

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

View File

@ -76,7 +76,7 @@ class ErrorHandling implements ExtenderInterface
* contain "details" - arbitrary data with more context for to the error.
*
* @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
*/
public function handler(string $exceptionClass, string $handlerClass): self

View File

@ -110,7 +110,7 @@ class Frontend implements ExtenderInterface
/**
* 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:
* - \Flarum\Frontend\Document $document

View File

@ -31,7 +31,7 @@ class LanguagePack implements ExtenderInterface, LifecycleInterface
/**
* 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')
{
@ -115,7 +115,7 @@ class LanguagePack implements ExtenderInterface, LifecycleInterface
return true;
}
/** @var ExtensionManager $extensions */
/** @var ExtensionManager|null $extensions */
static $extensions;
$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.
* 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.
* @return self
*/
@ -77,7 +77,7 @@ class Middleware implements ExtenderInterface
*
* @param string $originalMiddleware: ::class attribute of the original middleware class.
* 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.
* @return self
*/
@ -93,7 +93,7 @@ class Middleware implements ExtenderInterface
*
* @param string $originalMiddleware: ::class attribute of the original middleware class.
* 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.
* @return self
*/

View File

@ -13,6 +13,7 @@ use Flarum\Extension\Extension;
use Flarum\Foundation\Paths;
use Illuminate\Contracts\Container\Container;
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.
@ -65,7 +66,7 @@ class View implements ExtenderInterface, LifecycleInterface
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) {
$view->addNamespace($namespace, $hints);
}

View File

@ -25,6 +25,6 @@ class ExtensionBootError extends Exception
$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
* of installed extensions. Used to figure out which dependencies
* 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
*/
public function calculateDependencies($extensionSet)
@ -489,6 +486,7 @@ class Extension implements Arrayable
}
/**
* @return int|void
* @internal
*/
public function migrate(Migrator $migrator, $direction = 'up')
@ -498,7 +496,7 @@ class Extension implements Arrayable
}
if ($direction == 'up') {
return $migrator->run($this->getPath().'/migrations', $this);
$migrator->run($this->getPath().'/migrations', $this);
} else {
return $migrator->reset($this->getPath().'/migrations', $this);
}

View File

@ -316,7 +316,7 @@ class ExtensionManager
*
* @param Extension $extension
* @param string $direction
* @return void
* @return int
*
* @internal
*/
@ -326,20 +326,20 @@ class ExtensionManager
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.
*
* @param Extension $extension
* @return array Notes from the migrator.
* @return void
*
* @internal
*/
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 mixed|null $limit
* @param int|null $limit
* @param int $offset
*
* @return QueryResults

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Compiler\JsCompiler;
use Flarum\Frontend\Compiler\LessCompiler;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Filesystem\Cloud;
/**
* A factory class for creating frontend asset compilers.
@ -38,7 +38,7 @@ class Assets
protected $name;
/**
* @var Filesystem
* @var Cloud
*/
protected $assetsDir;
@ -67,7 +67,7 @@ class Assets
*/
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->assetsDir = $assetsDir;
@ -200,12 +200,12 @@ class Assets
$this->name = $name;
}
public function getAssetsDir(): Filesystem
public function getAssetsDir(): Cloud
{
return $this->assetsDir;
}
public function setAssetsDir(Filesystem $assetsDir)
public function setAssetsDir(Cloud $assetsDir)
{
$this->assetsDir = $assetsDir;
}

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ namespace Flarum\Frontend\Compiler;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\Compiler\Source\SourceInterface;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Filesystem\Cloud;
/**
* @internal
@ -21,9 +21,10 @@ class RevisionCompiler implements CompilerInterface
const EMPTY_REVISION = 'empty';
/**
* @var Filesystem
* @var Cloud
*/
protected $assetsDir;
/**
* @var VersionerInterface
*/
@ -40,11 +41,11 @@ class RevisionCompiler implements CompilerInterface
protected $sourcesCallbacks = [];
/**
* @param Filesystem $assetsDir
* @param Cloud $assetsDir
* @param string $filename
* @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->filename = $filename;
@ -71,7 +72,7 @@ class RevisionCompiler implements CompilerInterface
// In case the previous and current revisions do not match
// 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 no file was written (because the sources were empty), we
// will set the revision to a special value so that we can tell
@ -145,7 +146,6 @@ class RevisionCompiler implements CompilerInterface
/**
* @param SourceInterface[] $sources
* @return string
*/
protected function compile(array $sources): string
{
@ -158,10 +158,6 @@ class RevisionCompiler implements CompilerInterface
return $output;
}
/**
* @param string $string
* @return string
*/
protected function format(string $string): string
{
return $string;
@ -169,7 +165,6 @@ class RevisionCompiler implements CompilerInterface
/**
* @param SourceInterface[] $sources
* @return string
*/
protected function calculateRevision(array $sources): string
{
@ -196,12 +191,9 @@ class RevisionCompiler implements CompilerInterface
}
}
/**
* @param string $file
*/
protected function delete(string $file)
{
if ($this->assetsDir->has($file)) {
if ($this->assetsDir->exists($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 View
*/
protected function makeLayout(): View
protected function makeLayout(): ?View
{
if ($this->layoutView) {
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.
*
* @return Builder
* @return Builder<Group>
*/
public function query()
{
@ -29,14 +29,14 @@ class GroupRepository
* user, or throw an exception.
*
* @param int $id
* @param User $actor
* @return \Flarum\Group\Group
* @param User|null $actor
* @return Group
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFail($id, User $actor = null)
{
$query = Group::where('id', $id);
$query = $this->query()->where('id', $id);
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.
*
* @param Builder $query
* @param User $actor
* @return Builder
* @param Builder<Group> $query
* @param User|null $actor
* @return Builder<Group>
*/
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 string $type
* @property string $title
* @property string $last_ip_address
* @property string $last_user_agent
* @property string|null $last_ip_address
* @property string|null $last_user_agent
* @property \Flarum\User\User|null $user
*/
class AccessToken extends AbstractModel

View File

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

View File

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

View File

@ -11,6 +11,7 @@ namespace Flarum\Http;
use Flarum\Foundation\ErrorHandling\LogReporter;
use Flarum\Foundation\SiteInterface;
use Illuminate\Contracts\Container\Container;
use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\ServerRequestFactory;
@ -50,7 +51,7 @@ class Server
* We catch all exceptions happening during this process and format them to
* prevent exposure of sensitive information.
*
* @return \Psr\Http\Server\RequestHandlerInterface
* @return \Psr\Http\Server\RequestHandlerInterface|void
*/
private function safelyBootAndGetHandler()
{
@ -80,7 +81,9 @@ class Server
*/
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
// Since the config is loaded very early, it is very likely to be available from the container
$message = $error->getMessage();
@ -96,12 +99,12 @@ class Server
<pre>$error</pre>
ERROR;
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
// 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,
// 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.';
exit(1);

View File

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

View File

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

View File

@ -13,6 +13,7 @@ use Flarum\Console\AbstractCommand;
use Flarum\Install\Installation;
use Flarum\Install\Pipeline;
use Flarum\Install\Step;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends AbstractCommand
@ -83,7 +84,9 @@ class InstallCommand extends AbstractCommand
if ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input);
} 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;
/**
* @param Factory $view
* @param Installation $installation
*/
public function __construct(Factory $view, Installation $installation)
{
$this->view = $view;
@ -37,7 +33,6 @@ class IndexController extends AbstractHtmlController
}
/**
* @param Request $request
* @return \Illuminate\Contracts\Support\Renderable
*/
public function render(Request $request)

View File

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

View File

@ -60,6 +60,11 @@ class EnableBundledExtensions implements Step
*/
private $enabledExtensions;
/**
* @var Migrator|null
*/
private $migrator;
public function __construct(ConnectionInterface $database, $vendorPath, $assetPath, $enabledExtensions = null)
{
$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(
new DatabaseMigrationRepository($this->database, 'migrations'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ namespace Flarum\Notification;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Mail\Message;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -23,7 +24,7 @@ class NotificationMailer
protected $mailer;
/**
* @var TranslatorInterface
* @var TranslatorInterface&Translator
*/
protected $translator;
@ -33,9 +34,7 @@ class NotificationMailer
protected $settings;
/**
* @param Mailer $mailer
* @param TranslatorInterface $translator
* @param SettingsRepositoryInterface $settings
* @param TranslatorInterface&Translator $translator
*/
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)
{
$primaries = Notification::selectRaw('MAX(id) AS id')
$primaries = Notification::query()
->selectRaw('MAX(id) AS id')
->selectRaw('SUM(read_at IS NULL) AS unread_count')
->where('user_id', $user->id)
->whereIn('type', $user->getAlertableNotificationTypes())
@ -35,7 +36,8 @@ class NotificationRepository
->skip($offset)
->take($limit);
return Notification::select('notifications.*', 'p.unread_count')
return Notification::query()
->select('notifications.*', 'p.unread_count')
->joinSub($primaries, 'p', 'notifications.id', '=', 'p.id')
->latest()
->get();

View File

@ -23,7 +23,7 @@ interface MergeableInterface
* passed model.
*
* @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,
* 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\Post\Event\Deleted;
use Flarum\User\User;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
/**
* @property int $id
* @property int $discussion_id
* @property int $number
* @property int|Expression $number
* @property \Carbon\Carbon $created_at
* @property int|null $user_id
* @property string|null $type
@ -94,8 +93,8 @@ class Post extends AbstractModel
static::creating(function (self $post) {
$post->type = $post::$type;
/** @var ConnectionInterface $db */
$db = static::getConnectionResolver();
$db = static::getConnectionResolver()->connection();
$post->number = new Expression('('.
$db->table('posts', 'pn')
->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.
*
* @return Builder
* @return Builder<Post>
*/
public function query()
{
@ -27,7 +27,7 @@ class PostRepository
/**
* @param User|null $user
* @return Builder
* @return Builder<Post>
*/
protected function queryVisibleTo(User $user = null)
{
@ -45,8 +45,8 @@ class PostRepository
* user, or throw an exception.
*
* @param int $id
* @param \Flarum\User\User $actor
* @return \Flarum\Post\Post
* @param \Flarum\User\User|null $actor
* @return Post
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
@ -126,7 +126,7 @@ class PostRepository
/**
* @param array $ids
* @param User|null $actor
* @return Builder
* @return Builder<Post>
*/
protected function queryIds(array $ids, User $actor = null)
{

View File

@ -42,9 +42,9 @@ class ExceptionHandler implements ExceptionHandling
*
* @param \Illuminate\Http\Request $request
* @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.
}

View File

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

View File

@ -14,6 +14,7 @@ use Flarum\Foundation\Config;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\Paths;
use Illuminate\Container\Container as ContainerImplementation;
use Illuminate\Contracts\Cache\Factory as CacheFactory;
use Illuminate\Contracts\Container\Container;
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
// 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->setContainer($container);
@ -90,7 +91,12 @@ class QueueServiceProvider extends AbstractServiceProvider
// Bind a simple cache manager that returns the cache store.
$this->container->singleton('cache', function (Container $container) {
return new class($container) implements CacheFactory {
public function __construct($container)
/**
* @var Container
*/
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}

View File

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

View File

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

View File

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

View File

@ -28,10 +28,6 @@ class IndexController extends AbstractHtmlController
$this->view = $view;
}
/**
* @param Request $request
* @return \Psr\Http\Message\ResponseInterface
*/
public function render(Request $request)
{
$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
*/
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
if (method_exists($this, $ability)) {
$result = $this->sanitizeResult(call_user_func_array([$this, $ability], [$actor, $instance]));
@ -73,7 +71,7 @@ abstract class AbstractPolicy
* `return SOME_BOOLEAN_LOGIC;
*
* @param mixed $result
* @return string|void
* @return string|void|null
*/
public function sanitizeResult($result)
{

View File

@ -54,12 +54,6 @@ class RegisterUserHandler
*/
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)
{
$this->events = $events;
@ -91,6 +85,7 @@ class RegisterUserHandler
// If a valid authentication token was provided as an attribute,
// then we won't require the user to choose a password.
if (isset($data['attributes']['token'])) {
/** @var RegistrationToken $token */
$token = RegistrationToken::validOrFail($data['attributes']['token']);
$password = $password ?: Str::random(20);

View File

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

View File

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

View File

@ -21,6 +21,8 @@ use Illuminate\Support\Str;
* @property array $user_attributes
* @property array $payload
* @property \Carbon\Carbon $created_at
*
* @method static self validOrFail(string $token)
*/
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.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder<self> $query
* @param string $token
*
* @throws InvalidConfirmationTokenException
@ -83,7 +85,7 @@ class RegistrationToken extends AbstractModel
*/
public function scopeValidOrFail($query, string $token)
{
/** @var RegistrationToken $token */
/** @var RegistrationToken|null $token */
$token = $query->find($token);
if (! $token || $token->created_at->lessThan(Carbon::now()->subDay())) {

View File

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

View File

@ -109,7 +109,7 @@ class User extends AbstractModel
/**
* The access gate.
*
* @var Gate
* @var Access\Gate
*/
protected static $gate;
@ -172,7 +172,7 @@ class User extends AbstractModel
}
/**
* @param Gate $gate
* @param Access\Gate $gate
*/
public static function setGate($gate)
{
@ -296,7 +296,7 @@ class User extends AbstractModel
/**
* Change the path of the user avatar.
*
* @param string $path
* @param string|null $path
* @return $this
*/
public function changeAvatarPath($path)
@ -311,7 +311,6 @@ class User extends AbstractModel
/**
* Get the URL of the user's avatar.
*
* @todo Allow different storage locations to be used
* @param string|null $value
* @return string
*/
@ -410,15 +409,6 @@ class User extends AbstractModel
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
* to their preferences.
@ -482,7 +472,7 @@ class User extends AbstractModel
* Get the values of all registered preferences for this user, by
* transforming their stored preferences and merging them with the defaults.
*
* @param string $value
* @param string|null $value
* @return array
*/
public function getPreferencesAttribute($value)
@ -679,7 +669,7 @@ class User extends AbstractModel
/**
* 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()
{
@ -838,7 +828,7 @@ class User extends AbstractModel
* Register a callback that processes a user's list of groups.
*
* @param callable $callback
* @return array $groupIds
* @return void
*
* @internal
*/

View File

@ -16,7 +16,7 @@ class UserRepository
/**
* Get a new query builder for the users table.
*
* @return \Illuminate\Database\Eloquent\Builder
* @return Builder<User>
*/
public function query()
{
@ -27,15 +27,15 @@ class UserRepository
* Find a user by ID, optionally making sure it is visible to a certain
* user, or throw an exception.
*
* @param int $id
* @param User $actor
* @param int|string $id
* @param User|null $actor
* @return User
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFail($id, User $actor = null)
{
$query = User::where('id', $id);
$query = $this->query()->where('id', $id);
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
* user, or throw an exception.
*
* @param int $id
* @param User $actor
* @param string $username
* @param User|null $actor
* @return User
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFailByUsername($username, User $actor = null)
{
$query = User::where('username', $username);
$query = $this->query()->where('username', $username);
return $this->scopeVisibleTo($query, $actor)->firstOrFail();
}
@ -67,7 +67,7 @@ class UserRepository
{
$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)
{
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)
{
$query = User::where('username', $username);
$query = $this->query()->where('username', $username);
return $this->scopeVisibleTo($query, $actor)->value('id');
}
@ -107,7 +107,7 @@ class UserRepository
{
$string = $this->escapeLikeString($string);
$query = User::where('username', 'like', '%'.$string.'%')
$query = $this->query()->where('username', 'like', '%'.$string.'%')
->orderByRaw('username = ? 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.
*
* @param Builder $query
* @param User $actor
* @return Builder
* @param Builder<User> $query
* @param User|null $actor
* @return Builder<User>
*/
protected function scopeVisibleTo(Builder $query, User $actor = null)
{

View File

@ -109,6 +109,9 @@ class UserServiceProvider extends AbstractServiceProvider
User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $container));
}
/**
* @var \Illuminate\Container\Container $container
*/
User::setHasher($container->make('hash'));
User::setPasswordCheckers($container->make('flarum.user.password_checkers'));
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
{
/**
* @var User
* @var User|null
*/
protected $user;

View File

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

View File

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

View File

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

View File

@ -1,291 +1,17 @@
includes:
- vendor/phpstan/phpstan-php-parser/extension.neon
- larastan-extension.neon
- phpstan-baseline.neon
parameters:
stubFiles:
- stubs/Illuminate/Enumerable.stub
- stubs/Illuminate/Database/EloquentBuilder.stub
- stubs/Illuminate/Collection.stub
- stubs/Illuminate/Database/EloquentCollection.stub
- stubs/Illuminate/Database/Factory.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: []
stubFiles:
- stubs/Illuminate/Contracts/Container/Container.stub
- stubs/Illuminate/Queue/ListenerOptions.stub
- stubs/Illuminate/Support/ServiceProvider.stub
- stubs/Illuminate/Filesystem/Filesystem.stub
- stubs/Illuminate/Filesystem/FilesystemManager.stub
parametersSchema:
databaseMigrationsPath: listOf(string())
checkModelProperties: bool()
services:
-
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
# We're changing the disk return type from Filesystem to Cloud,
# rather than hacking every bit of the codebase with a phpdoc @var.
- stubs/Illuminate/Contracts/Filesystem/Factory.stub
- stubs/Illuminate/Contracts/Filesystem/Cloud.stub
- stubs/Illuminate/Contracts/Filesystem/Filesystem.stub

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