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