mirror of https://github.com/rectorphp/rector.git
128 lines
5.1 KiB
PHP
128 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\Transform\NodeTypeAnalyzer;
|
|
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\MethodCall;
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
|
use PhpParser\Node\Expr\Variable;
|
|
use PhpParser\Node\Stmt\Class_;
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
use PHPStan\Reflection\ClassReflection;
|
|
use PHPStan\Reflection\MethodReflection;
|
|
use PHPStan\Reflection\ParametersAcceptorSelector;
|
|
use PHPStan\Reflection\Php\PhpPropertyReflection;
|
|
use PHPStan\Reflection\ReflectionProvider;
|
|
use PHPStan\Type\MixedType;
|
|
use PHPStan\Type\ObjectType;
|
|
use PHPStan\Type\Type;
|
|
use PHPStan\Type\TypeCombinator;
|
|
use PHPStan\Type\TypeWithClassName;
|
|
use Rector\Naming\Naming\PropertyNaming;
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
|
use Rector\ValueObject\MethodName;
|
|
final class TypeProvidingExprFromClassResolver
|
|
{
|
|
/**
|
|
* @readonly
|
|
* @var \PHPStan\Reflection\ReflectionProvider
|
|
*/
|
|
private $reflectionProvider;
|
|
/**
|
|
* @readonly
|
|
* @var \Rector\NodeNameResolver\NodeNameResolver
|
|
*/
|
|
private $nodeNameResolver;
|
|
/**
|
|
* @readonly
|
|
* @var \Rector\Naming\Naming\PropertyNaming
|
|
*/
|
|
private $propertyNaming;
|
|
public function __construct(ReflectionProvider $reflectionProvider, NodeNameResolver $nodeNameResolver, PropertyNaming $propertyNaming)
|
|
{
|
|
$this->reflectionProvider = $reflectionProvider;
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
|
$this->propertyNaming = $propertyNaming;
|
|
}
|
|
/**
|
|
* @return MethodCall|PropertyFetch|Variable|null
|
|
*/
|
|
public function resolveTypeProvidingExprFromClass(Class_ $class, ClassMethod $classMethod, ObjectType $objectType) : ?Expr
|
|
{
|
|
$className = (string) $this->nodeNameResolver->getName($class);
|
|
// A. match a method
|
|
$classReflection = $this->reflectionProvider->getClass($className);
|
|
$methodCallProvidingType = $this->resolveMethodCallProvidingType($classReflection, $objectType);
|
|
if ($methodCallProvidingType instanceof MethodCall) {
|
|
return $methodCallProvidingType;
|
|
}
|
|
// B. match existing property
|
|
$propertyFetch = $this->resolvePropertyFetchProvidingType($classReflection, $objectType);
|
|
if ($propertyFetch instanceof PropertyFetch) {
|
|
return $propertyFetch;
|
|
}
|
|
// C. param in constructor?
|
|
return $this->resolveConstructorParamProvidingType($classMethod, $objectType);
|
|
}
|
|
private function resolveMethodCallProvidingType(ClassReflection $classReflection, ObjectType $objectType) : ?MethodCall
|
|
{
|
|
$methodReflections = $this->getClassMethodReflections($classReflection);
|
|
foreach ($methodReflections as $methodReflection) {
|
|
$functionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
|
|
$returnType = $functionVariant->getReturnType();
|
|
if (!$this->isMatchingType($returnType, $objectType)) {
|
|
continue;
|
|
}
|
|
$thisVariable = new Variable('this');
|
|
return new MethodCall($thisVariable, $methodReflection->getName());
|
|
}
|
|
return null;
|
|
}
|
|
private function resolvePropertyFetchProvidingType(ClassReflection $classReflection, ObjectType $objectType) : ?PropertyFetch
|
|
{
|
|
$nativeReflectionClass = $classReflection->getNativeReflection();
|
|
foreach ($nativeReflectionClass->getProperties() as $reflectionProperty) {
|
|
/** @var PhpPropertyReflection $phpPropertyReflection */
|
|
$phpPropertyReflection = $classReflection->getNativeProperty($reflectionProperty->getName());
|
|
$readableType = $phpPropertyReflection->getReadableType();
|
|
if (!$this->isMatchingType($readableType, $objectType)) {
|
|
continue;
|
|
}
|
|
return new PropertyFetch(new Variable('this'), $reflectionProperty->getName());
|
|
}
|
|
return null;
|
|
}
|
|
private function resolveConstructorParamProvidingType(ClassMethod $classMethod, ObjectType $objectType) : ?Variable
|
|
{
|
|
if (!$this->nodeNameResolver->isName($classMethod, MethodName::CONSTRUCT)) {
|
|
return null;
|
|
}
|
|
$variableName = $this->propertyNaming->fqnToVariableName($objectType);
|
|
return new Variable($variableName);
|
|
}
|
|
private function isMatchingType(Type $readableType, ObjectType $objectType) : bool
|
|
{
|
|
if ($readableType instanceof MixedType) {
|
|
return \false;
|
|
}
|
|
$readableType = TypeCombinator::removeNull($readableType);
|
|
if (!$readableType instanceof TypeWithClassName) {
|
|
return \false;
|
|
}
|
|
return $readableType->equals($objectType);
|
|
}
|
|
/**
|
|
* @return MethodReflection[]
|
|
*/
|
|
private function getClassMethodReflections(ClassReflection $classReflection) : array
|
|
{
|
|
$nativeReflection = $classReflection->getNativeReflection();
|
|
$methodReflections = [];
|
|
foreach ($nativeReflection->getMethods() as $reflectionMethod) {
|
|
$methodReflections[] = $classReflection->getNativeMethod($reflectionMethod->getName());
|
|
}
|
|
return $methodReflections;
|
|
}
|
|
}
|