1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection\Php;
4:
5: use PhpParser\Node;
6: use PhpParser\Node\Stmt\ClassMethod;
7: use PHPStan\Reflection\Assertions;
8: use PHPStan\Reflection\ClassMemberReflection;
9: use PHPStan\Reflection\ClassReflection;
10: use PHPStan\Reflection\ExtendedMethodReflection;
11: use PHPStan\Reflection\MissingMethodFromReflectionException;
12: use PHPStan\TrinaryLogic;
13: use PHPStan\Type\ArrayType;
14: use PHPStan\Type\BooleanType;
15: use PHPStan\Type\Generic\TemplateTypeMap;
16: use PHPStan\Type\IntegerType;
17: use PHPStan\Type\MixedType;
18: use PHPStan\Type\ObjectWithoutClassType;
19: use PHPStan\Type\StringType;
20: use PHPStan\Type\Type;
21: use PHPStan\Type\TypeCombinator;
22: use PHPStan\Type\VoidType;
23: use function in_array;
24: use function strtolower;
25:
26: /**
27: * @api
28: */
29: final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection
30: {
31:
32: /**
33: * @param Type[] $realParameterTypes
34: * @param Type[] $phpDocParameterTypes
35: * @param Type[] $realParameterDefaultValues
36: * @param array<string, bool> $immediatelyInvokedCallableParameters
37: * @param array<string, Type> $phpDocClosureThisTypeParameters
38: */
39: public function __construct(
40: private ClassReflection $declaringClass,
41: private ClassMethod $classMethod,
42: string $fileName,
43: TemplateTypeMap $templateTypeMap,
44: array $realParameterTypes,
45: array $phpDocParameterTypes,
46: array $realParameterDefaultValues,
47: Type $realReturnType,
48: ?Type $phpDocReturnType,
49: ?Type $throwType,
50: ?string $deprecatedDescription,
51: bool $isDeprecated,
52: bool $isInternal,
53: private bool $isFinal,
54: ?bool $isPure,
55: bool $acceptsNamedArguments,
56: Assertions $assertions,
57: private ?Type $selfOutType,
58: ?string $phpDocComment,
59: array $parameterOutTypes,
60: array $immediatelyInvokedCallableParameters,
61: array $phpDocClosureThisTypeParameters,
62: )
63: {
64: $name = strtolower($classMethod->name->name);
65: if (in_array($name, ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) {
66: $realReturnType = new VoidType();
67: }
68: if ($name === '__tostring') {
69: $realReturnType = new StringType();
70: }
71: if ($name === '__isset') {
72: $realReturnType = new BooleanType();
73: }
74: if ($name === '__sleep') {
75: $realReturnType = new ArrayType(new IntegerType(), new StringType());
76: }
77: if ($name === '__set_state') {
78: $realReturnType = TypeCombinator::intersect(new ObjectWithoutClassType(), $realReturnType);
79: }
80: if ($name === '__set') {
81: $realReturnType = new VoidType();
82: }
83:
84: if ($name === '__debuginfo') {
85: $realReturnType = TypeCombinator::intersect(TypeCombinator::addNull(
86: new ArrayType(new MixedType(true), new MixedType(true)),
87: ), $realReturnType);
88: }
89:
90: if ($name === '__unserialize') {
91: $realReturnType = new VoidType();
92: }
93: if ($name === '__serialize') {
94: $realReturnType = new ArrayType(new MixedType(true), new MixedType(true));
95: }
96:
97: parent::__construct(
98: $classMethod,
99: $fileName,
100: $templateTypeMap,
101: $realParameterTypes,
102: $phpDocParameterTypes,
103: $realParameterDefaultValues,
104: $realReturnType,
105: $phpDocReturnType,
106: $throwType,
107: $deprecatedDescription,
108: $isDeprecated,
109: $isInternal,
110: $isPure,
111: $acceptsNamedArguments,
112: $assertions,
113: $phpDocComment,
114: $parameterOutTypes,
115: $immediatelyInvokedCallableParameters,
116: $phpDocClosureThisTypeParameters,
117: );
118: }
119:
120: public function getDeclaringClass(): ClassReflection
121: {
122: return $this->declaringClass;
123: }
124:
125: public function getPrototype(): ClassMemberReflection
126: {
127: try {
128: return $this->declaringClass->getNativeMethod($this->getClassMethod()->name->name)->getPrototype();
129: } catch (MissingMethodFromReflectionException) {
130: return $this;
131: }
132: }
133:
134: private function getClassMethod(): ClassMethod
135: {
136: /** @var Node\Stmt\ClassMethod $functionLike */
137: $functionLike = $this->getFunctionLike();
138: return $functionLike;
139: }
140:
141: public function isStatic(): bool
142: {
143: return $this->getClassMethod()->isStatic();
144: }
145:
146: public function isPrivate(): bool
147: {
148: return $this->getClassMethod()->isPrivate();
149: }
150:
151: public function isPublic(): bool
152: {
153: return $this->getClassMethod()->isPublic();
154: }
155:
156: public function isFinal(): TrinaryLogic
157: {
158: return TrinaryLogic::createFromBoolean($this->classMethod->isFinal() || $this->isFinal);
159: }
160:
161: public function isFinalByKeyword(): TrinaryLogic
162: {
163: return TrinaryLogic::createFromBoolean($this->classMethod->isFinal());
164: }
165:
166: public function isBuiltin(): bool
167: {
168: return false;
169: }
170:
171: public function getSelfOutType(): ?Type
172: {
173: return $this->selfOutType;
174: }
175:
176: public function returnsByReference(): TrinaryLogic
177: {
178: return TrinaryLogic::createFromBoolean($this->getClassMethod()->returnsByRef());
179: }
180:
181: public function isAbstract(): TrinaryLogic
182: {
183: return TrinaryLogic::createFromBoolean($this->getClassMethod()->isAbstract());
184: }
185:
186: public function hasSideEffects(): TrinaryLogic
187: {
188: if (
189: strtolower($this->getName()) !== '__construct'
190: && $this->getReturnType()->isVoid()->yes()
191: ) {
192: return TrinaryLogic::createYes();
193: }
194: if ($this->isPure !== null) {
195: return TrinaryLogic::createFromBoolean(!$this->isPure);
196: }
197:
198: return TrinaryLogic::createMaybe();
199: }
200:
201: }
202: