mirror of
https://github.com/flarum/framework.git
synced 2025-03-15 00:05:12 +08:00
Pushing latest stuff
This commit is contained in:
parent
05aa62f70c
commit
853926ce0b
@ -7,11 +7,19 @@
|
||||
"phpstan/phpstan-php-parser": "^1.0",
|
||||
"phpstan/phpstan": "^1.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,3 +36,256 @@ parameters:
|
||||
excludePaths:
|
||||
- *.blade.php
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
checkModelProperties: false
|
||||
databaseMigrationsPath: []
|
||||
|
||||
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
|
||||
|
65
php-packages/phpstan/src/Concerns/HasContainer.php
Normal file
65
php-packages/phpstan/src/Concerns/HasContainer.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
24
php-packages/phpstan/src/Concerns/LoadsAuthModel.php
Normal file
24
php-packages/phpstan/src/Concerns/LoadsAuthModel.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of PhpStorm.
|
||||
*
|
||||
* (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
*
|
||||
* For the full 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;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of PhpStorm.
|
||||
*
|
||||
* (c) Nuno Maduro <enunomaduro@gmail.com>
|
||||
*
|
||||
* For the full 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;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
}
|
231
php-packages/phpstan/src/Methods/BuilderHelper.php
Normal file
231
php-packages/phpstan/src/Methods/BuilderHelper.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Reflection\AnnotationScopeMethodParameterReflection;
|
||||
use Flarum\PHPStan\Reflection\AnnotationScopeMethodReflection;
|
||||
use Flarum\PHPStan\Reflection\DynamicWhereParameterReflection;
|
||||
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
|
||||
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());
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
57
php-packages/phpstan/src/Methods/Extension.php
Normal file
57
php-packages/phpstan/src/Methods/Extension.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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()];
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
72
php-packages/phpstan/src/Methods/Kernel.php
Normal file
72
php-packages/phpstan/src/Methods/Kernel.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Kernel
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
/**
|
||||
* @var PhpMethodReflectionFactory
|
||||
*/
|
||||
private $methodReflectionFactory;
|
||||
/**
|
||||
* @var ReflectionProvider
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
/**
|
||||
* Kernel constructor.
|
||||
*
|
||||
* @param PhpMethodReflectionFactory $methodReflectionFactory
|
||||
*/
|
||||
public function __construct(
|
||||
PhpMethodReflectionFactory $methodReflectionFactory,
|
||||
ReflectionProvider $reflectionProvider
|
||||
) {
|
||||
$this->methodReflectionFactory = $methodReflectionFactory;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
* @return PassableContract
|
||||
*/
|
||||
public function handle(ClassReflection $classReflection, string $methodName): PassableContract
|
||||
{
|
||||
$pipeline = new Pipeline($this->getContainer());
|
||||
|
||||
$passable = new Passable($this->methodReflectionFactory, $this->reflectionProvider, $pipeline, $classReflection, $methodName);
|
||||
|
||||
$pipeline->send($passable)
|
||||
->through(
|
||||
[
|
||||
Pipes\SelfClass::class,
|
||||
Pipes\Macros::class,
|
||||
Pipes\Contracts::class,
|
||||
Pipes\Facades::class,
|
||||
Pipes\Managers::class,
|
||||
Pipes\Auths::class,
|
||||
]
|
||||
)
|
||||
->then(
|
||||
function ($method) {
|
||||
}
|
||||
);
|
||||
|
||||
return $passable;
|
||||
}
|
||||
}
|
259
php-packages/phpstan/src/Methods/Macro.php
Normal file
259
php-packages/phpstan/src/Methods/Macro.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use function array_map;
|
||||
use Closure;
|
||||
use ErrorException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\PassedByReference;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypehintHelper;
|
||||
use ReflectionFunction;
|
||||
use ReflectionParameter;
|
||||
use ReflectionType;
|
||||
use stdClass;
|
||||
|
||||
final class Macro implements MethodReflection
|
||||
{
|
||||
/**
|
||||
* @var ClassReflection
|
||||
*/
|
||||
private $classReflection;
|
||||
|
||||
/**
|
||||
* The method name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* The reflection function.
|
||||
*
|
||||
* @var ReflectionFunction
|
||||
*/
|
||||
private $reflectionFunction;
|
||||
|
||||
/**
|
||||
* The parameters.
|
||||
*
|
||||
* @var ReflectionParameter[]
|
||||
*/
|
||||
private $parameters;
|
||||
|
||||
/**
|
||||
* The is static.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isStatic = false;
|
||||
|
||||
/**
|
||||
* Map of macro methods and thrown exception classes.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $methodThrowTypeMap = [
|
||||
'validate' => ValidationException::class,
|
||||
'validateWithBag' => ValidationException::class,
|
||||
];
|
||||
|
||||
public function __construct(ClassReflection $classReflection, string $methodName, ReflectionFunction $reflectionFunction)
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
$this->methodName = $methodName;
|
||||
$this->reflectionFunction = $reflectionFunction;
|
||||
$this->parameters = $this->reflectionFunction->getParameters();
|
||||
|
||||
if ($this->reflectionFunction->isClosure()) {
|
||||
try {
|
||||
/** @var Closure $closure */
|
||||
$closure = $this->reflectionFunction->getClosure();
|
||||
Closure::bind($closure, new stdClass);
|
||||
// The closure can be bound so it was not explicitly marked as static
|
||||
} catch (ErrorException $e) {
|
||||
// The closure was explicitly marked as static
|
||||
$this->isStatic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->classReflection;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isFinal(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function isInternal(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return $this->isStatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the is static value.
|
||||
*
|
||||
* @param bool $isStatic
|
||||
* @return void
|
||||
*/
|
||||
public function setIsStatic(bool $isStatic): void
|
||||
{
|
||||
$this->isStatic = $isStatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return $this->reflectionFunction->getDocComment() ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/** @return ParameterReflection[] */
|
||||
public function getParameters(): array
|
||||
{
|
||||
return array_map(function (ReflectionParameter $reflection): ParameterReflection {
|
||||
return new class($reflection) implements ParameterReflection
|
||||
{
|
||||
/**
|
||||
* @var ReflectionParameter
|
||||
*/
|
||||
private $reflection;
|
||||
|
||||
public function __construct(ReflectionParameter $reflection)
|
||||
{
|
||||
$this->reflection = $reflection;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflection->getName();
|
||||
}
|
||||
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return $this->reflection->isOptional();
|
||||
}
|
||||
|
||||
public function getType(): Type
|
||||
{
|
||||
$type = $this->reflection->getType();
|
||||
|
||||
if ($type === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return TypehintHelper::decideTypeFromReflection($this->reflection->getType());
|
||||
}
|
||||
|
||||
public function passedByReference(): PassedByReference
|
||||
{
|
||||
return PassedByReference::createNo();
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->reflection->isVariadic();
|
||||
}
|
||||
|
||||
public function getDefaultValue(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}, $this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters value.
|
||||
*
|
||||
* @param ReflectionParameter[] $parameters
|
||||
* @return void
|
||||
*/
|
||||
public function setParameters(array $parameters): void
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function getReturnType(): ?ReflectionType
|
||||
{
|
||||
return $this->reflectionFunction->getReturnType();
|
||||
}
|
||||
|
||||
public function isDeprecated(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createFromBoolean($this->reflectionFunction->isDeprecated());
|
||||
}
|
||||
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return [
|
||||
new FunctionVariant(TemplateTypeMap::createEmpty(), null, $this->getParameters(), $this->reflectionFunction->isVariadic(), TypehintHelper::decideTypeFromReflection($this->getReturnType())),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getThrowType(): ?Type
|
||||
{
|
||||
if (array_key_exists($this->methodName, $this->methodThrowTypeMap)) {
|
||||
return new ObjectType($this->methodThrowTypeMap[$this->methodName]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
use PHPStan\Analyser\OutOfClassScope;
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
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\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class ModelFactoryMethodsClassReflectionExtension implements MethodsClassReflectionExtension
|
||||
{
|
||||
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
|
||||
{
|
||||
// Class only available on Laravel 8
|
||||
if (! class_exists('\Illuminate\Database\Eloquent\Factories\Factory')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $classReflection->isSubclassOf(Factory::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! Str::startsWith($methodName, ['for', 'has'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$relationship = Str::camel(Str::substr($methodName, 3));
|
||||
|
||||
$parent = $classReflection->getParentClass();
|
||||
|
||||
if ($parent === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$modelType = $parent->getActiveTemplateTypeMap()->getType('TModel');
|
||||
|
||||
if ($modelType === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $modelType->hasMethod($relationship)->yes();
|
||||
}
|
||||
|
||||
public function getMethod(
|
||||
ClassReflection $classReflection,
|
||||
string $methodName
|
||||
): MethodReflection {
|
||||
return new class($classReflection, $methodName) implements MethodReflection
|
||||
{
|
||||
/** @var ClassReflection */
|
||||
private $classReflection;
|
||||
|
||||
/** @var string */
|
||||
private $methodName;
|
||||
|
||||
public function __construct(ClassReflection $classReflection, string $methodName)
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): 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(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVariants(): array
|
||||
{
|
||||
$returnType = new ObjectType($this->classReflection->getName());
|
||||
$stateParameter = ParametersAcceptorSelector::selectSingle($this->classReflection->getMethod('state', new OutOfClassScope())->getVariants())->getParameters()[0];
|
||||
$countParameter = ParametersAcceptorSelector::selectSingle($this->classReflection->getMethod('count', new OutOfClassScope())->getVariants())->getParameters()[0];
|
||||
|
||||
$variants = [
|
||||
new FunctionVariant(TemplateTypeMap::createEmpty(), null, [], false, $returnType),
|
||||
];
|
||||
|
||||
if (Str::startsWith($this->methodName, 'for')) {
|
||||
$variants[] = new FunctionVariant(TemplateTypeMap::createEmpty(), null, [$stateParameter], false, $returnType);
|
||||
} else {
|
||||
$variants[] = new FunctionVariant(TemplateTypeMap::createEmpty(), null, [$countParameter], false, $returnType);
|
||||
$variants[] = new FunctionVariant(TemplateTypeMap::createEmpty(), null, [$stateParameter], false, $returnType);
|
||||
$variants[] = new FunctionVariant(TemplateTypeMap::createEmpty(), null, [$countParameter, $stateParameter], false, $returnType);
|
||||
}
|
||||
|
||||
return $variants;
|
||||
}
|
||||
|
||||
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(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
212
php-packages/phpstan/src/Methods/ModelForwardsCallsExtension.php
Normal file
212
php-packages/phpstan/src/Methods/ModelForwardsCallsExtension.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
|
||||
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\TrinaryLogic;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeTraverser;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
|
||||
final class ModelForwardsCallsExtension implements MethodsClassReflectionExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
/** @var EloquentBuilderForwardsCallsExtension */
|
||||
private $eloquentBuilderForwardsCallsExtension;
|
||||
|
||||
/** @var array<string, MethodReflection> */
|
||||
private $cache = [];
|
||||
|
||||
public function __construct(BuilderHelper $builderHelper, ReflectionProvider $reflectionProvider, EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->eloquentBuilderForwardsCallsExtension = $eloquentBuilderForwardsCallsExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingMethodFromReflectionException
|
||||
* @throws ShouldNotHappenException
|
||||
*/
|
||||
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) {
|
||||
$this->cache[$classReflection->getCacheKey().'-'.$methodName] = $methodReflection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
* @return MethodReflection
|
||||
*/
|
||||
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
|
||||
{
|
||||
return $this->cache[$classReflection->getCacheKey().'-'.$methodName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ShouldNotHappenException
|
||||
* @throws MissingMethodFromReflectionException
|
||||
*/
|
||||
private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection
|
||||
{
|
||||
if ($classReflection->getName() !== Model::class && ! $classReflection->isSubclassOf(Model::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builderName = $this->builderHelper->determineBuilderName($classReflection->getName());
|
||||
|
||||
if (in_array($methodName, ['increment', 'decrement'], true)) {
|
||||
$methodReflection = $classReflection->getNativeMethod($methodName);
|
||||
|
||||
return new class($classReflection, $methodName, $methodReflection) implements MethodReflection
|
||||
{
|
||||
/** @var ClassReflection */
|
||||
private $classReflection;
|
||||
|
||||
/** @var string */
|
||||
private $methodName;
|
||||
|
||||
/** @var MethodReflection */
|
||||
private $methodReflection;
|
||||
|
||||
public function __construct(ClassReflection $classReflection, string $methodName, MethodReflection $methodReflection)
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
$this->methodName = $methodName;
|
||||
$this->methodReflection = $methodReflection;
|
||||
}
|
||||
|
||||
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 $this->methodReflection->getVariants();
|
||||
}
|
||||
|
||||
public function isDeprecated(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isFinal(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function isInternal(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function getThrowType(): ?\PHPStan\Type\Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createYes();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([new ObjectType($classReflection->getName())]);
|
||||
$genericBuilderAndModelType = new GenericObjectType($builderName, [new ObjectType($classReflection->getName())]);
|
||||
|
||||
if ($builderReflection->hasNativeMethod($methodName)) {
|
||||
$reflection = $builderReflection->getNativeMethod($methodName);
|
||||
|
||||
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($reflection->getVariants());
|
||||
|
||||
$returnType = TypeTraverser::map($parametersAcceptor->getReturnType(), static function (Type $type, callable $traverse) use ($genericBuilderAndModelType) {
|
||||
if ($type instanceof TypeWithClassName && $type->getClassName() === Builder::class) {
|
||||
return $genericBuilderAndModelType;
|
||||
}
|
||||
|
||||
return $traverse($type);
|
||||
});
|
||||
|
||||
return new EloquentBuilderMethodReflection(
|
||||
$methodName, $classReflection,
|
||||
$reflection,
|
||||
$parametersAcceptor->getParameters(),
|
||||
$returnType,
|
||||
$parametersAcceptor->isVariadic()
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->eloquentBuilderForwardsCallsExtension->hasMethod($builderReflection, $methodName)) {
|
||||
return $this->eloquentBuilderForwardsCallsExtension->getMethod($builderReflection, $methodName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
31
php-packages/phpstan/src/Methods/ModelTypeHelper.php
Normal file
31
php-packages/phpstan/src/Methods/ModelTypeHelper.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\ObjectWithoutClassType;
|
||||
use PHPStan\Type\StaticType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeTraverser;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
|
||||
final class ModelTypeHelper
|
||||
{
|
||||
public static function replaceStaticTypeWithModel(Type $type, string $modelClass): Type
|
||||
{
|
||||
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($modelClass): Type {
|
||||
if ($type instanceof ObjectWithoutClassType || $type instanceof StaticType) {
|
||||
return new ObjectType($modelClass);
|
||||
}
|
||||
|
||||
if ($type instanceof TypeWithClassName && $type->getClassName() === Model::class) {
|
||||
return new ObjectType($modelClass);
|
||||
}
|
||||
|
||||
return $traverse($type);
|
||||
});
|
||||
}
|
||||
}
|
217
php-packages/phpstan/src/Methods/Passable.php
Normal file
217
php-packages/phpstan/src/Methods/Passable.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Contracts\Pipeline\Pipeline;
|
||||
use LogicException;
|
||||
use Mockery;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\Php\PhpMethodReflection;
|
||||
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Passable implements PassableContract
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
/**
|
||||
* @var \PHPStan\Reflection\Php\PhpMethodReflectionFactory
|
||||
*/
|
||||
private $methodReflectionFactory;
|
||||
|
||||
/**
|
||||
* @var ReflectionProvider
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Pipeline\Pipeline
|
||||
*/
|
||||
private $pipeline;
|
||||
|
||||
/**
|
||||
* @var \PHPStan\Reflection\ClassReflection
|
||||
*/
|
||||
private $classReflection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* @var \PHPStan\Reflection\MethodReflection|null
|
||||
*/
|
||||
private $methodReflection;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $staticAllowed = false;
|
||||
|
||||
/**
|
||||
* Method constructor.
|
||||
*
|
||||
* @param \PHPStan\Reflection\Php\PhpMethodReflectionFactory $methodReflectionFactory
|
||||
* @param ReflectionProvider $reflectionProvider
|
||||
* @param \Illuminate\Contracts\Pipeline\Pipeline $pipeline
|
||||
* @param \PHPStan\Reflection\ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
*/
|
||||
public function __construct(
|
||||
PhpMethodReflectionFactory $methodReflectionFactory,
|
||||
ReflectionProvider $reflectionProvider,
|
||||
Pipeline $pipeline,
|
||||
ClassReflection $classReflection,
|
||||
string $methodName
|
||||
) {
|
||||
$this->methodReflectionFactory = $methodReflectionFactory;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->pipeline = $pipeline;
|
||||
$this->classReflection = $classReflection;
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClassReflection(): ClassReflection
|
||||
{
|
||||
return $this->classReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setClassReflection(ClassReflection $classReflection): PassableContract
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMethodName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasFound(): bool
|
||||
{
|
||||
return $this->methodReflection !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function searchOn(string $class): bool
|
||||
{
|
||||
$classReflection = $this->reflectionProvider->getClass($class);
|
||||
|
||||
$found = $classReflection->hasNativeMethod($this->methodName);
|
||||
|
||||
if ($found) {
|
||||
$this->setMethodReflection($classReflection->getNativeMethod($this->methodName));
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMethodReflection(): MethodReflection
|
||||
{
|
||||
if ($this->methodReflection === null) {
|
||||
throw new LogicException("MethodReflection doesn't exist");
|
||||
}
|
||||
|
||||
return $this->methodReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMethodReflection(MethodReflection $methodReflection): void
|
||||
{
|
||||
$this->methodReflection = $methodReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStaticAllowed(bool $staticAllowed): void
|
||||
{
|
||||
$this->staticAllowed = $staticAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isStaticAllowed(): bool
|
||||
{
|
||||
return $this->staticAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendToPipeline(string $class, $staticAllowed = false): bool
|
||||
{
|
||||
$classReflection = $this->reflectionProvider->getClass($class);
|
||||
|
||||
$this->setStaticAllowed($this->staticAllowed ?: $staticAllowed);
|
||||
|
||||
$originalClassReflection = $this->classReflection;
|
||||
$this->pipeline->send($this->setClassReflection($classReflection))
|
||||
->then(
|
||||
function (PassableContract $passable) use ($originalClassReflection) {
|
||||
if ($passable->hasFound()) {
|
||||
$this->setMethodReflection($passable->getMethodReflection());
|
||||
$this->setStaticAllowed($passable->isStaticAllowed());
|
||||
}
|
||||
|
||||
$this->setClassReflection($originalClassReflection);
|
||||
}
|
||||
);
|
||||
|
||||
if ($result = $this->hasFound()) {
|
||||
$methodReflection = $this->getMethodReflection();
|
||||
if (get_class($methodReflection) === PhpMethodReflection::class) {
|
||||
$methodReflection = Mockery::mock($methodReflection);
|
||||
$methodReflection->shouldReceive('isStatic')
|
||||
->andReturn($this->isStaticAllowed());
|
||||
}
|
||||
|
||||
$this->setMethodReflection($methodReflection);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getReflectionProvider(): ReflectionProvider
|
||||
{
|
||||
return $this->reflectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMethodReflectionFactory(): PhpMethodReflectionFactory
|
||||
{
|
||||
return $this->methodReflectionFactory;
|
||||
}
|
||||
}
|
61
php-packages/phpstan/src/Methods/Pipes/Auths.php
Normal file
61
php-packages/phpstan/src/Methods/Pipes/Auths.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword;
|
||||
use function in_array;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Auths implements PipeContract
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
use Concerns\LoadsAuthModel;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $classes = [
|
||||
Authenticatable::class,
|
||||
CanResetPassword::class,
|
||||
Authorizable::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$classReflectionName = $passable->getClassReflection()
|
||||
->getName();
|
||||
|
||||
$found = false;
|
||||
|
||||
$config = $this->resolve('config');
|
||||
|
||||
if ($config !== null && in_array($classReflectionName, $this->classes, true)) {
|
||||
$authModel = $this->getAuthModel($config);
|
||||
|
||||
if ($authModel !== null) {
|
||||
$found = $passable->sendToPipeline($authModel);
|
||||
}
|
||||
} elseif ($classReflectionName === \Illuminate\Contracts\Auth\Factory::class || $classReflectionName === \Illuminate\Auth\AuthManager::class) {
|
||||
$found = $passable->sendToPipeline(
|
||||
\Illuminate\Contracts\Auth\Guard::class
|
||||
);
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
}
|
60
php-packages/phpstan/src/Methods/Pipes/Contracts.php
Normal file
60
php-packages/phpstan/src/Methods/Pipes/Contracts.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Closure;
|
||||
use function get_class;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Contracts implements PipeContract
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$found = false;
|
||||
|
||||
foreach ($this->concretes($passable->getClassReflection()) as $concrete) {
|
||||
if ($found = $passable->sendToPipeline($concrete)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \PHPStan\Reflection\ClassReflection $classReflection
|
||||
* @return class-string[]
|
||||
*/
|
||||
private function concretes(ClassReflection $classReflection): array
|
||||
{
|
||||
if ($classReflection->isInterface() && Str::startsWith($classReflection->getName(), 'Illuminate\Contracts')) {
|
||||
$concrete = $this->resolve($classReflection->getName());
|
||||
|
||||
if ($concrete !== null) {
|
||||
$class = get_class($concrete);
|
||||
|
||||
if ($class) {
|
||||
return [$class];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
59
php-packages/phpstan/src/Methods/Pipes/Facades.php
Normal file
59
php-packages/phpstan/src/Methods/Pipes/Facades.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Facades implements PipeContract
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$classReflection = $passable->getClassReflection();
|
||||
|
||||
$found = false;
|
||||
|
||||
if ($classReflection->isSubclassOf(Facade::class)) {
|
||||
$facadeClass = $classReflection->getName();
|
||||
|
||||
if ($concrete = $facadeClass::getFacadeRoot()) {
|
||||
$class = get_class($concrete);
|
||||
|
||||
if ($class) {
|
||||
$found = $passable->sendToPipeline($class, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found && Str::startsWith($passable->getMethodName(), 'assert')) {
|
||||
$fakeFacadeClass = $this->getFake($facadeClass);
|
||||
|
||||
if ($passable->getReflectionProvider()->hasClass($fakeFacadeClass)) {
|
||||
assert(class_exists($fakeFacadeClass));
|
||||
$found = $passable->sendToPipeline($fakeFacadeClass, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFake(string $facade): string
|
||||
{
|
||||
$shortClassName = substr($facade, strrpos($facade, '\\') + 1);
|
||||
|
||||
return sprintf('\\Illuminate\\Support\\Testing\\Fakes\\%sFake', $shortClassName);
|
||||
}
|
||||
}
|
96
php-packages/phpstan/src/Methods/Pipes/Macros.php
Normal file
96
php-packages/phpstan/src/Methods/Pipes/Macros.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Carbon\Traits\Macro as CarbonMacro;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
use Flarum\PHPStan\Methods\Macro;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Macros implements PipeContract
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
private function hasIndirectTraitUse(ClassReflection $class, string $traitName): bool
|
||||
{
|
||||
foreach ($class->getTraits() as $trait) {
|
||||
if ($this->hasIndirectTraitUse($trait, $traitName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $class->hasTraitUse($traitName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$classReflection = $passable->getClassReflection();
|
||||
|
||||
/** @var class-string $className */
|
||||
$className = null;
|
||||
$found = false;
|
||||
$macroTraitProperty = null;
|
||||
|
||||
if ($classReflection->isInterface() && Str::startsWith($classReflection->getName(), 'Illuminate\Contracts')) {
|
||||
/** @var object|null $concrete */
|
||||
$concrete = $this->resolve($classReflection->getName());
|
||||
|
||||
if ($concrete !== null) {
|
||||
$className = get_class($concrete);
|
||||
|
||||
if ($className && $passable->getReflectionProvider()
|
||||
->getClass($className)
|
||||
->hasTraitUse(Macroable::class)) {
|
||||
$macroTraitProperty = 'macros';
|
||||
}
|
||||
}
|
||||
} elseif ($classReflection->hasTraitUse(Macroable::class) || $classReflection->getName() === Builder::class) {
|
||||
$className = $classReflection->getName();
|
||||
$macroTraitProperty = 'macros';
|
||||
} elseif ($this->hasIndirectTraitUse($classReflection, CarbonMacro::class)) {
|
||||
$className = $classReflection->getName();
|
||||
$macroTraitProperty = 'globalMacros';
|
||||
}
|
||||
|
||||
if ($className !== null && $macroTraitProperty) {
|
||||
$classReflection = $passable->getReflectionProvider()->getClass($className);
|
||||
$refObject = new \ReflectionClass($className);
|
||||
$refProperty = $refObject->getProperty($macroTraitProperty);
|
||||
$refProperty->setAccessible(true);
|
||||
|
||||
$found = $className === Builder::class
|
||||
? $className::hasGlobalMacro($passable->getMethodName())
|
||||
: $className::hasMacro($passable->getMethodName());
|
||||
|
||||
if ($found) {
|
||||
$reflectionFunction = new \ReflectionFunction($refProperty->getValue()[$passable->getMethodName()]);
|
||||
|
||||
$methodReflection = new Macro(
|
||||
$classReflection, $passable->getMethodName(), $reflectionFunction
|
||||
);
|
||||
|
||||
$methodReflection->setIsStatic(true);
|
||||
|
||||
$passable->setMethodReflection($methodReflection);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
}
|
56
php-packages/phpstan/src/Methods/Pipes/Managers.php
Normal file
56
php-packages/phpstan/src/Methods/Pipes/Managers.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Manager;
|
||||
use InvalidArgumentException;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Managers implements PipeContract
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$classReflection = $passable->getClassReflection();
|
||||
|
||||
$found = false;
|
||||
|
||||
if ($classReflection->isSubclassOf(Manager::class)) {
|
||||
$driver = null;
|
||||
|
||||
$concrete = $this->resolve(
|
||||
$classReflection->getName()
|
||||
);
|
||||
|
||||
try {
|
||||
$driver = $concrete->driver();
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
// ..
|
||||
}
|
||||
|
||||
if ($driver !== null) {
|
||||
$class = get_class($driver);
|
||||
|
||||
if ($class) {
|
||||
$found = $passable->sendToPipeline($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
}
|
28
php-packages/phpstan/src/Methods/Pipes/SelfClass.php
Normal file
28
php-packages/phpstan/src/Methods/Pipes/SelfClass.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods\Pipes;
|
||||
|
||||
use Closure;
|
||||
use Flarum\PHPStan\Contracts\Methods\PassableContract;
|
||||
use Flarum\PHPStan\Contracts\Methods\Pipes\PipeContract;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SelfClass implements PipeContract
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(PassableContract $passable, Closure $next): void
|
||||
{
|
||||
$className = $passable->getClassReflection()
|
||||
->getName();
|
||||
|
||||
if (! $passable->searchOn($className)) {
|
||||
$next($passable);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Flarum\PHPStan\Reflection\EloquentBuilderMethodReflection;
|
||||
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\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
|
||||
final class RelationForwardsCallsExtension implements MethodsClassReflectionExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var array<string, MethodReflection> */
|
||||
private $cache = [];
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
/** @var EloquentBuilderForwardsCallsExtension */
|
||||
private $eloquentBuilderForwardsCallsExtension;
|
||||
|
||||
public function __construct(BuilderHelper $builderHelper, ReflectionProvider $reflectionProvider, EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->eloquentBuilderForwardsCallsExtension = $eloquentBuilderForwardsCallsExtension;
|
||||
}
|
||||
|
||||
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) {
|
||||
$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->isSubclassOf(Relation::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Type|TemplateMixedType|null $relatedModel */
|
||||
$relatedModel = $classReflection->getActiveTemplateTypeMap()->getType('TRelatedModel');
|
||||
|
||||
if ($relatedModel === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($relatedModel instanceof TypeWithClassName) {
|
||||
$modelReflection = $relatedModel->getClassReflection();
|
||||
} else {
|
||||
$modelReflection = $this->reflectionProvider->getClass(Model::class);
|
||||
}
|
||||
|
||||
if ($modelReflection === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builderName = $this->builderHelper->determineBuilderName($modelReflection->getName());
|
||||
|
||||
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([$relatedModel]);
|
||||
|
||||
if ($builderReflection->hasNativeMethod($methodName)) {
|
||||
$reflection = $builderReflection->getNativeMethod($methodName);
|
||||
} elseif ($this->eloquentBuilderForwardsCallsExtension->hasMethod($builderReflection, $methodName)) {
|
||||
$reflection = $this->eloquentBuilderForwardsCallsExtension->getMethod($builderReflection, $methodName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($reflection->getVariants());
|
||||
$returnType = $parametersAcceptor->getReturnType();
|
||||
|
||||
$types = [$relatedModel];
|
||||
|
||||
// BelongsTo relation needs second generic type
|
||||
if ($classReflection->getName() === BelongsTo::class) {
|
||||
$childType = $classReflection->getActiveTemplateTypeMap()->getType('TChildModel');
|
||||
|
||||
if ($childType !== null) {
|
||||
$types[] = $childType;
|
||||
}
|
||||
}
|
||||
|
||||
if ((new ObjectType(Builder::class))->isSuperTypeOf($returnType)->yes()) {
|
||||
return new EloquentBuilderMethodReflection(
|
||||
$methodName, $classReflection,
|
||||
$reflection, $parametersAcceptor->getParameters(),
|
||||
new GenericObjectType($classReflection->getName(), $types),
|
||||
$parametersAcceptor->isVariadic()
|
||||
);
|
||||
}
|
||||
|
||||
return new EloquentBuilderMethodReflection(
|
||||
$methodName, $classReflection,
|
||||
$reflection, $parametersAcceptor->getParameters(),
|
||||
$returnType,
|
||||
$parametersAcceptor->isVariadic()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Methods;
|
||||
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Filesystem\FilesystemManager;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Flarum\PHPStan\Reflection\StaticMethodReflection;
|
||||
use PHPStan\Analyser\OutOfClassScope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\MethodsClassReflectionExtension;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
||||
class StorageMethodsClassReflectionExtension implements MethodsClassReflectionExtension
|
||||
{
|
||||
/**
|
||||
* @var ReflectionProvider
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider)
|
||||
{
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
|
||||
{
|
||||
if ($classReflection->getName() !== Storage::class) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->reflectionProvider->getClass(FilesystemManager::class)->hasMethod($methodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->reflectionProvider->getClass(FilesystemAdapter::class)->hasMethod($methodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMethod(
|
||||
ClassReflection $classReflection,
|
||||
string $methodName
|
||||
): MethodReflection {
|
||||
if ($this->reflectionProvider->getClass(FilesystemManager::class)->hasMethod($methodName)) {
|
||||
return new StaticMethodReflection(
|
||||
$this->reflectionProvider->getClass(FilesystemManager::class)->getMethod($methodName, new OutOfClassScope())
|
||||
);
|
||||
}
|
||||
|
||||
return new StaticMethodReflection(
|
||||
$this->reflectionProvider->getClass(FilesystemAdapter::class)->getMethod($methodName, new OutOfClassScope())
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use Flarum\PHPStan\Support\HigherOrderCollectionProxyHelper;
|
||||
use PHPStan\Analyser\OutOfClassScope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\PropertiesClassReflectionExtension;
|
||||
use PHPStan\Reflection\PropertyReflection;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type;
|
||||
|
||||
final class HigherOrderCollectionProxyPropertyExtension implements PropertiesClassReflectionExtension
|
||||
{
|
||||
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
|
||||
{
|
||||
return HigherOrderCollectionProxyHelper::hasPropertyOrMethod($classReflection, $propertyName, 'property');
|
||||
}
|
||||
|
||||
public function getProperty(
|
||||
ClassReflection $classReflection,
|
||||
string $propertyName
|
||||
): PropertyReflection {
|
||||
$activeTemplateTypeMap = $classReflection->getActiveTemplateTypeMap();
|
||||
|
||||
/** @var Type\Constant\ConstantStringType $methodType */
|
||||
$methodType = $activeTemplateTypeMap->getType('T');
|
||||
|
||||
/** @var Type\ObjectType $modelType */
|
||||
$modelType = $activeTemplateTypeMap->getType('TValue');
|
||||
|
||||
$propertyType = $modelType->getProperty($propertyName, new OutOfClassScope())->getReadableType();
|
||||
|
||||
$returnType = HigherOrderCollectionProxyHelper::determineReturnType($methodType->getValue(), $modelType, $propertyType);
|
||||
|
||||
return new class($classReflection, $returnType) implements PropertyReflection
|
||||
{
|
||||
/** @var ClassReflection */
|
||||
private $classReflection;
|
||||
|
||||
/** @var Type\Type */
|
||||
private $returnType;
|
||||
|
||||
public function __construct(ClassReflection $classReflection, Type\Type $returnType)
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
$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 getReadableType(): Type\Type
|
||||
{
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
public function getWritableType(): Type\Type
|
||||
{
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
public function canChangeTypeAfterAssignment(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isDeprecated(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isInternal(): \PHPStan\TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
95
php-packages/phpstan/src/Properties/MigrationHelper.php
Normal file
95
php-packages/phpstan/src/Properties/MigrationHelper.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use PHPStan\File\FileHelper;
|
||||
use PHPStan\Parser\Parser;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
class MigrationHelper
|
||||
{
|
||||
/** @var Parser */
|
||||
private $parser;
|
||||
|
||||
/** @var string[] */
|
||||
private $databaseMigrationPath;
|
||||
|
||||
/** @var FileHelper */
|
||||
private $fileHelper;
|
||||
|
||||
/**
|
||||
* @param string[] $databaseMigrationPath
|
||||
*/
|
||||
public function __construct(Parser $parser, array $databaseMigrationPath, FileHelper $fileHelper)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
$this->databaseMigrationPath = $databaseMigrationPath;
|
||||
$this->fileHelper = $fileHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, SchemaTable>
|
||||
*/
|
||||
public function initializeTables(): array
|
||||
{
|
||||
if (empty($this->databaseMigrationPath)) {
|
||||
$this->databaseMigrationPath = [database_path('migrations')];
|
||||
}
|
||||
|
||||
$schemaAggregator = new SchemaAggregator();
|
||||
$filesArray = $this->getMigrationFiles();
|
||||
|
||||
if (empty($filesArray)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
ksort($filesArray);
|
||||
|
||||
$this->requireFiles($filesArray);
|
||||
|
||||
foreach ($filesArray as $file) {
|
||||
$schemaAggregator->addStatements($this->parser->parseFile($file->getPathname()));
|
||||
}
|
||||
|
||||
return $schemaAggregator->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SplFileInfo[]
|
||||
*/
|
||||
private function getMigrationFiles(): array
|
||||
{
|
||||
/** @var SplFileInfo[] $migrationFiles */
|
||||
$migrationFiles = [];
|
||||
|
||||
foreach ($this->databaseMigrationPath as $additionalPath) {
|
||||
$absolutePath = $this->fileHelper->absolutizePath($additionalPath);
|
||||
|
||||
if (is_dir($absolutePath)) {
|
||||
$migrationFiles += iterator_to_array(
|
||||
new RegexIterator(
|
||||
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absolutePath)),
|
||||
'/\.php$/i'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $migrationFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SplFileInfo[] $files
|
||||
*/
|
||||
private function requireFiles(array $files): void
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\PropertiesClassReflectionExtension;
|
||||
use PHPStan\Reflection\PropertyReflection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ModelAccessorExtension implements PropertiesClassReflectionExtension
|
||||
{
|
||||
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
|
||||
{
|
||||
if (! $classReflection->isSubclassOf(Model::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $classReflection->hasNativeMethod('get'.Str::studly($propertyName).'Attribute');
|
||||
}
|
||||
|
||||
public function getProperty(
|
||||
ClassReflection $classReflection,
|
||||
string $propertyName
|
||||
): PropertyReflection {
|
||||
$method = $classReflection->getNativeMethod('get'.Str::studly($propertyName).'Attribute');
|
||||
|
||||
return new ModelProperty(
|
||||
$classReflection,
|
||||
$method->getVariants()[0]->getReturnType(),
|
||||
$method->getVariants()[0]->getReturnType()
|
||||
);
|
||||
}
|
||||
}
|
98
php-packages/phpstan/src/Properties/ModelProperty.php
Normal file
98
php-packages/phpstan/src/Properties/ModelProperty.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\PropertyReflection;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class ModelProperty implements PropertyReflection
|
||||
{
|
||||
/** @var ClassReflection */
|
||||
private $declaringClass;
|
||||
|
||||
/** @var Type */
|
||||
private $readableType;
|
||||
|
||||
/** @var Type */
|
||||
private $writableType;
|
||||
|
||||
/** @var bool */
|
||||
private $writeable;
|
||||
|
||||
public function __construct(ClassReflection $declaringClass, Type $readableType, Type $writableType, bool $writeable = true)
|
||||
{
|
||||
$this->declaringClass = $declaringClass;
|
||||
$this->readableType = $readableType;
|
||||
$this->writableType = $writableType;
|
||||
$this->writeable = $writeable;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->declaringClass;
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return $this->writeable;
|
||||
}
|
||||
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getReadableType(): Type
|
||||
{
|
||||
return $this->readableType;
|
||||
}
|
||||
|
||||
public function getWritableType(): Type
|
||||
{
|
||||
return $this->writableType;
|
||||
}
|
||||
|
||||
public function canChangeTypeAfterAssignment(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isDeprecated(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isInternal(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
}
|
266
php-packages/phpstan/src/Properties/ModelPropertyExtension.php
Normal file
266
php-packages/phpstan/src/Properties/ModelPropertyExtension.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use ArrayObject;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Reflection\ReflectionHelper;
|
||||
use PHPStan\PhpDoc\TypeStringResolver;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\PropertiesClassReflectionExtension;
|
||||
use PHPStan\Reflection\PropertyReflection;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
use PHPStan\Type\IntegerType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ModelPropertyExtension implements PropertiesClassReflectionExtension
|
||||
{
|
||||
/** @var array<string, SchemaTable> */
|
||||
private $tables = [];
|
||||
|
||||
/** @var TypeStringResolver */
|
||||
private $stringResolver;
|
||||
|
||||
/** @var string */
|
||||
private $dateClass;
|
||||
|
||||
/** @var MigrationHelper */
|
||||
private $migrationHelper;
|
||||
|
||||
public function __construct(TypeStringResolver $stringResolver, MigrationHelper $migrationHelper)
|
||||
{
|
||||
$this->stringResolver = $stringResolver;
|
||||
$this->migrationHelper = $migrationHelper;
|
||||
}
|
||||
|
||||
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
|
||||
{
|
||||
if (! $classReflection->isSubclassOf(Model::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($classReflection->isAbstract()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($classReflection->hasNativeMethod('get'.Str::studly($propertyName).'Attribute')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReflectionHelper::hasPropertyTag($classReflection, $propertyName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($this->tables) === 0) {
|
||||
$this->tables = $this->migrationHelper->initializeTables();
|
||||
}
|
||||
|
||||
if ($propertyName === 'id') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$modelName = $classReflection->getNativeReflection()->getName();
|
||||
|
||||
try {
|
||||
$reflect = new \ReflectionClass($modelName);
|
||||
|
||||
/** @var Model $modelInstance */
|
||||
$modelInstance = $reflect->newInstanceWithoutConstructor();
|
||||
|
||||
$tableName = $modelInstance->getTable();
|
||||
} catch (\ReflectionException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! array_key_exists($tableName, $this->tables)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! array_key_exists($propertyName, $this->tables[$tableName]->columns)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->castPropertiesType($modelInstance);
|
||||
|
||||
$column = $this->tables[$tableName]->columns[$propertyName];
|
||||
|
||||
[$readableType, $writableType] = $this->getReadableAndWritableTypes($column, $modelInstance);
|
||||
|
||||
$column->readableType = $readableType;
|
||||
$column->writeableType = $writableType;
|
||||
|
||||
$this->tables[$tableName]->columns[$propertyName] = $column;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getProperty(
|
||||
ClassReflection $classReflection,
|
||||
string $propertyName
|
||||
): PropertyReflection {
|
||||
$modelName = $classReflection->getNativeReflection()->getName();
|
||||
|
||||
try {
|
||||
$reflect = new \ReflectionClass($modelName);
|
||||
|
||||
/** @var Model $modelInstance */
|
||||
$modelInstance = $reflect->newInstanceWithoutConstructor();
|
||||
|
||||
$tableName = $modelInstance->getTable();
|
||||
} catch (\ReflectionException $e) {
|
||||
// `hasProperty` should return false if there was a reflection exception.
|
||||
// so this should never happen
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
if (
|
||||
(! array_key_exists($tableName, $this->tables)
|
||||
|| ! array_key_exists($propertyName, $this->tables[$tableName]->columns)
|
||||
)
|
||||
&& $propertyName === 'id'
|
||||
) {
|
||||
return new ModelProperty(
|
||||
$classReflection,
|
||||
new IntegerType(),
|
||||
new IntegerType()
|
||||
);
|
||||
}
|
||||
|
||||
$column = $this->tables[$tableName]->columns[$propertyName];
|
||||
|
||||
return new ModelProperty(
|
||||
$classReflection,
|
||||
$this->stringResolver->resolve($column->readableType),
|
||||
$this->stringResolver->resolve($column->writeableType)
|
||||
);
|
||||
}
|
||||
|
||||
private function getDateClass(): string
|
||||
{
|
||||
if (! $this->dateClass) {
|
||||
$this->dateClass = class_exists(\Illuminate\Support\Facades\Date::class)
|
||||
? '\\'.get_class(\Illuminate\Support\Facades\Date::now())
|
||||
: '\Illuminate\Support\Carbon';
|
||||
|
||||
$this->dateClass .= '|\Carbon\Carbon';
|
||||
}
|
||||
|
||||
return $this->dateClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SchemaColumn $column
|
||||
* @param Model $modelInstance
|
||||
* @return string[]
|
||||
* @phpstan-return array<int, string>
|
||||
*/
|
||||
private function getReadableAndWritableTypes(SchemaColumn $column, Model $modelInstance): array
|
||||
{
|
||||
$readableType = $column->readableType;
|
||||
$writableType = $column->writeableType;
|
||||
|
||||
if (in_array($column->name, $modelInstance->getDates(), true)) {
|
||||
return [$this->getDateClass().($column->nullable ? '|null' : ''), $this->getDateClass().'|string'.($column->nullable ? '|null' : '')];
|
||||
}
|
||||
|
||||
switch ($column->readableType) {
|
||||
case 'string':
|
||||
case 'int':
|
||||
case 'float':
|
||||
$readableType = $writableType = $column->readableType.($column->nullable ? '|null' : '');
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
switch ((string) config('database.default')) {
|
||||
case 'sqlite':
|
||||
case 'mysql':
|
||||
$writableType = '0|1|bool';
|
||||
$readableType = 'bool';
|
||||
break;
|
||||
default:
|
||||
$readableType = $writableType = 'bool';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'enum':
|
||||
if (! $column->options) {
|
||||
$readableType = $writableType = 'string';
|
||||
} else {
|
||||
$readableType = $writableType = '\''.implode('\'|\'', $column->options).'\'';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return [$readableType, $writableType];
|
||||
}
|
||||
|
||||
private function castPropertiesType(Model $modelInstance): void
|
||||
{
|
||||
$casts = $modelInstance->getCasts();
|
||||
foreach ($casts as $name => $type) {
|
||||
if (! array_key_exists($name, $this->tables[$modelInstance->getTable()]->columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$realType = 'boolean';
|
||||
break;
|
||||
case 'string':
|
||||
$realType = 'string';
|
||||
break;
|
||||
case 'array':
|
||||
case 'json':
|
||||
$realType = 'array';
|
||||
break;
|
||||
case 'object':
|
||||
$realType = 'object';
|
||||
break;
|
||||
case 'int':
|
||||
case 'integer':
|
||||
case 'timestamp':
|
||||
$realType = 'integer';
|
||||
break;
|
||||
case 'real':
|
||||
case 'double':
|
||||
case 'float':
|
||||
$realType = 'float';
|
||||
break;
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
$realType = $this->getDateClass();
|
||||
break;
|
||||
case 'collection':
|
||||
$realType = '\Illuminate\Support\Collection';
|
||||
break;
|
||||
case 'Illuminate\Database\Eloquent\Casts\AsArrayObject':
|
||||
$realType = ArrayObject::class;
|
||||
break;
|
||||
case 'Illuminate\Database\Eloquent\Casts\AsCollection':
|
||||
$realType = '\Illuminate\Support\Collection<mixed, mixed>';
|
||||
break;
|
||||
default:
|
||||
$realType = class_exists($type) ? ('\\'.$type) : 'mixed';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->tables[$modelInstance->getTable()]->columns[$name]->nullable) {
|
||||
$realType .= '|null';
|
||||
}
|
||||
|
||||
$this->tables[$modelInstance->getTable()]->columns[$name]->readableType = $realType;
|
||||
$this->tables[$modelInstance->getTable()]->columns[$name]->writeableType = $realType;
|
||||
}
|
||||
}
|
||||
}
|
116
php-packages/phpstan/src/Properties/ModelRelationsExtension.php
Normal file
116
php-packages/phpstan/src/Properties/ModelRelationsExtension.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Concerns;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use Flarum\PHPStan\Reflection\ReflectionHelper;
|
||||
use Flarum\PHPStan\Types\RelationParserHelper;
|
||||
use PHPStan\Analyser\OutOfClassScope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Reflection\PropertiesClassReflectionExtension;
|
||||
use PHPStan\Reflection\PropertyReflection;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\UnionType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ModelRelationsExtension implements PropertiesClassReflectionExtension
|
||||
{
|
||||
use Concerns\HasContainer;
|
||||
|
||||
/** @var RelationParserHelper */
|
||||
private $relationParserHelper;
|
||||
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
public function __construct(
|
||||
RelationParserHelper $relationParserHelper,
|
||||
BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->relationParserHelper = $relationParserHelper;
|
||||
$this->builderHelper = $builderHelper;
|
||||
}
|
||||
|
||||
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
|
||||
{
|
||||
if (! $classReflection->isSubclassOf(Model::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReflectionHelper::hasPropertyTag($classReflection, $propertyName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasNativeMethod = $classReflection->hasNativeMethod($propertyName);
|
||||
|
||||
if (! $hasNativeMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod($propertyName)->getVariants())->getReturnType();
|
||||
|
||||
if (! (new ObjectType(Relation::class))->isSuperTypeOf($returnType)->yes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
|
||||
{
|
||||
$method = $classReflection->getMethod($propertyName, new OutOfClassScope());
|
||||
|
||||
/** @var ObjectType $returnType */
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType();
|
||||
|
||||
if ($returnType instanceof GenericObjectType) {
|
||||
/** @var ObjectType $relatedModelType */
|
||||
$relatedModelType = $returnType->getTypes()[0];
|
||||
$relatedModelClassName = $relatedModelType->getClassName();
|
||||
} else {
|
||||
$relatedModelClassName = $this
|
||||
->relationParserHelper
|
||||
->findRelatedModelInRelationMethod($method);
|
||||
}
|
||||
|
||||
if ($relatedModelClassName === null) {
|
||||
$relatedModelClassName = Model::class;
|
||||
}
|
||||
|
||||
$relatedModel = new ObjectType($relatedModelClassName);
|
||||
$collectionClass = $this->builderHelper->determineCollectionClassName($relatedModelClassName);
|
||||
|
||||
if (Str::contains($returnType->getClassName(), 'Many')) {
|
||||
return new ModelProperty(
|
||||
$classReflection,
|
||||
new GenericObjectType($collectionClass, [$relatedModel]),
|
||||
new NeverType(), false
|
||||
);
|
||||
}
|
||||
|
||||
if (Str::endsWith($returnType->getClassName(), 'MorphTo')) {
|
||||
return new ModelProperty($classReflection, new UnionType([
|
||||
new ObjectType(Model::class),
|
||||
new MixedType(),
|
||||
]), new NeverType(), false);
|
||||
}
|
||||
|
||||
return new ModelProperty($classReflection, new UnionType([
|
||||
$relatedModel,
|
||||
new NullType(),
|
||||
]), new NeverType(), false);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use ReflectionNamedType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ReflectionTypeContainer extends ReflectionNamedType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* ReflectionTypeContainer constructor.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function allowsNull(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isBuiltin(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
432
php-packages/phpstan/src/Properties/SchemaAggregator.php
Normal file
432
php-packages/phpstan/src/Properties/SchemaAggregator.php
Normal file
@ -0,0 +1,432 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use PhpParser;
|
||||
use PhpParser\NodeFinder;
|
||||
|
||||
/**
|
||||
* @see https://github.com/psalm/laravel-psalm-plugin/blob/master/src/SchemaAggregator.php
|
||||
*/
|
||||
final class SchemaAggregator
|
||||
{
|
||||
/** @var array<string, SchemaTable> */
|
||||
public $tables = [];
|
||||
|
||||
/**
|
||||
* @param array<int, PhpParser\Node\Stmt> $stmts
|
||||
*/
|
||||
public function addStatements(array $stmts): void
|
||||
{
|
||||
$nodeFinder = new NodeFinder();
|
||||
|
||||
/** @var PhpParser\Node\Stmt\Class_[] $classes */
|
||||
$classes = $nodeFinder->findInstanceOf($stmts, PhpParser\Node\Stmt\Class_::class);
|
||||
|
||||
foreach ($classes as $stmt) {
|
||||
$this->addClassStatements($stmt->stmts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, PhpParser\Node\Stmt> $stmts
|
||||
*/
|
||||
private function addClassStatements(array $stmts): void
|
||||
{
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod
|
||||
&& $stmt->name->name !== 'down'
|
||||
&& $stmt->stmts
|
||||
) {
|
||||
$this->addUpMethodStatements($stmt->stmts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Stmt[] $stmts
|
||||
*/
|
||||
private function addUpMethodStatements(array $stmts): void
|
||||
{
|
||||
$nodeFinder = new NodeFinder();
|
||||
$methods = $nodeFinder->findInstanceOf($stmts, PhpParser\Node\Stmt\Expression::class);
|
||||
|
||||
foreach ($methods as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Expression
|
||||
&& $stmt->expr instanceof PhpParser\Node\Expr\StaticCall
|
||||
&& ($stmt->expr->class instanceof PhpParser\Node\Name)
|
||||
&& $stmt->expr->name instanceof PhpParser\Node\Identifier
|
||||
&& ($stmt->expr->class->toCodeString() === '\Illuminate\Support\Facades\Schema' || $stmt->expr->class->toCodeString() === '\Schema')
|
||||
) {
|
||||
switch ($stmt->expr->name->name) {
|
||||
case 'create':
|
||||
$this->alterTable($stmt->expr, true);
|
||||
break;
|
||||
|
||||
case 'table':
|
||||
$this->alterTable($stmt->expr, false);
|
||||
break;
|
||||
|
||||
case 'drop':
|
||||
case 'dropIfExists':
|
||||
$this->dropTable($stmt->expr);
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
$this->renameTable($stmt->expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function alterTable(PhpParser\Node\Expr\StaticCall $call, bool $creating): void
|
||||
{
|
||||
if (! isset($call->args[0])
|
||||
|| ! $call->getArgs()[0]->value instanceof PhpParser\Node\Scalar\String_
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tableName = $call->getArgs()[0]->value->value;
|
||||
|
||||
if ($creating) {
|
||||
$this->tables[$tableName] = new SchemaTable($tableName);
|
||||
}
|
||||
|
||||
if (! isset($call->args[1])
|
||||
|| ! $call->getArgs()[1]->value instanceof PhpParser\Node\Expr\Closure
|
||||
|| count($call->getArgs()[1]->value->params) < 1
|
||||
|| ($call->getArgs()[1]->value->params[0]->type instanceof PhpParser\Node\Name
|
||||
&& $call->getArgs()[1]->value->params[0]->type->toCodeString()
|
||||
!== '\\Illuminate\Database\Schema\Blueprint')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$updateClosure = $call->getArgs()[1]->value;
|
||||
|
||||
if ($call->getArgs()[1]->value->params[0]->var instanceof PhpParser\Node\Expr\Variable
|
||||
&& is_string($call->getArgs()[1]->value->params[0]->var->name)
|
||||
) {
|
||||
$argName = $call->getArgs()[1]->value->params[0]->var->name;
|
||||
|
||||
$this->processColumnUpdates($tableName, $argName, $updateClosure->stmts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @param string $argName
|
||||
* @param PhpParser\Node\Stmt[] $stmts
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function processColumnUpdates(string $tableName, string $argName, array $stmts): void
|
||||
{
|
||||
if (! isset($this->tables[$tableName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->tables[$tableName];
|
||||
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Expression
|
||||
&& $stmt->expr instanceof PhpParser\Node\Expr\MethodCall
|
||||
&& $stmt->expr->name instanceof PhpParser\Node\Identifier
|
||||
) {
|
||||
$rootVar = $stmt->expr;
|
||||
|
||||
$firstMethodCall = $rootVar;
|
||||
|
||||
$nullable = false;
|
||||
|
||||
while ($rootVar instanceof PhpParser\Node\Expr\MethodCall) {
|
||||
if ($rootVar->name instanceof PhpParser\Node\Identifier
|
||||
&& $rootVar->name->name === 'nullable'
|
||||
) {
|
||||
$nullable = true;
|
||||
}
|
||||
|
||||
$firstMethodCall = $rootVar;
|
||||
$rootVar = $rootVar->var;
|
||||
}
|
||||
|
||||
if ($rootVar instanceof PhpParser\Node\Expr\Variable
|
||||
&& $rootVar->name === $argName
|
||||
&& $firstMethodCall->name instanceof PhpParser\Node\Identifier
|
||||
) {
|
||||
$firstArg = $firstMethodCall->getArgs()[0]->value ?? null;
|
||||
$secondArg = $firstMethodCall->getArgs()[1]->value ?? null;
|
||||
|
||||
if ($firstMethodCall->name->name === 'foreignIdFor') {
|
||||
if ($firstArg instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||
&& $firstArg->class instanceof PhpParser\Node\Name
|
||||
) {
|
||||
$modelClass = $firstArg->class->toCodeString();
|
||||
} elseif ($firstArg instanceof PhpParser\Node\Scalar\String_) {
|
||||
$modelClass = $firstArg->value;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnName = Str::snake(class_basename($modelClass)).'_id';
|
||||
if ($secondArg instanceof PhpParser\Node\Scalar\String_) {
|
||||
$columnName = $secondArg->value;
|
||||
}
|
||||
|
||||
$table->setColumn(new SchemaColumn($columnName, 'int', $nullable));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $firstArg instanceof PhpParser\Node\Scalar\String_) {
|
||||
if ($firstMethodCall->name->name === 'timestamps'
|
||||
|| $firstMethodCall->name->name === 'timestampsTz'
|
||||
|| $firstMethodCall->name->name === 'nullableTimestamps'
|
||||
|| $firstMethodCall->name->name === 'nullableTimestampsTz'
|
||||
|| $firstMethodCall->name->name === 'rememberToken'
|
||||
) {
|
||||
switch (strtolower($firstMethodCall->name->name)) {
|
||||
case 'droptimestamps':
|
||||
case 'droptimestampstz':
|
||||
$table->dropColumn('created_at');
|
||||
$table->dropColumn('updated_at');
|
||||
break;
|
||||
|
||||
case 'remembertoken':
|
||||
$table->setColumn(new SchemaColumn('remember_token', 'string', $nullable));
|
||||
break;
|
||||
|
||||
case 'dropremembertoken':
|
||||
$table->dropColumn('remember_token');
|
||||
break;
|
||||
|
||||
case 'timestamps':
|
||||
case 'timestampstz':
|
||||
case 'nullabletimestamps':
|
||||
$table->setColumn(new SchemaColumn('created_at', 'string', true));
|
||||
$table->setColumn(new SchemaColumn('updated_at', 'string', true));
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($firstMethodCall->name->name === 'softDeletes'
|
||||
|| $firstMethodCall->name->name === 'softDeletesTz'
|
||||
|| $firstMethodCall->name->name === 'dropSoftDeletes'
|
||||
|| $firstMethodCall->name->name === 'dropSoftDeletesTz'
|
||||
) {
|
||||
$columnName = 'deleted_at';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$columnName = $firstArg->value;
|
||||
}
|
||||
|
||||
$secondArgArray = null;
|
||||
|
||||
if ($secondArg instanceof PhpParser\Node\Expr\Array_) {
|
||||
$secondArgArray = [];
|
||||
|
||||
foreach ($secondArg->items as $array_item) {
|
||||
if ($array_item !== null && $array_item->value instanceof PhpParser\Node\Scalar\String_) {
|
||||
$secondArgArray[] = $array_item->value->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (strtolower($firstMethodCall->name->name)) {
|
||||
case 'biginteger':
|
||||
case 'increments':
|
||||
case 'integer':
|
||||
case 'integerincrements':
|
||||
case 'mediumincrements':
|
||||
case 'mediuminteger':
|
||||
case 'smallincrements':
|
||||
case 'smallinteger':
|
||||
case 'tinyincrements':
|
||||
case 'tinyinteger':
|
||||
case 'unsignedbiginteger':
|
||||
case 'unsignedinteger':
|
||||
case 'unsignedmediuminteger':
|
||||
case 'unsignedsmallinteger':
|
||||
case 'unsignedtinyinteger':
|
||||
case 'bigincrements':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'int', $nullable));
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'datetimetz':
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'ipaddress':
|
||||
case 'json':
|
||||
case 'jsonb':
|
||||
case 'linestring':
|
||||
case 'longtext':
|
||||
case 'macaddress':
|
||||
case 'mediumtext':
|
||||
case 'multilinestring':
|
||||
case 'string':
|
||||
case 'text':
|
||||
case 'time':
|
||||
case 'timestamp':
|
||||
case 'uuid':
|
||||
case 'binary':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'string', $nullable));
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'bool', $nullable));
|
||||
break;
|
||||
|
||||
case 'geometry':
|
||||
case 'geometrycollection':
|
||||
case 'multipoint':
|
||||
case 'multipolygon':
|
||||
case 'multipolygonz':
|
||||
case 'point':
|
||||
case 'polygon':
|
||||
case 'computed':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'mixed', $nullable));
|
||||
break;
|
||||
|
||||
case 'double':
|
||||
case 'float':
|
||||
case 'unsigneddecimal':
|
||||
case 'decimal':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'float', $nullable));
|
||||
break;
|
||||
|
||||
case 'after':
|
||||
if ($secondArg instanceof PhpParser\Node\Expr\Closure
|
||||
&& $secondArg->params[0]->var instanceof PhpParser\Node\Expr\Variable
|
||||
&& ! ($secondArg->params[0]->var->name instanceof PhpParser\Node\Expr)) {
|
||||
$argName = $secondArg->params[0]->var->name;
|
||||
$this->processColumnUpdates($tableName, $argName, $secondArg->stmts);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'dropcolumn':
|
||||
case 'dropifexists':
|
||||
case 'dropsoftdeletes':
|
||||
case 'dropsoftdeletestz':
|
||||
case 'removecolumn':
|
||||
case 'drop':
|
||||
$table->dropColumn($columnName);
|
||||
break;
|
||||
|
||||
case 'dropforeign':
|
||||
case 'dropindex':
|
||||
case 'dropprimary':
|
||||
case 'dropunique':
|
||||
case 'foreign':
|
||||
case 'index':
|
||||
case 'primary':
|
||||
case 'renameindex':
|
||||
case 'spatialIndex':
|
||||
case 'unique':
|
||||
case 'dropspatialindex':
|
||||
break;
|
||||
|
||||
case 'dropmorphs':
|
||||
$table->dropColumn($columnName.'_type');
|
||||
$table->dropColumn($columnName.'_id');
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'enum', $nullable, $secondArgArray));
|
||||
break;
|
||||
|
||||
case 'morphs':
|
||||
$table->setColumn(new SchemaColumn($columnName.'_type', 'string', $nullable));
|
||||
$table->setColumn(new SchemaColumn($columnName.'_id', 'int', $nullable));
|
||||
break;
|
||||
|
||||
case 'nullablemorphs':
|
||||
$table->setColumn(new SchemaColumn($columnName.'_type', 'string', true));
|
||||
$table->setColumn(new SchemaColumn($columnName.'_id', 'int', true));
|
||||
break;
|
||||
|
||||
case 'nullableuuidmorphs':
|
||||
$table->setColumn(new SchemaColumn($columnName.'_type', 'string', true));
|
||||
$table->setColumn(new SchemaColumn($columnName.'_id', 'string', true));
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
case 'renamecolumn':
|
||||
if ($secondArg instanceof PhpParser\Node\Scalar\String_) {
|
||||
$table->renameColumn($columnName, $secondArg->value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'set':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'set', $nullable, $secondArgArray));
|
||||
break;
|
||||
|
||||
case 'softdeletestz':
|
||||
case 'timestamptz':
|
||||
case 'timetz':
|
||||
case 'year':
|
||||
case 'softdeletes':
|
||||
$table->setColumn(new SchemaColumn($columnName, 'string', true));
|
||||
break;
|
||||
|
||||
case 'uuidmorphs':
|
||||
$table->setColumn(new SchemaColumn($columnName.'_type', 'string', $nullable));
|
||||
$table->setColumn(new SchemaColumn($columnName.'_id', 'string', $nullable));
|
||||
break;
|
||||
|
||||
default:
|
||||
// We know a property exists with a name, we just don't know its type.
|
||||
$table->setColumn(new SchemaColumn($columnName, 'mixed', $nullable));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function dropTable(PhpParser\Node\Expr\StaticCall $call): void
|
||||
{
|
||||
if (! isset($call->args[0])
|
||||
|| ! $call->getArgs()[0]->value instanceof PhpParser\Node\Scalar\String_
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tableName = $call->getArgs()[0]->value->value;
|
||||
|
||||
unset($this->tables[$tableName]);
|
||||
}
|
||||
|
||||
private function renameTable(PhpParser\Node\Expr\StaticCall $call): void
|
||||
{
|
||||
if (! isset($call->args[0], $call->args[1])
|
||||
|| ! $call->getArgs()[0]->value instanceof PhpParser\Node\Scalar\String_
|
||||
|| ! $call->getArgs()[1]->value instanceof PhpParser\Node\Scalar\String_
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldTableName = $call->getArgs()[0]->value->value;
|
||||
$newTableName = $call->getArgs()[1]->value->value;
|
||||
|
||||
if (! isset($this->tables[$oldTableName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->tables[$oldTableName];
|
||||
|
||||
unset($this->tables[$oldTableName]);
|
||||
|
||||
$table->name = $newTableName;
|
||||
|
||||
$this->tables[$newTableName] = $table;
|
||||
}
|
||||
}
|
45
php-packages/phpstan/src/Properties/SchemaColumn.php
Normal file
45
php-packages/phpstan/src/Properties/SchemaColumn.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
/**
|
||||
* @see https://github.com/psalm/laravel-psalm-plugin/blob/master/src/SchemaColumn.php
|
||||
*/
|
||||
final class SchemaColumn
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var string */
|
||||
public $readableType;
|
||||
|
||||
/** @var string */
|
||||
public $writeableType;
|
||||
|
||||
/** @var bool */
|
||||
public $nullable;
|
||||
|
||||
/** @var ?array<int, string> */
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $readableType
|
||||
* @param bool $nullable
|
||||
* @param string[]|null $options
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $readableType,
|
||||
bool $nullable = false,
|
||||
?array $options = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->readableType = $readableType;
|
||||
$this->writeableType = $readableType;
|
||||
$this->nullable = $nullable;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
47
php-packages/phpstan/src/Properties/SchemaTable.php
Normal file
47
php-packages/phpstan/src/Properties/SchemaTable.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Properties;
|
||||
|
||||
/**
|
||||
* @see https://github.com/psalm/laravel-psalm-plugin/blob/master/src/SchemaTable.php
|
||||
*/
|
||||
final class SchemaTable
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var SchemaColumn[] */
|
||||
public $columns = [];
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function setColumn(SchemaColumn $column): void
|
||||
{
|
||||
$this->columns[$column->name] = $column;
|
||||
}
|
||||
|
||||
public function renameColumn(string $oldName, string $newName): void
|
||||
{
|
||||
if (! isset($this->columns[$oldName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldColumn = $this->columns[$oldName];
|
||||
|
||||
unset($this->columns[$oldName]);
|
||||
|
||||
$oldColumn->name = $newName;
|
||||
|
||||
$this->columns[$newName] = $oldColumn;
|
||||
}
|
||||
|
||||
public function dropColumn(string $columnName): void
|
||||
{
|
||||
unset($this->columns[$columnName]);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\PassedByReference;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class AnnotationScopeMethodParameterReflection implements ParameterReflection
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var Type */
|
||||
private $type;
|
||||
|
||||
/** @var PassedByReference */
|
||||
private $passedByReference;
|
||||
|
||||
/** @var bool */
|
||||
private $isOptional;
|
||||
|
||||
/** @var bool */
|
||||
private $isVariadic;
|
||||
|
||||
/** @var Type|null */
|
||||
private $defaultValue;
|
||||
|
||||
public function __construct(string $name, Type $type, PassedByReference $passedByReference, bool $isOptional, bool $isVariadic, ?Type $defaultValue)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->passedByReference = $passedByReference;
|
||||
$this->isOptional = $isOptional;
|
||||
$this->isVariadic = $isVariadic;
|
||||
$this->defaultValue = $defaultValue;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return $this->isOptional;
|
||||
}
|
||||
|
||||
public function getType(): Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function passedByReference(): PassedByReference
|
||||
{
|
||||
return $this->passedByReference;
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->isVariadic;
|
||||
}
|
||||
|
||||
public function getDefaultValue(): ?Type
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptor;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class AnnotationScopeMethodReflection implements MethodReflection
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var ClassReflection */
|
||||
private $declaringClass;
|
||||
|
||||
/** @var Type */
|
||||
private $returnType;
|
||||
|
||||
/** @var bool */
|
||||
private $isStatic;
|
||||
|
||||
/** @var AnnotationScopeMethodParameterReflection[] */
|
||||
private $parameters;
|
||||
|
||||
/** @var bool */
|
||||
private $isVariadic;
|
||||
|
||||
/** @var FunctionVariant[]|null */
|
||||
private $variants = null;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param ClassReflection $declaringClass
|
||||
* @param Type $returnType
|
||||
* @param AnnotationScopeMethodParameterReflection[] $parameters
|
||||
* @param bool $isStatic
|
||||
* @param bool $isVariadic
|
||||
*/
|
||||
public function __construct(string $name, ClassReflection $declaringClass, Type $returnType, array $parameters, bool $isStatic, bool $isVariadic)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->declaringClass = $declaringClass;
|
||||
$this->returnType = $returnType;
|
||||
$this->parameters = $parameters;
|
||||
$this->isStatic = $isStatic;
|
||||
$this->isVariadic = $isVariadic;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->declaringClass;
|
||||
}
|
||||
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return $this->isStatic;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParametersAcceptor[]
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
if ($this->variants === null) {
|
||||
$this->variants = [new FunctionVariant(TemplateTypeMap::createEmpty(), null, $this->parameters, $this->isVariadic, $this->returnType)];
|
||||
}
|
||||
|
||||
return $this->variants;
|
||||
}
|
||||
|
||||
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(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\PassedByReference;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class DynamicWhereParameterReflection implements ParameterReflection
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'dynamic-where-parameter';
|
||||
}
|
||||
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getType(): Type
|
||||
{
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
public function passedByReference(): PassedByReference
|
||||
{
|
||||
return PassedByReference::createNo();
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDefaultValue(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class EloquentBuilderMethodReflection implements MethodReflection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* @var ClassReflection
|
||||
*/
|
||||
private $classReflection;
|
||||
|
||||
/** @var MethodReflection */
|
||||
private $originalMethodReflection;
|
||||
|
||||
/**
|
||||
* @var ParameterReflection[]
|
||||
*/
|
||||
private $parameters;
|
||||
|
||||
/**
|
||||
* @var Type
|
||||
*/
|
||||
private $returnType;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isVariadic;
|
||||
|
||||
/**
|
||||
* @param string $methodName
|
||||
* @param ClassReflection $classReflection
|
||||
* @param MethodReflection $originalMethodReflection
|
||||
* @param ParameterReflection[] $parameters
|
||||
* @param Type|null $returnType
|
||||
* @param bool $isVariadic
|
||||
*/
|
||||
public function __construct(string $methodName, ClassReflection $classReflection, MethodReflection $originalMethodReflection, array $parameters, ?Type $returnType = null, bool $isVariadic = false)
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
$this->classReflection = $classReflection;
|
||||
$this->originalMethodReflection = $originalMethodReflection;
|
||||
$this->parameters = $parameters;
|
||||
$this->returnType = $returnType ?? new ObjectType(Builder::class);
|
||||
$this->isVariadic = $isVariadic;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->classReflection;
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return [
|
||||
new FunctionVariant(
|
||||
TemplateTypeMap::createEmpty(),
|
||||
null,
|
||||
$this->parameters,
|
||||
$this->isVariadic,
|
||||
$this->returnType
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MethodReflection
|
||||
*/
|
||||
public function getOriginalMethodReflection(): MethodReflection
|
||||
{
|
||||
return $this->originalMethodReflection;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class ModelScopeMethodReflection implements MethodReflection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* @var ClassReflection
|
||||
*/
|
||||
private $classReflection;
|
||||
|
||||
/**
|
||||
* @var ClassReflection
|
||||
*/
|
||||
private $relation;
|
||||
|
||||
public function __construct(string $methodName, ClassReflection $classReflection, ClassReflection $relation)
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
$this->classReflection = $classReflection;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->classReflection;
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return [
|
||||
new FunctionVariant(
|
||||
TemplateTypeMap::createEmpty(),
|
||||
null,
|
||||
[],
|
||||
false,
|
||||
new ObjectType($this->relation->getName())
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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(): ?Type
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
}
|
28
php-packages/phpstan/src/Reflection/ReflectionHelper.php
Normal file
28
php-packages/phpstan/src/Reflection/ReflectionHelper.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
|
||||
final class ReflectionHelper
|
||||
{
|
||||
/**
|
||||
* Does the given class or any of its ancestors have an `@property*` annotation with the given name?
|
||||
*/
|
||||
public static function hasPropertyTag(ClassReflection $classReflection, string $propertyName): bool
|
||||
{
|
||||
if (array_key_exists($propertyName, $classReflection->getPropertyTags())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($classReflection->getAncestors() as $ancestor) {
|
||||
if (array_key_exists($propertyName, $ancestor->getPropertyTags())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Reflection;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class StaticMethodReflection implements MethodReflection
|
||||
{
|
||||
/** @var MethodReflection */
|
||||
private $methodReflection;
|
||||
|
||||
public function __construct(MethodReflection $methodReflection)
|
||||
{
|
||||
$this->methodReflection = $methodReflection;
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->methodReflection->getDeclaringClass();
|
||||
}
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return $this->methodReflection->isPrivate();
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return $this->methodReflection->isPublic();
|
||||
}
|
||||
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return $this->methodReflection->getDocComment();
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->methodReflection->getName();
|
||||
}
|
||||
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this->methodReflection->getPrototype();
|
||||
}
|
||||
|
||||
public function getVariants(): array
|
||||
{
|
||||
return $this->methodReflection->getVariants();
|
||||
}
|
||||
|
||||
public function isDeprecated(): TrinaryLogic
|
||||
{
|
||||
return $this->methodReflection->isDeprecated();
|
||||
}
|
||||
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return $this->methodReflection->getDeprecatedDescription();
|
||||
}
|
||||
|
||||
public function isFinal(): TrinaryLogic
|
||||
{
|
||||
return $this->methodReflection->isFinal();
|
||||
}
|
||||
|
||||
public function isInternal(): TrinaryLogic
|
||||
{
|
||||
return $this->methodReflection->isInternal();
|
||||
}
|
||||
|
||||
public function getThrowType(): ?Type
|
||||
{
|
||||
return $this->methodReflection->getThrowType();
|
||||
}
|
||||
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return $this->methodReflection->hasSideEffects();
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use Flarum\PHPStan\Methods\ModelTypeHelper;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BuilderModelFindExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider, BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Builder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
$methodName = $methodReflection->getName();
|
||||
|
||||
if (! Str::startsWith($methodName, 'find')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$model = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TModelClass');
|
||||
|
||||
if ($model === null || ! $model instanceof ObjectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->reflectionProvider->getClass(Builder::class)->hasNativeMethod($methodName) &&
|
||||
! $this->reflectionProvider->getClass(QueryBuilder::class)->hasNativeMethod($methodName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
/** @var ObjectType $model */
|
||||
$model = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TModelClass');
|
||||
$returnType = $methodReflection->getVariants()[0]->getReturnType();
|
||||
$argType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
$returnType = ModelTypeHelper::replaceStaticTypeWithModel($returnType, $model->getClassName());
|
||||
|
||||
if ($argType->isIterable()->yes()) {
|
||||
if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($model->getClassName());
|
||||
|
||||
return new GenericObjectType($collectionClassName, [$model]);
|
||||
}
|
||||
|
||||
return TypeCombinator::remove($returnType, $model);
|
||||
}
|
||||
|
||||
if ($argType instanceof MixedType) {
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
return TypeCombinator::remove(
|
||||
TypeCombinator::remove(
|
||||
$returnType,
|
||||
new ArrayType(new MixedType(), $model)
|
||||
),
|
||||
new ObjectType(Collection::class)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||
use PHPStan\Type\Constant\ConstantFloatType;
|
||||
use PHPStan\Type\Constant\ConstantIntegerType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
use PHPStan\Type\UnionType;
|
||||
|
||||
class CollectionFilterDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
public function getClass(): string
|
||||
{
|
||||
return Enumerable::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'filter';
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$calledOnType = $scope->getType($methodCall->var);
|
||||
|
||||
if (! $calledOnType instanceof \PHPStan\Type\Generic\GenericObjectType) {
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
$keyType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TKey');
|
||||
$valueType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TValue');
|
||||
|
||||
if ($keyType === null || $valueType === null) {
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
if (count($methodCall->getArgs()) < 1) {
|
||||
$falseyTypes = $this->getFalseyTypes();
|
||||
|
||||
$nonFalseyTypes = TypeCombinator::remove($valueType, $falseyTypes);
|
||||
|
||||
if ((new ObjectType(Collection::class))->isSuperTypeOf($calledOnType)->yes()) {
|
||||
return new GenericObjectType($calledOnType->getClassName(), [$nonFalseyTypes]);
|
||||
}
|
||||
|
||||
return new GenericObjectType($calledOnType->getClassName(), [$keyType, $nonFalseyTypes]);
|
||||
}
|
||||
|
||||
$callbackArg = $methodCall->getArgs()[0]->value;
|
||||
|
||||
$var = null;
|
||||
$expr = null;
|
||||
|
||||
if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) {
|
||||
$statement = $callbackArg->stmts[0];
|
||||
if ($statement instanceof Return_ && $statement->expr !== null) {
|
||||
$var = $callbackArg->params[0]->var;
|
||||
$expr = $statement->expr;
|
||||
}
|
||||
} elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) {
|
||||
$var = $callbackArg->params[0]->var;
|
||||
$expr = $callbackArg->expr;
|
||||
}
|
||||
|
||||
if ($var !== null && $expr !== null) {
|
||||
if (! $var instanceof Variable || ! is_string($var->name)) {
|
||||
throw new \PHPStan\ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$itemVariableName = $var->name;
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$scope = $scope->assignVariable($itemVariableName, $valueType);
|
||||
$scope = $scope->filterByTruthyValue($expr);
|
||||
$valueType = $scope->getVariableType($itemVariableName);
|
||||
}
|
||||
|
||||
if ((new ObjectType(Collection::class))->isSuperTypeOf($calledOnType)->yes()) {
|
||||
return new GenericObjectType($calledOnType->getClassName(), [$valueType]);
|
||||
}
|
||||
|
||||
return new GenericObjectType($calledOnType->getClassName(), [$keyType, $valueType]);
|
||||
}
|
||||
|
||||
private function getFalseyTypes(): UnionType
|
||||
{
|
||||
return new UnionType([new NullType(), new ConstantBooleanType(false), new ConstantIntegerType(0), new ConstantFloatType(0.0), new ConstantStringType(''), new ConstantStringType('0'), new ConstantArrayType([], [])]);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Flarum\PHPStan\Support\CollectionHelper;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class CollectionMakeDynamicStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* @var CollectionHelper
|
||||
*/
|
||||
private $collectionHelper;
|
||||
|
||||
public function __construct(CollectionHelper $collectionHelper)
|
||||
{
|
||||
$this->collectionHelper = $collectionHelper;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return Collection::class;
|
||||
}
|
||||
|
||||
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'make';
|
||||
}
|
||||
|
||||
public function getTypeFromStaticMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
StaticCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($methodCall->getArgs()) < 1) {
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
$valueType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
return $this->collectionHelper->determineGenericCollectionTypeFromType($valueType);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Flarum\PHPStan\Concerns\HasContainer;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class ContainerArrayAccessDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
use HasContainer;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $className;
|
||||
|
||||
public function __construct(string $className)
|
||||
{
|
||||
$this->className = $className;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'offsetGet';
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$args = $methodCall->getArgs();
|
||||
|
||||
if (count($args) === 0) {
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
$argType = $scope->getType($args[0]->value);
|
||||
|
||||
if (! $argType instanceof ConstantStringType) {
|
||||
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
$resolvedValue = $this->resolve($argType->getValue());
|
||||
|
||||
if ($resolvedValue === null) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
if (is_object($resolvedValue)) {
|
||||
$class = get_class($resolvedValue);
|
||||
|
||||
return new ObjectType($class);
|
||||
}
|
||||
|
||||
return $scope->getTypeFromValue($resolvedValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\Generic\TemplateMixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class EloquentBuilderExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider, BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return EloquentBuilder::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
$builderReflection = $this->reflectionProvider->getClass(EloquentBuilder::class);
|
||||
|
||||
// Don't handle dynamic wheres
|
||||
if (Str::startsWith($methodReflection->getName(), 'where') &&
|
||||
! $builderReflection->hasNativeMethod($methodReflection->getName())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Str::startsWith($methodReflection->getName(), 'find') &&
|
||||
$builderReflection->hasNativeMethod($methodReflection->getName())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$templateTypeMap = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap();
|
||||
|
||||
if (! $templateTypeMap->getType('TModelClass') instanceof ObjectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $builderReflection->hasNativeMethod($methodReflection->getName());
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$returnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType();
|
||||
$templateTypeMap = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap();
|
||||
|
||||
/** @var Type|ObjectType|TemplateMixedType $modelType */
|
||||
$modelType = $templateTypeMap->getType('TModelClass');
|
||||
|
||||
if ($modelType instanceof ObjectType && in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($modelType->getClassName());
|
||||
|
||||
return new GenericObjectType($collectionClassName, [$modelType]);
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Flarum\PHPStan\Concerns\HasContainer;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use Throwable;
|
||||
|
||||
class AppExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
use HasContainer;
|
||||
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'app' || $functionReflection->getName() === 'resolve';
|
||||
}
|
||||
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($functionCall->getArgs()) === 0) {
|
||||
return new ObjectType(Application::class);
|
||||
}
|
||||
|
||||
/** @var Expr $expr */
|
||||
$expr = $functionCall->getArgs()[0]->value;
|
||||
|
||||
if ($expr instanceof String_) {
|
||||
try {
|
||||
/** @var object|null $resolved */
|
||||
$resolved = $this->resolve($expr->value);
|
||||
|
||||
if ($resolved === null) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
return new ObjectType(get_class($resolved));
|
||||
} catch (Throwable $exception) {
|
||||
return new ErrorType();
|
||||
}
|
||||
}
|
||||
|
||||
if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) {
|
||||
return new ObjectType($expr->class->toString());
|
||||
}
|
||||
|
||||
return new NeverType();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use Flarum\PHPStan\Support\CollectionHelper;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class CollectExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* @var CollectionHelper
|
||||
*/
|
||||
private $collectionHelper;
|
||||
|
||||
public function __construct(CollectionHelper $collectionHelper)
|
||||
{
|
||||
$this->collectionHelper = $collectionHelper;
|
||||
}
|
||||
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'collect';
|
||||
}
|
||||
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($functionCall->getArgs()) < 1) {
|
||||
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
|
||||
}
|
||||
|
||||
$valueType = $scope->getType($functionCall->getArgs()[0]->value);
|
||||
|
||||
return $this->collectionHelper->determineGenericCollectionTypeFromType($valueType);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use Illuminate\Support\HigherOrderTapProxy;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\ThisType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class TapExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'tap';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($functionCall->getArgs()) === 1) {
|
||||
$type = $scope->getType($functionCall->getArgs()[0]->value);
|
||||
|
||||
return new GenericObjectType(HigherOrderTapProxy::class, [
|
||||
$type instanceof ThisType ? $type->getStaticObjectType() : $type,
|
||||
]);
|
||||
}
|
||||
|
||||
if (count($functionCall->getArgs()) === 2) {
|
||||
return $scope->getType($functionCall->getArgs()[0]->value);
|
||||
}
|
||||
|
||||
return new NeverType();
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class TransExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'trans';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
// No path provided, so it returns a Translator instance
|
||||
if (count($functionCall->getArgs()) === 0) {
|
||||
return new ObjectType(\Illuminate\Contracts\Translation\Translator::class);
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use function count;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ValidatorExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'validator';
|
||||
}
|
||||
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($functionCall->getArgs()) === 0) {
|
||||
return new ObjectType(\Illuminate\Contracts\Validation\Factory::class);
|
||||
}
|
||||
|
||||
return new IntersectionType([
|
||||
new ObjectType(\Illuminate\Validation\Validator::class),
|
||||
new ObjectType(\Illuminate\Contracts\Validation\Validator::class),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes\Helpers;
|
||||
|
||||
use function count;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ValueExtension implements DynamicFunctionReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFunctionSupported(FunctionReflection $functionReflection): bool
|
||||
{
|
||||
return $functionReflection->getName() === 'value';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromFunctionCall(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $functionCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($functionCall->getArgs()) === 0) {
|
||||
return new NeverType();
|
||||
}
|
||||
|
||||
$arg = $functionCall->getArgs()[0]->value;
|
||||
if ($arg instanceof Closure) {
|
||||
$callbackType = $scope->getType($arg);
|
||||
$callbackReturnType = ParametersAcceptorSelector::selectFromArgs(
|
||||
$scope,
|
||||
$functionCall->getArgs(),
|
||||
$callbackType->getCallableParametersAcceptors($scope)
|
||||
)->getReturnType();
|
||||
|
||||
return $callbackReturnType;
|
||||
}
|
||||
|
||||
return $scope->getType($arg);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Support\HigherOrderTapProxy;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HigherOrderTapProxyExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return HigherOrderTapProxy::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$type = $scope->getType($methodCall->var);
|
||||
if ($type instanceof GenericObjectType) {
|
||||
$types = $type->getTypes();
|
||||
if (count($types) === 1 && $types[0] instanceof ObjectType) {
|
||||
return $types[0];
|
||||
}
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
}
|
||||
}
|
104
php-packages/phpstan/src/ReturnTypes/ModelExtension.php
Normal file
104
php-packages/phpstan/src/ReturnTypes/ModelExtension.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ModelExtension implements DynamicStaticMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/**
|
||||
* @param BuilderHelper $builderHelper
|
||||
*/
|
||||
public function __construct(BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Model::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
$name = $methodReflection->getName();
|
||||
if ($name === '__construct') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($name, ['get', 'hydrate', 'fromQuery'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $methodReflection->getDeclaringClass()->hasNativeMethod($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method = $methodReflection->getDeclaringClass()->getNativeMethod($methodReflection->getName());
|
||||
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType();
|
||||
|
||||
return (count(array_intersect([EloquentBuilder::class, QueryBuilder::class, Collection::class], $returnType->getReferencedClasses()))) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromStaticMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
StaticCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$method = $methodReflection->getDeclaringClass()
|
||||
->getMethod($methodReflection->getName(), $scope);
|
||||
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType();
|
||||
|
||||
if ((count(array_intersect([EloquentBuilder::class, QueryBuilder::class], $returnType->getReferencedClasses())) > 0)
|
||||
&& $methodCall->class instanceof \PhpParser\Node\Name
|
||||
) {
|
||||
$returnType = new GenericObjectType(
|
||||
$this->builderHelper->determineBuilderName($scope->resolveName($methodCall->class)),
|
||||
[new ObjectType($scope->resolveName($methodCall->class))]
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
$methodCall->class instanceof \PhpParser\Node\Name
|
||||
&& in_array(Collection::class, $returnType->getReferencedClasses(), true)
|
||||
&& in_array($methodReflection->getName(), ['get', 'hydrate', 'fromQuery', 'all', 'findMany'], true)
|
||||
) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($scope->resolveName($methodCall->class));
|
||||
|
||||
return new GenericObjectType($collectionClassName, [new ObjectType($scope->resolveName($methodCall->class))]);
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class ModelFactoryDynamicStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
|
||||
{
|
||||
public function getClass(): string
|
||||
{
|
||||
return Model::class;
|
||||
}
|
||||
|
||||
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
if ($methodReflection->getName() !== 'factory') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Class only available on Laravel 8
|
||||
if (! class_exists('\Illuminate\Database\Eloquent\Factories\Factory')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTypeFromStaticMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
StaticCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$class = $methodCall->class;
|
||||
|
||||
if (! $class instanceof Name) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
$modelName = basename(str_replace('\\', '/', $class->toCodeString()));
|
||||
|
||||
if (! class_exists('Database\\Factories\\'.$modelName.'Factory')) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
return new ObjectType('Database\\Factories\\'.$modelName.'Factory');
|
||||
}
|
||||
}
|
108
php-packages/phpstan/src/ReturnTypes/ModelFindExtension.php
Normal file
108
php-packages/phpstan/src/ReturnTypes/ModelFindExtension.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ModelFindExtension implements DynamicStaticMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider, BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Model::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
$methodName = $methodReflection->getName();
|
||||
|
||||
if (! Str::startsWith($methodName, 'find')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->reflectionProvider->getClass(Builder::class)->hasNativeMethod($methodName) &&
|
||||
! $this->reflectionProvider->getClass(QueryBuilder::class)->hasNativeMethod($methodName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromStaticMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
StaticCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
if (count($methodCall->getArgs()) < 1) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
$modelName = $methodReflection->getDeclaringClass()->getName();
|
||||
$returnType = $methodReflection->getVariants()[0]->getReturnType();
|
||||
$argType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
if ($argType->isIterable()->yes()) {
|
||||
if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($modelName);
|
||||
|
||||
return new GenericObjectType($collectionClassName, [new ObjectType($modelName)]);
|
||||
}
|
||||
|
||||
return TypeCombinator::remove($returnType, new ObjectType($modelName));
|
||||
}
|
||||
|
||||
if ($argType instanceof MixedType) {
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
return TypeCombinator::remove(
|
||||
TypeCombinator::remove(
|
||||
$returnType,
|
||||
new ArrayType(new MixedType(), new ObjectType($modelName))
|
||||
),
|
||||
new ObjectType(Collection::class)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RelationCollectionExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
public function __construct(BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Relation::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
if (Str::startsWith($methodReflection->getName(), 'find')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$modelType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TRelatedModel');
|
||||
|
||||
if (! $modelType instanceof ObjectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
|
||||
if (! in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $methodReflection->getDeclaringClass()->hasNativeMethod($methodReflection->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
/** @var ObjectType $modelType */
|
||||
$modelType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TRelatedModel');
|
||||
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
|
||||
if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($modelType->getClassname());
|
||||
|
||||
return new GenericObjectType($collectionClassName, [$modelType]);
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Support\Str;
|
||||
use Flarum\PHPStan\Methods\BuilderHelper;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RelationFindExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/** @var BuilderHelper */
|
||||
private $builderHelper;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(ReflectionProvider $reflectionProvider, BuilderHelper $builderHelper)
|
||||
{
|
||||
$this->builderHelper = $builderHelper;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Relation::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
if (! Str::startsWith($methodReflection->getName(), 'find')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$modelType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TRelatedModel');
|
||||
|
||||
if (! $modelType instanceof ObjectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $methodReflection->getDeclaringClass()->hasNativeMethod($methodReflection->getName()) ||
|
||||
$this->reflectionProvider->getClass(Builder::class)->hasNativeMethod($methodReflection->getName()) ||
|
||||
$this->reflectionProvider->getClass(QueryBuilder::class)->hasNativeMethod($methodReflection->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
/** @var ObjectType $modelType */
|
||||
$modelType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TRelatedModel');
|
||||
|
||||
$argType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
$returnType = $methodReflection->getVariants()[0]->getReturnType();
|
||||
|
||||
if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) {
|
||||
if ($argType->isIterable()->yes()) {
|
||||
$collectionClassName = $this->builderHelper->determineCollectionClassName($modelType->getClassname());
|
||||
|
||||
return new GenericObjectType($collectionClassName, [$modelType]);
|
||||
}
|
||||
|
||||
$returnType = TypeCombinator::remove($returnType, new ObjectType(Collection::class));
|
||||
|
||||
return TypeCombinator::remove($returnType, new ArrayType(new MixedType(), $modelType));
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
}
|
61
php-packages/phpstan/src/ReturnTypes/RequestExtension.php
Normal file
61
php-packages/phpstan/src/ReturnTypes/RequestExtension.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RequestExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return Request::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$uploadedFileType = new ObjectType(UploadedFile::class);
|
||||
$uploadedFileArrayType = new ArrayType(new IntegerType(), $uploadedFileType);
|
||||
|
||||
if (count($methodCall->getArgs()) === 0) {
|
||||
return new ArrayType(new IntegerType(), $uploadedFileType);
|
||||
}
|
||||
|
||||
if (count($methodCall->getArgs()) === 1) {
|
||||
return TypeCombinator::union($uploadedFileArrayType, TypeCombinator::addNull($uploadedFileType));
|
||||
}
|
||||
|
||||
return TypeCombinator::union(TypeCombinator::union($uploadedFileArrayType, $uploadedFileType), $scope->getType($methodCall->getArgs()[1]->value));
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Flarum\PHPStan\Concerns\HasContainer;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class StorageDynamicStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
|
||||
{
|
||||
use HasContainer;
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return Storage::class;
|
||||
}
|
||||
|
||||
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'disk';
|
||||
}
|
||||
|
||||
public function getTypeFromStaticMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
StaticCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
return new ObjectType(FilesystemAdapter::class);
|
||||
}
|
||||
}
|
53
php-packages/phpstan/src/ReturnTypes/TestCaseExtension.php
Normal file
53
php-packages/phpstan/src/ReturnTypes/TestCaseExtension.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\ReturnTypes;
|
||||
|
||||
use Illuminate\Foundation\Testing\TestCase;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TestCaseExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
public function getClass(): string
|
||||
{
|
||||
return TestCase::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return in_array($methodReflection->getName(), [
|
||||
'mock',
|
||||
'partialMock',
|
||||
'spy',
|
||||
], true);
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
$defaultReturnType = new ObjectType('Mockery\\MockInterface');
|
||||
|
||||
$classType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
if (! $classType instanceof ConstantStringType) {
|
||||
return $defaultReturnType;
|
||||
}
|
||||
|
||||
$objectType = new ObjectType($classType->getValue());
|
||||
|
||||
return TypeCombinator::intersect($defaultReturnType, $objectType);
|
||||
}
|
||||
}
|
99
php-packages/phpstan/src/Support/CollectionHelper.php
Normal file
99
php-packages/phpstan/src/Support/CollectionHelper.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use PHPStan\Type\GeneralizePrecision;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
use PHPStan\Type\TypeUtils;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
use Traversable;
|
||||
|
||||
final class CollectionHelper
|
||||
{
|
||||
public function determineGenericCollectionTypeFromType(Type $type): GenericObjectType
|
||||
{
|
||||
$keyType = TypeCombinator::union(new IntegerType(), new StringType());
|
||||
|
||||
if ($type instanceof TypeWithClassName) {
|
||||
if ((new ObjectType(Enumerable::class))->isSuperTypeOf($type)->yes()) {
|
||||
return $this->getTypeFromEloquentCollection($type);
|
||||
}
|
||||
|
||||
if (
|
||||
(new ObjectType(Traversable::class))->isSuperTypeOf($type)->yes() ||
|
||||
(new ObjectType(IteratorAggregate::class))->isSuperTypeOf($type)->yes() ||
|
||||
(new ObjectType(Iterator::class))->isSuperTypeOf($type)->yes()
|
||||
) {
|
||||
return $this->getTypeFromIterator($type);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $type->isArray()->yes()) {
|
||||
return new GenericObjectType(Collection::class, [$type->toArray()->getIterableKeyType(), $type->toArray()->getIterableValueType()]);
|
||||
}
|
||||
|
||||
if ($type->isIterableAtLeastOnce()->no()) {
|
||||
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
|
||||
}
|
||||
|
||||
return new GenericObjectType(Collection::class, [
|
||||
TypeUtils::generalizeType($type->getIterableKeyType(), GeneralizePrecision::lessSpecific()),
|
||||
TypeUtils::generalizeType($type->getIterableValueType(), GeneralizePrecision::lessSpecific()),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getTypeFromEloquentCollection(TypeWithClassName $valueType): GenericObjectType
|
||||
{
|
||||
$keyType = TypeCombinator::union(new IntegerType(), new StringType());
|
||||
|
||||
$classReflection = $valueType->getClassReflection();
|
||||
|
||||
if ($classReflection === null) {
|
||||
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
|
||||
}
|
||||
|
||||
$innerValueType = $classReflection->getActiveTemplateTypeMap()->getType('TValue');
|
||||
|
||||
if ($classReflection->getName() === EloquentCollection::class || $classReflection->isSubclassOf(EloquentCollection::class)) {
|
||||
$keyType = new IntegerType();
|
||||
}
|
||||
|
||||
if ($innerValueType !== null) {
|
||||
return new GenericObjectType(Collection::class, [$keyType, $innerValueType]);
|
||||
}
|
||||
|
||||
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
|
||||
}
|
||||
|
||||
private function getTypeFromIterator(TypeWithClassName $valueType): GenericObjectType
|
||||
{
|
||||
$keyType = TypeCombinator::union(new IntegerType(), new StringType());
|
||||
|
||||
$classReflection = $valueType->getClassReflection();
|
||||
|
||||
if ($classReflection === null) {
|
||||
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
|
||||
}
|
||||
|
||||
$templateTypes = array_values($classReflection->getActiveTemplateTypeMap()->getTypes());
|
||||
|
||||
if (count($templateTypes) === 1) {
|
||||
return new GenericObjectType(Collection::class, [$keyType, $templateTypes[0]]);
|
||||
}
|
||||
|
||||
return new GenericObjectType(Collection::class, $templateTypes);
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
use Illuminate\Support\HigherOrderCollectionProxy;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Type;
|
||||
|
||||
class HigherOrderCollectionProxyHelper
|
||||
{
|
||||
/**
|
||||
* @phpstan-param 'method'|'property' $propertyOrMethod
|
||||
*/
|
||||
public static function hasPropertyOrMethod(ClassReflection $classReflection, string $name, string $propertyOrMethod): bool
|
||||
{
|
||||
if ($classReflection->getName() !== HigherOrderCollectionProxy::class) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$activeTemplateTypeMap = $classReflection->getActiveTemplateTypeMap();
|
||||
|
||||
if ($activeTemplateTypeMap->count() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$methodType = $activeTemplateTypeMap->getType('T');
|
||||
$valueType = $activeTemplateTypeMap->getType('TValue');
|
||||
|
||||
if (($methodType === null) || ($valueType === null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $methodType instanceof Type\Constant\ConstantStringType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $valueType->canCallMethods()->yes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($propertyOrMethod === 'method') {
|
||||
return $valueType->hasMethod($name)->yes();
|
||||
}
|
||||
|
||||
return $valueType->hasProperty($name)->yes();
|
||||
}
|
||||
|
||||
public static function determineReturnType(string $name, Type\Type $valueType, Type\Type $methodOrPropertyReturnType): Type\Type
|
||||
{
|
||||
if ((new Type\ObjectType(Model::class))->isSuperTypeOf($valueType)->yes()) {
|
||||
$collectionType = Collection::class;
|
||||
$types = [$valueType];
|
||||
} else {
|
||||
$collectionType = SupportCollection::class;
|
||||
$types = [new Type\IntegerType(), $valueType];
|
||||
}
|
||||
switch ($name) {
|
||||
case 'average':
|
||||
case 'avg':
|
||||
$returnType = new Type\FloatType();
|
||||
break;
|
||||
case 'contains':
|
||||
case 'every':
|
||||
case 'some':
|
||||
$returnType = new Type\BooleanType();
|
||||
break;
|
||||
case 'each':
|
||||
case 'filter':
|
||||
case 'reject':
|
||||
case 'skipUntil':
|
||||
case 'skipWhile':
|
||||
case 'sortBy':
|
||||
case 'sortByDesc':
|
||||
case 'takeUntil':
|
||||
case 'takeWhile':
|
||||
case 'unique':
|
||||
$returnType = new Type\Generic\GenericObjectType($collectionType, $types);
|
||||
break;
|
||||
case 'keyBy':
|
||||
if ($collectionType === SupportCollection::class) {
|
||||
$returnType = new Type\Generic\GenericObjectType($collectionType, [$methodOrPropertyReturnType, $valueType]);
|
||||
} else {
|
||||
$returnType = new Type\Generic\GenericObjectType($collectionType, $types);
|
||||
}
|
||||
break;
|
||||
case 'first':
|
||||
$returnType = Type\TypeCombinator::addNull($valueType);
|
||||
break;
|
||||
case 'flatMap':
|
||||
$returnType = new Type\Generic\GenericObjectType(SupportCollection::class, [new Type\IntegerType(), new Type\MixedType()]);
|
||||
break;
|
||||
case 'groupBy':
|
||||
case 'partition':
|
||||
$innerTypes = [
|
||||
new Type\Generic\GenericObjectType($collectionType, $types),
|
||||
];
|
||||
|
||||
if ($collectionType === SupportCollection::class) {
|
||||
array_unshift($innerTypes, new Type\IntegerType());
|
||||
}
|
||||
|
||||
$returnType = new Type\Generic\GenericObjectType($collectionType, $innerTypes);
|
||||
break;
|
||||
case 'map':
|
||||
$returnType = new Type\Generic\GenericObjectType(SupportCollection::class, [
|
||||
new Type\IntegerType(),
|
||||
$methodOrPropertyReturnType,
|
||||
]);
|
||||
break;
|
||||
case 'max':
|
||||
case 'min':
|
||||
$returnType = $methodOrPropertyReturnType;
|
||||
break;
|
||||
case 'sum':
|
||||
if ($methodOrPropertyReturnType->accepts(new Type\IntegerType(), true)->yes()) {
|
||||
$returnType = new Type\IntegerType();
|
||||
} else {
|
||||
$returnType = new Type\ErrorType();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
$returnType = new Type\ErrorType();
|
||||
break;
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Analyser\SpecifiedTypes;
|
||||
use PHPStan\Analyser\TypeSpecifier;
|
||||
use PHPStan\Analyser\TypeSpecifierAwareExtension;
|
||||
use PHPStan\Analyser\TypeSpecifierContext;
|
||||
use PHPStan\Reflection\FunctionReflection;
|
||||
use PHPStan\Type\FunctionTypeSpecifyingExtension;
|
||||
|
||||
final class AbortIfFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
|
||||
{
|
||||
/** @var TypeSpecifier */
|
||||
private $typeSpecifier;
|
||||
|
||||
/** @var bool */
|
||||
protected $negate;
|
||||
|
||||
/** @var string */
|
||||
protected $methodName;
|
||||
|
||||
public function __construct(bool $negate, string $methodName)
|
||||
{
|
||||
$this->negate = $negate;
|
||||
$this->methodName = $methodName.'_'.($negate === false ? 'if' : 'unless');
|
||||
}
|
||||
|
||||
public function isFunctionSupported(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $node,
|
||||
TypeSpecifierContext $context
|
||||
): bool {
|
||||
return $functionReflection->getName() === $this->methodName && $context->null();
|
||||
}
|
||||
|
||||
public function specifyTypes(
|
||||
FunctionReflection $functionReflection,
|
||||
FuncCall $node,
|
||||
Scope $scope,
|
||||
TypeSpecifierContext $context
|
||||
): SpecifiedTypes {
|
||||
if (count($node->args) < 2) {
|
||||
return new SpecifiedTypes();
|
||||
}
|
||||
|
||||
$context = $this->negate === false ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createTruthy();
|
||||
|
||||
return $this->typeSpecifier->specifyTypesInCondition($scope, $node->getArgs()[0]->value, $context);
|
||||
}
|
||||
|
||||
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
|
||||
{
|
||||
$this->typeSpecifier = $typeSpecifier;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use function count;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\PhpDoc\TypeNodeResolverExtension;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class GenericEloquentBuilderTypeNodeResolverExtension implements TypeNodeResolverExtension
|
||||
{
|
||||
public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type
|
||||
{
|
||||
if (! $typeNode instanceof UnionTypeNode || count($typeNode->types) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$modelTypeNode = null;
|
||||
$builderTypeNode = null;
|
||||
foreach ($typeNode->types as $innerTypeNode) {
|
||||
if ($innerTypeNode instanceof IdentifierTypeNode
|
||||
&& is_subclass_of($nameScope->resolveStringName($innerTypeNode->name), Model::class)
|
||||
) {
|
||||
$modelTypeNode = $innerTypeNode;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$innerTypeNode instanceof IdentifierTypeNode
|
||||
&& ($nameScope->resolveStringName($innerTypeNode->name) === Builder::class || is_subclass_of($nameScope->resolveStringName($innerTypeNode->name), Builder::class))
|
||||
) {
|
||||
$builderTypeNode = $innerTypeNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ($modelTypeNode === null || $builderTypeNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builderTypeName = $nameScope->resolveStringName($builderTypeNode->name);
|
||||
$modelTypeName = $nameScope->resolveStringName($modelTypeNode->name);
|
||||
|
||||
return new GenericObjectType($builderTypeName, [
|
||||
new ObjectType($modelTypeName),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use function count;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\PhpDoc\TypeNodeResolver;
|
||||
use PHPStan\PhpDoc\TypeNodeResolverExtension;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @see https://github.com/nunomaduro/larastan/issues/476
|
||||
* @see https://gist.github.com/ondrejmirtes/56af016d0595788d5400b8dfb6520adc
|
||||
*
|
||||
* This extension interprets docblocks like:
|
||||
*
|
||||
* \Illuminate\Database\Eloquent\Collection|\App\Account[] $accounts
|
||||
*
|
||||
* and transforms them into:
|
||||
*
|
||||
* \Illuminate\Database\Eloquent\Collection<\App\Account> $accounts
|
||||
*
|
||||
* Now IDE's can benefit from auto-completion, and we can benefit from the correct type passed to the generic collection
|
||||
*/
|
||||
class GenericEloquentCollectionTypeNodeResolverExtension implements TypeNodeResolverExtension
|
||||
{
|
||||
/**
|
||||
* @var TypeNodeResolver
|
||||
*/
|
||||
private $typeNodeResolver;
|
||||
|
||||
public function __construct(TypeNodeResolver $typeNodeResolver)
|
||||
{
|
||||
$this->typeNodeResolver = $typeNodeResolver;
|
||||
}
|
||||
|
||||
public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type
|
||||
{
|
||||
if (! $typeNode instanceof UnionTypeNode || count($typeNode->types) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$arrayTypeNode = null;
|
||||
$identifierTypeNode = null;
|
||||
foreach ($typeNode->types as $innerTypeNode) {
|
||||
if ($innerTypeNode instanceof ArrayTypeNode) {
|
||||
$arrayTypeNode = $innerTypeNode;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($innerTypeNode instanceof IdentifierTypeNode) {
|
||||
$identifierTypeNode = $innerTypeNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ($arrayTypeNode === null || $identifierTypeNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$identifierTypeName = $nameScope->resolveStringName($identifierTypeNode->name);
|
||||
if ($identifierTypeName !== Collection::class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$innerArrayTypeNode = $arrayTypeNode->type;
|
||||
if (! $innerArrayTypeNode instanceof IdentifierTypeNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$resolvedInnerArrayType = $this->typeNodeResolver->resolve($innerArrayTypeNode, $nameScope);
|
||||
|
||||
return new GenericObjectType($identifierTypeName, [
|
||||
$resolvedInnerArrayType,
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types\ModelProperty;
|
||||
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\ClassStringType;
|
||||
use PHPStan\Type\CompoundType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\Generic\TemplateType;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\Generic\TemplateTypeVariance;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\ObjectWithoutClassType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
use PHPStan\Type\UnionType;
|
||||
|
||||
class GenericModelPropertyType extends ModelPropertyType
|
||||
{
|
||||
/** @var Type */
|
||||
private $type;
|
||||
|
||||
public function __construct(Type $type)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getReferencedClasses(): array
|
||||
{
|
||||
return $this->getGenericType()->getReferencedClasses();
|
||||
}
|
||||
|
||||
public function getGenericType(): Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isSuperTypeOf(Type $type): TrinaryLogic
|
||||
{
|
||||
if ($type instanceof ConstantStringType) {
|
||||
return $this->getGenericType()->hasProperty($type->getValue());
|
||||
}
|
||||
|
||||
if ($type instanceof self) {
|
||||
return TrinaryLogic::createYes();
|
||||
}
|
||||
|
||||
if ($type instanceof parent) {
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
|
||||
if ($type instanceof CompoundType) {
|
||||
return $type->isSubTypeOf($this);
|
||||
}
|
||||
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function traverse(callable $cb): Type
|
||||
{
|
||||
$newType = $cb($this->getGenericType());
|
||||
|
||||
if ($newType === $this->getGenericType()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new self($newType);
|
||||
}
|
||||
|
||||
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
|
||||
{
|
||||
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
|
||||
return $receivedType->inferTemplateTypesOn($this);
|
||||
}
|
||||
|
||||
if ($receivedType instanceof ConstantStringType) {
|
||||
$typeToInfer = new ObjectType($receivedType->getValue());
|
||||
} elseif ($receivedType instanceof self) {
|
||||
$typeToInfer = $receivedType->type;
|
||||
} elseif ($receivedType instanceof ClassStringType) {
|
||||
$typeToInfer = $this->getGenericType();
|
||||
|
||||
if ($typeToInfer instanceof TemplateType) {
|
||||
$typeToInfer = $typeToInfer->getBound();
|
||||
}
|
||||
|
||||
$typeToInfer = TypeCombinator::intersect($typeToInfer, new ObjectWithoutClassType());
|
||||
} else {
|
||||
return TemplateTypeMap::createEmpty();
|
||||
}
|
||||
|
||||
if (! $this->getGenericType()->isSuperTypeOf($typeToInfer)->no()) {
|
||||
return $this->getGenericType()->inferTemplateTypes($typeToInfer);
|
||||
}
|
||||
|
||||
return TemplateTypeMap::createEmpty();
|
||||
}
|
||||
|
||||
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
|
||||
{
|
||||
$variance = $positionVariance->compose(TemplateTypeVariance::createCovariant());
|
||||
|
||||
return $this->getGenericType()->getReferencedTemplateTypes($variance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $properties
|
||||
* @return Type
|
||||
*/
|
||||
public static function __set_state(array $properties): Type
|
||||
{
|
||||
return new self($properties['type']);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types\ModelProperty;
|
||||
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class ModelPropertyType extends StringType
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $properties
|
||||
* @return Type
|
||||
*/
|
||||
public static function __set_state(array $properties): Type
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types\ModelProperty;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\PhpDoc\TypeNodeResolver;
|
||||
use PHPStan\PhpDoc\TypeNodeResolverExtension;
|
||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* Ensures a 'model-property' type in PHPDoc is recognised to be of type ModelPropertyType.
|
||||
*/
|
||||
class ModelPropertyTypeNodeResolverExtension implements TypeNodeResolverExtension
|
||||
{
|
||||
/** @var bool */
|
||||
protected $active;
|
||||
|
||||
/** @var TypeNodeResolver */
|
||||
protected $baseResolver;
|
||||
|
||||
public function __construct(TypeNodeResolver $baseResolver, bool $active)
|
||||
{
|
||||
$this->baseResolver = $baseResolver;
|
||||
$this->active = $active;
|
||||
}
|
||||
|
||||
public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type
|
||||
{
|
||||
if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === 'model-property') {
|
||||
return $this->active ? new ModelPropertyType() : new StringType();
|
||||
}
|
||||
|
||||
if ($typeNode instanceof GenericTypeNode && $typeNode->type->name === 'model-property') {
|
||||
if (! $this->active) {
|
||||
return new StringType();
|
||||
}
|
||||
|
||||
if (count($typeNode->genericTypes) !== 1) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
$genericType = $this->baseResolver->resolve($typeNode->genericTypes[0], $nameScope);
|
||||
|
||||
if ((new ObjectType(Model::class))->isSuperTypeOf($genericType)->no()) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
if ($genericType instanceof NeverType) {
|
||||
return new ErrorType();
|
||||
}
|
||||
|
||||
return new GenericModelPropertyType($genericType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Flarum\PHPStan\Concerns\HasContainer;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class ModelRelationsDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
use HasContainer;
|
||||
|
||||
/** @var RelationParserHelper */
|
||||
private $relationParserHelper;
|
||||
|
||||
public function __construct(RelationParserHelper $relationParserHelper)
|
||||
{
|
||||
$this->relationParserHelper = $relationParserHelper;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return Model::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
$variants = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
|
||||
|
||||
$returnType = $variants->getReturnType();
|
||||
|
||||
if (! $returnType instanceof ObjectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! (new ObjectType(Relation::class))->isSuperTypeOf($returnType)->yes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $methodReflection->getDeclaringClass()->hasNativeMethod($methodReflection->getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($variants->getParameters()) !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($methodReflection->getName(), [
|
||||
'hasOne', 'hasOneThrough', 'morphOne',
|
||||
'belongsTo', 'morphTo',
|
||||
'hasMany', 'hasManyThrough', 'morphMany',
|
||||
'belongsToMany', 'morphToMany', 'morphedByMany',
|
||||
], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$relatedModel = $this
|
||||
->relationParserHelper
|
||||
->findRelatedModelInRelationMethod($methodReflection);
|
||||
|
||||
return $relatedModel !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MethodReflection $methodReflection
|
||||
* @param MethodCall $methodCall
|
||||
* @param Scope $scope
|
||||
* @return Type
|
||||
*
|
||||
* @throws ShouldNotHappenException
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
/** @var ObjectType $returnType */
|
||||
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
|
||||
|
||||
/** @var string $relatedModelClassName */
|
||||
$relatedModelClassName = $this
|
||||
->relationParserHelper
|
||||
->findRelatedModelInRelationMethod($methodReflection);
|
||||
|
||||
$classReflection = $methodReflection->getDeclaringClass();
|
||||
|
||||
if ($returnType->isInstanceOf(BelongsTo::class)->yes()) {
|
||||
return new GenericObjectType($returnType->getClassName(), [
|
||||
new ObjectType($relatedModelClassName),
|
||||
new ObjectType($classReflection->getName()),
|
||||
]);
|
||||
}
|
||||
|
||||
return new GenericObjectType($returnType->getClassName(), [new ObjectType($relatedModelClassName)]);
|
||||
}
|
||||
}
|
45
php-packages/phpstan/src/Types/Passable.php
Normal file
45
php-packages/phpstan/src/Types/Passable.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use Flarum\PHPStan\Contracts\Types\PassableContract;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Passable implements PassableContract
|
||||
{
|
||||
/**
|
||||
* @var \PHPStan\Type\Type
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Passable constructor.
|
||||
*
|
||||
* @param \PHPStan\Type\Type $type
|
||||
*/
|
||||
public function __construct(Type $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PHPStan\Type\Type
|
||||
*/
|
||||
public function getType(): Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \PHPStan\Type\Type $type
|
||||
*/
|
||||
public function setType(Type $type): void
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericObjectType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
class RelationDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
public function getClass(): string
|
||||
{
|
||||
return Model::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return in_array($methodReflection->getName(), [
|
||||
'hasOne', 'hasOneThrough', 'morphOne',
|
||||
'belongsTo', 'morphTo',
|
||||
'hasMany', 'hasManyThrough', 'morphMany',
|
||||
'belongsToMany', 'morphToMany', 'morphedByMany',
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ShouldNotHappenException
|
||||
*/
|
||||
public function getTypeFromMethodCall(
|
||||
MethodReflection $methodReflection,
|
||||
MethodCall $methodCall,
|
||||
Scope $scope
|
||||
): Type {
|
||||
/** @var FunctionVariant $functionVariant */
|
||||
$functionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
|
||||
$returnType = $functionVariant->getReturnType();
|
||||
|
||||
if (count($methodCall->getArgs()) === 0) {
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
$argType = $scope->getType($methodCall->getArgs()[0]->value);
|
||||
|
||||
if (! $argType instanceof ConstantStringType) {
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
if (! $returnType instanceof ObjectType) {
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
return new GenericObjectType($returnType->getClassName(), [new ObjectType($argType->getValue())]);
|
||||
}
|
||||
}
|
132
php-packages/phpstan/src/Types/RelationParserHelper.php
Normal file
132
php-packages/phpstan/src/Types/RelationParserHelper.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\NodeFinder;
|
||||
use PHPStan\Analyser\ScopeContext;
|
||||
use PHPStan\Analyser\ScopeFactory;
|
||||
use PHPStan\Parser\Parser;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\Generic\GenericClassStringType;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\ObjectType;
|
||||
|
||||
class RelationParserHelper
|
||||
{
|
||||
/** @var Parser */
|
||||
private $parser;
|
||||
|
||||
/** @var ScopeFactory */
|
||||
private $scopeFactory;
|
||||
|
||||
/** @var ReflectionProvider */
|
||||
private $reflectionProvider;
|
||||
|
||||
public function __construct(Parser $parser, ScopeFactory $scopeFactory, ReflectionProvider $reflectionProvider)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
$this->scopeFactory = $scopeFactory;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function findRelatedModelInRelationMethod(
|
||||
MethodReflection $methodReflection
|
||||
): ?string {
|
||||
$fileName = $methodReflection
|
||||
->getDeclaringClass()
|
||||
->getNativeReflection()
|
||||
->getMethod($methodReflection->getName())
|
||||
->getFileName();
|
||||
|
||||
if ($fileName === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fileStmts = $this->parser->parseFile($fileName);
|
||||
|
||||
/** @var Node\Stmt\ClassMethod|null $relationMethod */
|
||||
$relationMethod = $this->findMethod($methodReflection->getName(), $fileStmts);
|
||||
|
||||
if ($relationMethod === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Node\Stmt\Return_|null $returnStmt */
|
||||
$returnStmt = $this->findReturn($relationMethod);
|
||||
|
||||
if ($returnStmt === null || ! $returnStmt->expr instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodCall = $returnStmt->expr;
|
||||
|
||||
while ($methodCall->var instanceof MethodCall) {
|
||||
$methodCall = $methodCall->var;
|
||||
}
|
||||
|
||||
if (count($methodCall->getArgs()) < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scope = $this->scopeFactory->create(
|
||||
ScopeContext::create($fileName),
|
||||
false,
|
||||
[],
|
||||
$methodReflection
|
||||
);
|
||||
|
||||
$methodScope = $scope
|
||||
->enterClass($methodReflection->getDeclaringClass())
|
||||
->enterClassMethod($relationMethod, TemplateTypeMap::createEmpty(), [], null, null, null, false, false, false);
|
||||
|
||||
$argType = $methodScope->getType($methodCall->getArgs()[0]->value);
|
||||
$returnClass = null;
|
||||
|
||||
if ($argType instanceof ConstantStringType) {
|
||||
$returnClass = $argType->getValue();
|
||||
}
|
||||
|
||||
if ($argType instanceof GenericClassStringType) {
|
||||
$modelType = $argType->getGenericType();
|
||||
|
||||
if (! $modelType instanceof ObjectType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnClass = $modelType->getClassName();
|
||||
}
|
||||
|
||||
if ($returnClass === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->reflectionProvider->hasClass($returnClass) ? $returnClass : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param mixed $statements
|
||||
* @return Node|null
|
||||
*/
|
||||
private function findMethod(string $method, $statements): ?Node
|
||||
{
|
||||
return (new NodeFinder)->findFirst($statements, static function (Node $node) use ($method) {
|
||||
return $node instanceof Node\Stmt\ClassMethod
|
||||
&& $node->name->toString() === $method;
|
||||
});
|
||||
}
|
||||
|
||||
private function findReturn(Node\Stmt\ClassMethod $relationMethod): ?Node
|
||||
{
|
||||
/** @var Node[] $statements */
|
||||
$statements = $relationMethod->stmts;
|
||||
|
||||
return (new NodeFinder)->findFirstInstanceOf($statements, Node\Stmt\Return_::class);
|
||||
}
|
||||
}
|
80
php-packages/phpstan/src/Types/ViewStringType.php
Normal file
80
php-packages/phpstan/src/Types/ViewStringType.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\CompoundType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* The custom 'view-string' type class. It's a subset of the string type. Every string that passes the
|
||||
* view()->exists($string) test is a valid view-string type.
|
||||
*/
|
||||
class ViewStringType extends StringType
|
||||
{
|
||||
public function describe(\PHPStan\Type\VerbosityLevel $level): string
|
||||
{
|
||||
return 'view-string';
|
||||
}
|
||||
|
||||
public function accepts(Type $type, bool $strictTypes): TrinaryLogic
|
||||
{
|
||||
if ($type instanceof CompoundType) {
|
||||
return $type->isAcceptedBy($this, $strictTypes);
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantStringType) {
|
||||
/** @var \Illuminate\View\Factory $view */
|
||||
$view = view();
|
||||
|
||||
return TrinaryLogic::createFromBoolean($view->exists($type->getValue()));
|
||||
}
|
||||
|
||||
if ($type instanceof self) {
|
||||
return TrinaryLogic::createYes();
|
||||
}
|
||||
|
||||
if ($type instanceof StringType) {
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
public function isSuperTypeOf(Type $type): TrinaryLogic
|
||||
{
|
||||
if ($type instanceof ConstantStringType) {
|
||||
/** @var \Illuminate\View\Factory $view */
|
||||
$view = view();
|
||||
|
||||
return TrinaryLogic::createFromBoolean($view->exists($type->getValue()));
|
||||
}
|
||||
|
||||
if ($type instanceof self) {
|
||||
return TrinaryLogic::createYes();
|
||||
}
|
||||
|
||||
if ($type instanceof parent) {
|
||||
return TrinaryLogic::createMaybe();
|
||||
}
|
||||
|
||||
if ($type instanceof CompoundType) {
|
||||
return $type->isSubTypeOf($this);
|
||||
}
|
||||
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $properties
|
||||
* @return Type
|
||||
*/
|
||||
public static function __set_state(array $properties): Type
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flarum\PHPStan\Types;
|
||||
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\PhpDoc\TypeNodeResolverExtension;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* Ensures a 'view-string' type in PHPDoc is recognised to be of type ViewStringType.
|
||||
*/
|
||||
class ViewStringTypeNodeResolverExtension implements TypeNodeResolverExtension
|
||||
{
|
||||
public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type
|
||||
{
|
||||
if ($typeNode instanceof IdentifierTypeNode && $typeNode->__toString() === 'view-string') {
|
||||
return new ViewStringType();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Redis\Connections;
|
||||
|
||||
/**
|
||||
* @mixin \Redis
|
||||
*/
|
||||
abstract class Connection
|
||||
{}
|
Loading…
x
Reference in New Issue
Block a user