mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-23 19:24:48 +01:00
180 lines
5.7 KiB
PHP
180 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Rector\Privatization\NodeAnalyzer;
|
|
|
|
use PhpParser\Node\Expr\MethodCall;
|
|
use PhpParser\Node\Expr\StaticCall;
|
|
use PhpParser\Node\Stmt\Class_;
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
use PHPStan\Reflection\ReflectionProvider;
|
|
use PHPStan\Type\ObjectType;
|
|
use PHPStan\Type\TypeWithClassName;
|
|
use Rector\NodeCollector\NodeCollector\NodeRepository;
|
|
use Rector\NodeCollector\ValueObject\ArrayCallable;
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
use Rector\NodeTypeResolver\NodeTypeResolver;
|
|
|
|
final class ClassMethodExternalCallNodeAnalyzer
|
|
{
|
|
/**
|
|
* @var NodeNameResolver
|
|
*/
|
|
private $nodeNameResolver;
|
|
|
|
/**
|
|
* @var NodeTypeResolver
|
|
*/
|
|
private $nodeTypeResolver;
|
|
|
|
/**
|
|
* @var EventSubscriberMethodNamesResolver
|
|
*/
|
|
private $eventSubscriberMethodNamesResolver;
|
|
|
|
/**
|
|
* @var NodeRepository
|
|
*/
|
|
private $nodeRepository;
|
|
|
|
/**
|
|
* @var ReflectionProvider
|
|
*/
|
|
private $reflectionProvider;
|
|
|
|
public function __construct(
|
|
EventSubscriberMethodNamesResolver $eventSubscriberMethodNamesResolver,
|
|
NodeRepository $nodeRepository,
|
|
NodeNameResolver $nodeNameResolver,
|
|
NodeTypeResolver $nodeTypeResolver,
|
|
ReflectionProvider $reflectionProvider
|
|
) {
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
|
$this->nodeTypeResolver = $nodeTypeResolver;
|
|
$this->eventSubscriberMethodNamesResolver = $eventSubscriberMethodNamesResolver;
|
|
$this->nodeRepository = $nodeRepository;
|
|
$this->reflectionProvider = $reflectionProvider;
|
|
}
|
|
|
|
public function hasExternalCall(ClassMethod $classMethod): bool
|
|
{
|
|
$methodCalls = $this->nodeRepository->findCallsByClassMethod($classMethod);
|
|
|
|
/** @var string $methodName */
|
|
$methodName = $this->nodeNameResolver->getName($classMethod);
|
|
if ($this->isArrayCallable($classMethod, $methodCalls, $methodName)) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->isEventSubscriberMethod($classMethod, $methodName)) {
|
|
return true;
|
|
}
|
|
|
|
return $this->getExternalCalls($classMethod, $methodCalls) !== [];
|
|
}
|
|
|
|
/**
|
|
* @param MethodCall[]|StaticCall[]|ArrayCallable[] $methodCalls
|
|
* @return MethodCall[]
|
|
*/
|
|
public function getExternalCalls(ClassMethod $classMethod, array $methodCalls = []): array
|
|
{
|
|
/** @var MethodCall[]|StaticCall[]|ArrayCallable[] $methodCalls */
|
|
$methodCalls = $methodCalls ?: $this->nodeRepository->findCallsByClassMethod($classMethod);
|
|
|
|
/**
|
|
* remove static calls and [$this, 'call']
|
|
* @var MethodCall[] $methodCalls
|
|
*/
|
|
$methodCalls = array_filter($methodCalls, function (object $node): bool {
|
|
return $node instanceof MethodCall;
|
|
});
|
|
|
|
foreach ($methodCalls as $methodCall) {
|
|
$callerType = $this->nodeTypeResolver->resolve($methodCall->var);
|
|
|
|
if (! $callerType instanceof TypeWithClassName) {
|
|
// unable to handle reliably
|
|
return $methodCalls;
|
|
}
|
|
|
|
// external call
|
|
$nodeClassName = $methodCall->getAttribute(AttributeKey::CLASS_NAME);
|
|
if ($nodeClassName !== $callerType->getClassName()) {
|
|
return $methodCalls;
|
|
}
|
|
|
|
if (! $this->reflectionProvider->hasClass($callerType->getClassName())) {
|
|
return $methodCalls;
|
|
}
|
|
|
|
$methodName = $this->nodeNameResolver->getName($classMethod);
|
|
|
|
$classReflection = $this->reflectionProvider->getClass($callerType->getClassName());
|
|
$scope = $methodCall->getAttribute(AttributeKey::SCOPE);
|
|
$reflectionMethod = $classReflection->getMethod($methodName, $scope);
|
|
|
|
// parent class name, must be at least protected
|
|
$reflectionClass = $reflectionMethod->getDeclaringClass();
|
|
if ($reflectionClass->getName() !== $nodeClassName) {
|
|
return $methodCalls;
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @param StaticCall[]|MethodCall[]|ArrayCallable[] $methodCalls
|
|
*/
|
|
private function isArrayCallable(ClassMethod $classMethod, array $methodCalls, string $methodName): bool
|
|
{
|
|
/** @var ArrayCallable[] $arrayCallables */
|
|
$arrayCallables = array_filter($methodCalls, function (object $node): bool {
|
|
return $node instanceof ArrayCallable;
|
|
});
|
|
|
|
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
|
|
|
|
foreach ($arrayCallables as $arrayCallable) {
|
|
if ($className !== $arrayCallable->getClass()) {
|
|
continue;
|
|
}
|
|
if ($methodName !== $arrayCallable->getMethod()) {
|
|
continue;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function isEventSubscriberMethod(ClassMethod $classMethod, string $methodName): bool
|
|
{
|
|
$classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
|
if (! $classLike instanceof Class_) {
|
|
return false;
|
|
}
|
|
|
|
if (! $this->nodeTypeResolver->isObjectType(
|
|
$classLike,
|
|
new ObjectType('Symfony\Component\EventDispatcher\EventSubscriberInterface')
|
|
)) {
|
|
return false;
|
|
}
|
|
|
|
$getSubscribedEventsClassMethod = $classLike->getMethod('getSubscribedEvents');
|
|
if (! $getSubscribedEventsClassMethod instanceof ClassMethod) {
|
|
return false;
|
|
}
|
|
|
|
$methodNames = $this->eventSubscriberMethodNamesResolver->resolveFromClassMethod(
|
|
$getSubscribedEventsClassMethod
|
|
);
|
|
|
|
return in_array($methodName, $methodNames, true);
|
|
}
|
|
}
|