1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Rules\RestrictedUsage;
4:
5: use PhpParser\Node;
6: use PhpParser\Node\Identifier;
7: use PhpParser\Node\Name;
8: use PHPStan\Analyser\Scope;
9: use PHPStan\DependencyInjection\AutowiredService;
10: use PHPStan\DependencyInjection\Container;
11: use PHPStan\Node\StaticMethodCallableNode;
12: use PHPStan\Reflection\ReflectionProvider;
13: use PHPStan\Rules\Rule;
14: use PHPStan\Rules\RuleErrorBuilder;
15: use PHPStan\Rules\RuleLevelHelper;
16: use PHPStan\Type\ErrorType;
17: use PHPStan\Type\Type;
18:
19: /**
20: * @implements Rule<StaticMethodCallableNode>
21: */
22: #[AutowiredService]
23: final class RestrictedStaticMethodCallableUsageRule implements Rule
24: {
25:
26: /** @var RestrictedMethodUsageExtension[] $extensions */
27: private ?array $extensions = null;
28:
29: public function __construct(
30: private Container $container,
31: private ReflectionProvider $reflectionProvider,
32: private RuleLevelHelper $ruleLevelHelper,
33: )
34: {
35: }
36:
37: public function getNodeType(): string
38: {
39: return StaticMethodCallableNode::class;
40: }
41:
42: /**
43: * @api
44: */
45: public function processNode(Node $node, Scope $scope): array
46: {
47: if (!$node->getName() instanceof Identifier) {
48: return [];
49: }
50:
51: /** @var RestrictedMethodUsageExtension[] $extensions */
52: $extensions = $this->extensions ??= $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG);
53: if ($extensions === []) {
54: return [];
55: }
56:
57: $methodName = $node->getName()->name;
58: $referencedClasses = [];
59:
60: if ($node->getClass() instanceof Name) {
61: $referencedClasses[] = $scope->resolveName($node->getClass());
62: } else {
63: $classTypeResult = $this->ruleLevelHelper->findTypeToCheck(
64: $scope,
65: $node->getClass(),
66: '', // We don't care about the error message
67: static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(),
68: );
69:
70: if ($classTypeResult->getType() instanceof ErrorType) {
71: return [];
72: }
73:
74: $referencedClasses = $classTypeResult->getReferencedClasses();
75: }
76:
77: $errors = [];
78: foreach ($referencedClasses as $referencedClass) {
79: if (!$this->reflectionProvider->hasClass($referencedClass)) {
80: continue;
81: }
82:
83: $classReflection = $this->reflectionProvider->getClass($referencedClass);
84: if (!$classReflection->hasMethod($methodName)) {
85: continue;
86: }
87:
88: $methodReflection = $classReflection->getMethod($methodName, $scope);
89: foreach ($extensions as $extension) {
90: $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope);
91: if ($restrictedUsage === null) {
92: continue;
93: }
94:
95: if ($classReflection->getName() !== $methodReflection->getDeclaringClass()->getName()) {
96: $rewrittenMethodReflection = new RewrittenDeclaringClassMethodReflection($classReflection, $methodReflection);
97: $rewrittenRestrictedUsage = $extension->isRestrictedMethodUsage($rewrittenMethodReflection, $scope);
98: if ($rewrittenRestrictedUsage === null) {
99: continue;
100: }
101: }
102:
103: $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage)
104: ->identifier($restrictedUsage->identifier)
105: ->build();
106: }
107: }
108:
109: return $errors;
110: }
111:
112: }
113: