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