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\PhpDoc\ResolvedPhpDocBlock;
8: use PHPStan\Reflection\Assertions;
9: use PHPStan\Reflection\AttributeReflection;
10: use PHPStan\Reflection\ClassMemberReflection;
11: use PHPStan\Reflection\ClassReflection;
12: use PHPStan\Reflection\ExtendedMethodReflection;
13: use PHPStan\Reflection\MissingMethodFromReflectionException;
14: use PHPStan\ShouldNotHappenException;
15: use PHPStan\TrinaryLogic;
16: use PHPStan\Type\ArrayType;
17: use PHPStan\Type\BooleanType;
18: use PHPStan\Type\Generic\TemplateTypeMap;
19: use PHPStan\Type\IntegerType;
20: use PHPStan\Type\MixedType;
21: use PHPStan\Type\NullType;
22: use PHPStan\Type\ObjectWithoutClassType;
23: use PHPStan\Type\StringType;
24: use PHPStan\Type\Type;
25: use PHPStan\Type\TypeCombinator;
26: use PHPStan\Type\UnionType;
27: use PHPStan\Type\VoidType;
28: use function in_array;
29: use function sprintf;
30: use function strtolower;
31:
32: /**
33: * @api
34: */
35: final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection
36: {
37:
38: /**
39: * @param Type[] $realParameterTypes
40: * @param Type[] $phpDocParameterTypes
41: * @param Type[] $realParameterDefaultValues
42: * @param array<string, list<AttributeReflection>> $parameterAttributes
43: * @param array<string, bool> $immediatelyInvokedCallableParameters
44: * @param array<string, Type> $phpDocClosureThisTypeParameters
45: * @param list<AttributeReflection> $attributes
46: */
47: public function __construct(
48: private ClassReflection $declaringClass,
49: private ClassMethod|Node\PropertyHook $classMethod,
50: private ?string $hookForProperty,
51: string $fileName,
52: TemplateTypeMap $templateTypeMap,
53: array $realParameterTypes,
54: array $phpDocParameterTypes,
55: array $realParameterDefaultValues,
56: array $parameterAttributes,
57: Type $realReturnType,
58: ?Type $phpDocReturnType,
59: ?Type $throwType,
60: ?string $deprecatedDescription,
61: bool $isDeprecated,
62: bool $isInternal,
63: private bool $isFinal,
64: ?bool $isPure,
65: bool $acceptsNamedArguments,
66: Assertions $assertions,
67: private ?Type $selfOutType,
68: ?string $phpDocComment,
69: private ?ResolvedPhpDocBlock $resolvedPhpDoc,
70: array $parameterOutTypes,
71: array $immediatelyInvokedCallableParameters,
72: array $phpDocClosureThisTypeParameters,
73: private bool $isConstructor,
74: array $attributes,
75: array $pureUnlessCallableIsImpureParameters,
76: )
77: {
78: if ($this->classMethod instanceof Node\PropertyHook) {
79: if ($this->hookForProperty === null) {
80: throw new ShouldNotHappenException('Hook was provided but property was not');
81: }
82: } elseif ($this->hookForProperty !== null) {
83: throw new ShouldNotHappenException('Hooked property was provided but hook was not');
84: }
85:
86: $name = strtolower($classMethod->name->name);
87: if ($this->isConstructor) {
88: $realReturnType = new VoidType();
89: }
90: if (in_array($name, ['__destruct', '__unset', '__wakeup', '__clone'], true)) {
91: $realReturnType = new VoidType();
92: }
93: if ($name === '__tostring') {
94: $realReturnType = new StringType();
95: }
96: if ($name === '__isset') {
97: $realReturnType = new BooleanType();
98: }
99: if ($name === '__sleep') {
100: $realReturnType = new ArrayType(new IntegerType(), new StringType());
101: }
102: if ($name === '__set_state') {
103: $realReturnType = TypeCombinator::intersect(new ObjectWithoutClassType(), $realReturnType);
104: }
105: if ($name === '__set') {
106: $realReturnType = new VoidType();
107: }
108:
109: if ($name === '__debuginfo') {
110: $realReturnType = TypeCombinator::intersect(new UnionType([new ArrayType(new MixedType(true), new MixedType(true)), new NullType()]), $realReturnType);
111: }
112:
113: if ($name === '__unserialize') {
114: $realReturnType = new VoidType();
115: }
116: if ($name === '__serialize') {
117: $realReturnType = new ArrayType(new MixedType(true), new MixedType(true));
118: }
119:
120: parent::__construct(
121: $classMethod,
122: $fileName,
123: $templateTypeMap,
124: $realParameterTypes,
125: $phpDocParameterTypes,
126: $realParameterDefaultValues,
127: $parameterAttributes,
128: $realReturnType,
129: $phpDocReturnType,
130: $throwType,
131: $deprecatedDescription,
132: $isDeprecated,
133: $isInternal,
134: $isPure,
135: $acceptsNamedArguments,
136: $assertions,
137: $phpDocComment,
138: $parameterOutTypes,
139: $immediatelyInvokedCallableParameters,
140: $phpDocClosureThisTypeParameters,
141: $attributes,
142: $pureUnlessCallableIsImpureParameters,
143: );
144: }
145:
146: public function getDeclaringClass(): ClassReflection
147: {
148: return $this->declaringClass;
149: }
150:
151: public function getPrototype(): ClassMemberReflection
152: {
153: try {
154: return $this->declaringClass->getNativeMethod($this->getClassMethod()->name->name)->getPrototype();
155: } catch (MissingMethodFromReflectionException) {
156: return $this;
157: }
158: }
159:
160: private function getClassMethod(): ClassMethod|Node\PropertyHook
161: {
162: /** @var Node\Stmt\ClassMethod|Node\PropertyHook $functionLike */
163: $functionLike = $this->getFunctionLike();
164: return $functionLike;
165: }
166:
167: public function getName(): string
168: {
169: $function = $this->getFunctionLike();
170: if (!$function instanceof Node\PropertyHook) {
171: return parent::getName();
172: }
173:
174: if ($this->hookForProperty === null) {
175: throw new ShouldNotHappenException('Hook was provided but property was not');
176: }
177:
178: return sprintf('$%s::%s', $this->hookForProperty, $function->name->toString());
179: }
180:
181: /**
182: * @phpstan-assert-if-true !null $this->getHookedPropertyName()
183: * @phpstan-assert-if-true !null $this->getPropertyHookName()
184: */
185: public function isPropertyHook(): bool
186: {
187: return $this->hookForProperty !== null;
188: }
189:
190: public function getHookedPropertyName(): ?string
191: {
192: return $this->hookForProperty;
193: }
194:
195: /**
196: * @return 'get'|'set'|null
197: */
198: public function getPropertyHookName(): ?string
199: {
200: $function = $this->getFunctionLike();
201: if (!$function instanceof Node\PropertyHook) {
202: return null;
203: }
204:
205: $name = $function->name->toLowerString();
206: if (!in_array($name, ['get', 'set'], true)) {
207: throw new ShouldNotHappenException(sprintf('Unknown property hook: %s', $name));
208: }
209:
210: return $name;
211: }
212:
213: public function isStatic(): bool
214: {
215: $method = $this->getClassMethod();
216: if ($method instanceof Node\PropertyHook) {
217: return false;
218: }
219:
220: return $method->isStatic();
221: }
222:
223: public function isPrivate(): bool
224: {
225: $method = $this->getClassMethod();
226: if ($method instanceof Node\PropertyHook) {
227: return false;
228: }
229:
230: return $method->isPrivate();
231: }
232:
233: public function isPublic(): bool
234: {
235: $method = $this->getClassMethod();
236: if ($method instanceof Node\PropertyHook) {
237: return true;
238: }
239:
240: return $method->isPublic();
241: }
242:
243: public function isFinal(): TrinaryLogic
244: {
245: $method = $this->getClassMethod();
246: if ($method instanceof Node\PropertyHook) {
247: return TrinaryLogic::createFromBoolean($method->isFinal());
248: }
249:
250: return TrinaryLogic::createFromBoolean($method->isFinal() || $this->isFinal);
251: }
252:
253: public function isFinalByKeyword(): TrinaryLogic
254: {
255: return TrinaryLogic::createFromBoolean($this->getClassMethod()->isFinal());
256: }
257:
258: public function isBuiltin(): bool
259: {
260: return false;
261: }
262:
263: public function getSelfOutType(): ?Type
264: {
265: return $this->selfOutType;
266: }
267:
268: public function returnsByReference(): TrinaryLogic
269: {
270: return TrinaryLogic::createFromBoolean($this->getClassMethod()->returnsByRef());
271: }
272:
273: public function isAbstract(): TrinaryLogic
274: {
275: $method = $this->getClassMethod();
276: if ($method instanceof Node\PropertyHook) {
277: return TrinaryLogic::createFromBoolean($method->body === null);
278: }
279:
280: return TrinaryLogic::createFromBoolean($method->isAbstract());
281: }
282:
283: public function isConstructor(): bool
284: {
285: return $this->isConstructor;
286: }
287:
288: public function hasSideEffects(): TrinaryLogic
289: {
290: if (
291: strtolower($this->getName()) !== '__construct'
292: && $this->getReturnType()->isVoid()->yes()
293: ) {
294: return TrinaryLogic::createYes();
295: }
296: if ($this->isPure !== null) {
297: return TrinaryLogic::createFromBoolean(!$this->isPure);
298: }
299:
300: return TrinaryLogic::createMaybe();
301: }
302:
303: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
304: {
305: return $this->resolvedPhpDoc;
306: }
307:
308: }
309: