mirror of
https://github.com/flarum/core.git
synced 2025-07-27 19:50:20 +02:00
Pushing latest stuff
This commit is contained in:
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())
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user