1: | <?php |
2: | |
3: | declare(strict_types=1); |
4: | |
5: | namespace PHPStan\BetterReflection\Reflection; |
6: | |
7: | use Closure; |
8: | use Error; |
9: | use OutOfBoundsException; |
10: | use PhpParser\Modifiers; |
11: | use PhpParser\Node; |
12: | use PhpParser\Node\Stmt\Property as PropertyNode; |
13: | use PhpParser\NodeTraverser; |
14: | use PhpParser\NodeVisitor\FindingVisitor; |
15: | use ReflectionException; |
16: | use ReflectionProperty as CoreReflectionProperty; |
17: | use PHPStan\BetterReflection\NodeCompiler\CompiledValue; |
18: | use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue; |
19: | use PHPStan\BetterReflection\NodeCompiler\CompilerContext; |
20: | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter; |
21: | use PHPStan\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; |
22: | use PHPStan\BetterReflection\Reflection\Deprecated\DeprecatedHelper; |
23: | use PHPStan\BetterReflection\Reflection\Exception\ClassDoesNotExist; |
24: | use PHPStan\BetterReflection\Reflection\Exception\CodeLocationMissing; |
25: | use PHPStan\BetterReflection\Reflection\Exception\NoObjectProvided; |
26: | use PHPStan\BetterReflection\Reflection\Exception\NotAnObject; |
27: | use PHPStan\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass; |
28: | use PHPStan\BetterReflection\Reflection\StringCast\ReflectionPropertyStringCast; |
29: | use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound; |
30: | use PHPStan\BetterReflection\Reflector\Reflector; |
31: | use PHPStan\BetterReflection\Util\CalculateReflectionColumn; |
32: | use PHPStan\BetterReflection\Util\ClassExistenceChecker; |
33: | use PHPStan\BetterReflection\Util\Exception\NoNodePosition; |
34: | use PHPStan\BetterReflection\Util\GetLastDocComment; |
35: | |
36: | use function array_map; |
37: | use function assert; |
38: | use function count; |
39: | use function func_num_args; |
40: | use function is_object; |
41: | use function sprintf; |
42: | use function str_contains; |
43: | |
44: | |
45: | class ReflectionProperty |
46: | { |
47: | private Reflector $reflector; |
48: | private ReflectionClass $declaringClass; |
49: | private ReflectionClass $implementingClass; |
50: | private bool $isPromoted; |
51: | private bool $declaredAtCompileTime; |
52: | |
53: | private string $name; |
54: | |
55: | |
56: | private int $modifiers; |
57: | |
58: | |
59: | |
60: | |
61: | private $type; |
62: | |
63: | |
64: | |
65: | |
66: | private $default; |
67: | |
68: | |
69: | private $docComment; |
70: | |
71: | |
72: | private array $attributes; |
73: | |
74: | |
75: | private $startLine; |
76: | |
77: | |
78: | private $endLine; |
79: | |
80: | |
81: | private $startColumn; |
82: | |
83: | |
84: | private $endColumn; |
85: | |
86: | private bool $immediateVirtual; |
87: | |
88: | |
89: | private array $immediateHooks; |
90: | |
91: | |
92: | |
93: | |
94: | |
95: | private $cachedHooks = null; |
96: | |
97: | |
98: | |
99: | private $cachedVirtual = null; |
100: | |
101: | |
102: | |
103: | private $compiledDefaultValue = null; |
104: | |
105: | private function __construct(Reflector $reflector, PropertyNode $node, Node\PropertyItem $propertyNode, ReflectionClass $declaringClass, ReflectionClass $implementingClass, bool $isPromoted, bool $declaredAtCompileTime) |
106: | { |
107: | $this->reflector = $reflector; |
108: | $this->declaringClass = $declaringClass; |
109: | $this->implementingClass = $implementingClass; |
110: | $this->isPromoted = $isPromoted; |
111: | $this->declaredAtCompileTime = $declaredAtCompileTime; |
112: | $this->name = $propertyNode->name->name; |
113: | $this->modifiers = $this->computeModifiers($node); |
114: | $this->type = $this->createType($node); |
115: | $this->default = $propertyNode->default; |
116: | $this->docComment = GetLastDocComment::forNode($node); |
117: | $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups); |
118: | $this->immediateVirtual = $this->computeImmediateVirtual($node); |
119: | $this->immediateHooks = $this->createImmediateHooks($node); |
120: | $startLine = $node->getStartLine(); |
121: | if ($startLine === -1) { |
122: | $startLine = null; |
123: | } |
124: | $endLine = $node->getEndLine(); |
125: | if ($endLine === -1) { |
126: | $endLine = null; |
127: | } |
128: | |
129: | $this->startLine = $startLine; |
130: | |
131: | $this->endLine = $endLine; |
132: | try { |
133: | $this->startColumn = CalculateReflectionColumn::getStartColumn($declaringClass->getLocatedSource()->getSource(), $node); |
134: | } catch (NoNodePosition $exception) { |
135: | $this->startColumn = null; |
136: | } |
137: | try { |
138: | $this->endColumn = CalculateReflectionColumn::getEndColumn($declaringClass->getLocatedSource()->getSource(), $node); |
139: | } catch (NoNodePosition $exception) { |
140: | $this->endColumn = null; |
141: | } |
142: | } |
143: | |
144: | |
145: | |
146: | |
147: | |
148: | |
149: | |
150: | |
151: | |
152: | |
153: | public static function createFromInstance(object $instance, string $propertyName): self |
154: | { |
155: | $property = ReflectionClass::createFromInstance($instance)->getProperty($propertyName); |
156: | |
157: | if ($property === null) { |
158: | throw new OutOfBoundsException(sprintf('Could not find property: %s', $propertyName)); |
159: | } |
160: | |
161: | return $property; |
162: | } |
163: | |
164: | |
165: | public function withImplementingClass(ReflectionClass $implementingClass): self |
166: | { |
167: | $clone = clone $this; |
168: | $clone->implementingClass = $implementingClass; |
169: | |
170: | if ($clone->type !== null) { |
171: | $clone->type = $clone->type->withOwner($clone); |
172: | } |
173: | |
174: | $clone->attributes = array_map(static fn (ReflectionAttribute $attribute): ReflectionAttribute => $attribute->withOwner($clone), $this->attributes); |
175: | |
176: | $this->compiledDefaultValue = null; |
177: | |
178: | return $clone; |
179: | } |
180: | |
181: | |
182: | public function __toString(): string |
183: | { |
184: | return ReflectionPropertyStringCast::toString($this); |
185: | } |
186: | |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | public static function createFromNode(Reflector $reflector, PropertyNode $node, Node\PropertyItem $propertyProperty, ReflectionClass $declaringClass, ReflectionClass $implementingClass, bool $isPromoted = false, bool $declaredAtCompileTime = true): self |
193: | { |
194: | return new self( |
195: | $reflector, |
196: | $node, |
197: | $propertyProperty, |
198: | $declaringClass, |
199: | $implementingClass, |
200: | $isPromoted, |
201: | $declaredAtCompileTime, |
202: | ); |
203: | } |
204: | |
205: | |
206: | |
207: | |
208: | |
209: | |
210: | |
211: | |
212: | public function isDefault(): bool |
213: | { |
214: | return $this->declaredAtCompileTime; |
215: | } |
216: | |
217: | public function isDynamic(): bool |
218: | { |
219: | return ! $this->isDefault(); |
220: | } |
221: | |
222: | |
223: | |
224: | |
225: | |
226: | |
227: | public function getModifiers(): int |
228: | { |
229: | |
230: | $modifiers = $this->modifiers |
231: | + ($this->isVirtual() ? ReflectionPropertyAdapter::IS_VIRTUAL_COMPATIBILITY : 0); |
232: | |
233: | return $modifiers; |
234: | } |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | public function getName(): string |
242: | { |
243: | return $this->name; |
244: | } |
245: | |
246: | |
247: | |
248: | |
249: | public function isPrivate(): bool |
250: | { |
251: | return (bool) ($this->modifiers & CoreReflectionProperty::IS_PRIVATE); |
252: | } |
253: | |
254: | public function isPrivateSet(): bool |
255: | { |
256: | return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY); |
257: | } |
258: | |
259: | |
260: | |
261: | |
262: | public function isProtected(): bool |
263: | { |
264: | return (bool) ($this->modifiers & CoreReflectionProperty::IS_PROTECTED); |
265: | } |
266: | |
267: | public function isProtectedSet(): bool |
268: | { |
269: | return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY); |
270: | } |
271: | |
272: | |
273: | |
274: | |
275: | public function isPublic(): bool |
276: | { |
277: | return (bool) ($this->modifiers & CoreReflectionProperty::IS_PUBLIC); |
278: | } |
279: | |
280: | |
281: | |
282: | |
283: | public function isStatic(): bool |
284: | { |
285: | return (bool) ($this->modifiers & CoreReflectionProperty::IS_STATIC); |
286: | } |
287: | |
288: | public function isFinal(): bool |
289: | { |
290: | return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY); |
291: | } |
292: | |
293: | public function isAbstract(): bool |
294: | { |
295: | return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY); |
296: | } |
297: | |
298: | public function isPromoted(): bool |
299: | { |
300: | return $this->isPromoted; |
301: | } |
302: | |
303: | public function isInitialized(?object $object = null): bool |
304: | { |
305: | if ($object === null && $this->isStatic()) { |
306: | return ! $this->hasType() || $this->hasDefaultValue(); |
307: | } |
308: | |
309: | try { |
310: | $this->getValue($object); |
311: | |
312: | return true; |
313: | |
314: | |
315: | } catch (Error $e) { |
316: | if (strpos($e->getMessage(), 'must not be accessed before initialization') !== false) { |
317: | return false; |
318: | } |
319: | |
320: | throw $e; |
321: | } |
322: | } |
323: | |
324: | public function isReadOnly(): bool |
325: | { |
326: | return ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY) |
327: | || $this->getDeclaringClass()->isReadOnly(); |
328: | } |
329: | |
330: | public function getDeclaringClass(): ReflectionClass |
331: | { |
332: | return $this->declaringClass; |
333: | } |
334: | |
335: | public function getImplementingClass(): ReflectionClass |
336: | { |
337: | return $this->implementingClass; |
338: | } |
339: | |
340: | |
341: | public function getDocComment(): ?string |
342: | { |
343: | return $this->docComment; |
344: | } |
345: | |
346: | public function hasDefaultValue(): bool |
347: | { |
348: | return ! $this->hasType() || $this->default !== null; |
349: | } |
350: | |
351: | public function getDefaultValueExpression(): ?\PhpParser\Node\Expr |
352: | { |
353: | return $this->default; |
354: | } |
355: | |
356: | |
357: | |
358: | |
359: | |
360: | |
361: | public function getDefaultValue() |
362: | { |
363: | if ($this->default === null) { |
364: | return null; |
365: | } |
366: | |
367: | if ($this->compiledDefaultValue === null) { |
368: | $this->compiledDefaultValue = (new CompileNodeToValue())->__invoke( |
369: | $this->default, |
370: | new CompilerContext( |
371: | $this->reflector, |
372: | $this, |
373: | ), |
374: | ); |
375: | } |
376: | |
377: | |
378: | $value = $this->compiledDefaultValue->value; |
379: | |
380: | return $value; |
381: | } |
382: | |
383: | public function isDeprecated(): bool |
384: | { |
385: | return DeprecatedHelper::isDeprecated($this); |
386: | } |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | |
394: | |
395: | public function getStartLine(): int |
396: | { |
397: | if ($this->startLine === null) { |
398: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
399: | } |
400: | |
401: | return $this->startLine; |
402: | } |
403: | |
404: | |
405: | |
406: | |
407: | |
408: | |
409: | |
410: | |
411: | public function getEndLine(): int |
412: | { |
413: | if ($this->endLine === null) { |
414: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
415: | } |
416: | |
417: | return $this->endLine; |
418: | } |
419: | |
420: | |
421: | |
422: | |
423: | |
424: | |
425: | public function getStartColumn(): int |
426: | { |
427: | if ($this->startColumn === null) { |
428: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
429: | } |
430: | |
431: | return $this->startColumn; |
432: | } |
433: | |
434: | |
435: | |
436: | |
437: | |
438: | |
439: | public function getEndColumn(): int |
440: | { |
441: | if ($this->endColumn === null) { |
442: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
443: | } |
444: | |
445: | return $this->endColumn; |
446: | } |
447: | |
448: | |
449: | public function getAttributes(): array |
450: | { |
451: | return $this->attributes; |
452: | } |
453: | |
454: | |
455: | public function getAttributesByName(string $name): array |
456: | { |
457: | return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); |
458: | } |
459: | |
460: | |
461: | |
462: | |
463: | |
464: | |
465: | public function getAttributesByInstance(string $className): array |
466: | { |
467: | return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); |
468: | } |
469: | |
470: | |
471: | |
472: | |
473: | |
474: | |
475: | |
476: | public function getValue(?object $object = null) |
477: | { |
478: | $implementingClassName = $this->getImplementingClass()->getName(); |
479: | |
480: | if ($this->isStatic()) { |
481: | $this->assertClassExist($implementingClassName); |
482: | |
483: | $closure = Closure::bind(fn (string $implementingClassName, string $propertyName) => $implementingClassName::${$propertyName}, null, $implementingClassName); |
484: | |
485: | |
486: | assert($closure instanceof Closure); |
487: | |
488: | return $closure->__invoke($implementingClassName, $this->getName()); |
489: | } |
490: | |
491: | $instance = $this->assertObject($object); |
492: | |
493: | $closure = Closure::bind(fn (object $instance, string $propertyName) => $instance->{$propertyName}, $instance, $implementingClassName); |
494: | |
495: | |
496: | assert($closure instanceof Closure); |
497: | |
498: | return $closure->__invoke($instance, $this->getName()); |
499: | } |
500: | |
501: | |
502: | |
503: | |
504: | |
505: | |
506: | |
507: | |
508: | |
509: | public function setValue($object, $value = null): void |
510: | { |
511: | $implementingClassName = $this->getImplementingClass()->getName(); |
512: | |
513: | if ($this->isStatic()) { |
514: | $this->assertClassExist($implementingClassName); |
515: | |
516: | $closure = Closure::bind(function (string $_implementingClassName, string $_propertyName, $value): void { |
517: | |
518: | $_implementingClassName::${$_propertyName} = $value; |
519: | }, null, $implementingClassName); |
520: | |
521: | |
522: | assert($closure instanceof Closure); |
523: | |
524: | $closure->__invoke($implementingClassName, $this->getName(), func_num_args() === 2 ? $value : $object); |
525: | |
526: | return; |
527: | } |
528: | |
529: | $instance = $this->assertObject($object); |
530: | |
531: | $closure = Closure::bind(function (object $instance, string $propertyName, $value): void { |
532: | $instance->{$propertyName} = $value; |
533: | }, $instance, $implementingClassName); |
534: | |
535: | |
536: | assert($closure instanceof Closure); |
537: | |
538: | $closure->__invoke($instance, $this->getName(), $value); |
539: | } |
540: | |
541: | |
542: | |
543: | |
544: | public function allowsNull(): bool |
545: | { |
546: | return $this->type === null || $this->type->allowsNull(); |
547: | } |
548: | |
549: | |
550: | |
551: | |
552: | private function createType(PropertyNode $node) |
553: | { |
554: | $type = $node->type; |
555: | |
556: | if ($type === null) { |
557: | return null; |
558: | } |
559: | |
560: | assert($type instanceof Node\Identifier || $type instanceof Node\Name || $type instanceof Node\NullableType || $type instanceof Node\UnionType || $type instanceof Node\IntersectionType); |
561: | |
562: | return ReflectionType::createFromNode($this->reflector, $this, $type); |
563: | } |
564: | |
565: | |
566: | |
567: | |
568: | |
569: | |
570: | |
571: | |
572: | public function getType() |
573: | { |
574: | return $this->type; |
575: | } |
576: | |
577: | |
578: | |
579: | |
580: | |
581: | |
582: | public function hasType(): bool |
583: | { |
584: | return $this->type !== null; |
585: | } |
586: | |
587: | public function isVirtual(): bool |
588: | { |
589: | $this->cachedVirtual ??= $this->createCachedVirtual(); |
590: | |
591: | return $this->cachedVirtual; |
592: | } |
593: | |
594: | public function hasHooks(): bool |
595: | { |
596: | return $this->getHooks() !== []; |
597: | } |
598: | |
599: | |
600: | |
601: | |
602: | public function hasHook(string $hookType): bool |
603: | { |
604: | return isset($this->getHooks()[$hookType]); |
605: | } |
606: | |
607: | |
608: | |
609: | |
610: | public function getHook(string $hookType): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod |
611: | { |
612: | return $this->getHooks()[$hookType] ?? null; |
613: | } |
614: | |
615: | |
616: | public function getHooks(): array |
617: | { |
618: | $this->cachedHooks ??= $this->createCachedHooks(); |
619: | |
620: | return $this->cachedHooks; |
621: | } |
622: | |
623: | |
624: | |
625: | |
626: | |
627: | |
628: | private function assertClassExist(string $className): void |
629: | { |
630: | if (! ClassExistenceChecker::classExists($className, true) && ! ClassExistenceChecker::traitExists($className, true)) { |
631: | throw new ClassDoesNotExist('Property cannot be retrieved as the class does not exist'); |
632: | } |
633: | } |
634: | |
635: | |
636: | |
637: | |
638: | |
639: | |
640: | |
641: | |
642: | |
643: | private function assertObject($object): object |
644: | { |
645: | if ($object === null) { |
646: | throw NoObjectProvided::create(); |
647: | } |
648: | |
649: | if (! is_object($object)) { |
650: | throw NotAnObject::fromNonObject($object); |
651: | } |
652: | |
653: | $implementingClassName = $this->getImplementingClass()->getName(); |
654: | |
655: | if (get_class($object) !== $implementingClassName) { |
656: | throw ObjectNotInstanceOfClass::fromClassName($implementingClassName); |
657: | } |
658: | |
659: | return $object; |
660: | } |
661: | |
662: | |
663: | private function computeModifiers(PropertyNode $node): int |
664: | { |
665: | $modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY : 0; |
666: | $modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0; |
667: | $modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0; |
668: | $modifiers += $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0; |
669: | $modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0; |
670: | $modifiers += $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0; |
671: | $modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0; |
672: | $modifiers += ($node->flags & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY) === ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0; |
673: | $modifiers += ($node->flags & Modifiers::ABSTRACT) === Modifiers::ABSTRACT ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0; |
674: | |
675: | |
676: | return $modifiers; |
677: | } |
678: | |
679: | private function computeImmediateVirtual(PropertyNode $node): bool |
680: | { |
681: | if ($node->hooks === []) { |
682: | return false; |
683: | } |
684: | |
685: | $setHook = null; |
686: | $getHook = null; |
687: | |
688: | foreach ($node->hooks as $hook) { |
689: | if ($hook->name->name === 'set') { |
690: | $setHook = $hook; |
691: | } elseif ($hook->name->name === 'get') { |
692: | $getHook = $hook; |
693: | } |
694: | } |
695: | |
696: | if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnSetHook($setHook)) { |
697: | return false; |
698: | } |
699: | |
700: | if ($getHook === null) { |
701: | return true; |
702: | } |
703: | |
704: | return $this->computeImmediateVirtualBasedOnGetHook($getHook); |
705: | } |
706: | |
707: | private function computeImmediateVirtualBasedOnGetHook(Node\PropertyHook $getHook): bool |
708: | { |
709: | $getHookBody = $getHook->getStmts(); |
710: | |
711: | |
712: | if ($getHookBody === null) { |
713: | return true; |
714: | } |
715: | |
716: | return ! $this->isHookUsingThisProperty($getHook); |
717: | } |
718: | |
719: | private function computeImmediateVirtualBasedOnSetHook(Node\PropertyHook $setHook): bool |
720: | { |
721: | $setHookBody = $setHook->getStmts(); |
722: | |
723: | |
724: | if ($setHookBody === null) { |
725: | return true; |
726: | } |
727: | |
728: | |
729: | if (count($setHookBody) === 1 && $setHookBody[0] instanceof Node\Stmt\Return_) { |
730: | return false; |
731: | } |
732: | |
733: | return ! $this->isHookUsingThisProperty($setHook); |
734: | } |
735: | |
736: | private function isHookUsingThisProperty(Node\PropertyHook $hook): bool |
737: | { |
738: | $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch); |
739: | $traverser = new NodeTraverser($visitor); |
740: | $traverser->traverse([$hook]); |
741: | |
742: | foreach ($visitor->getFoundNodes() as $propertyFetchNode) { |
743: | assert($propertyFetchNode instanceof Node\Expr\PropertyFetch); |
744: | |
745: | if ( |
746: | $propertyFetchNode->var instanceof Node\Expr\Variable |
747: | && $propertyFetchNode->var->name === 'this' |
748: | && $propertyFetchNode->name instanceof Node\Identifier |
749: | && $propertyFetchNode->name->name === $this->name |
750: | ) { |
751: | return true; |
752: | } |
753: | } |
754: | |
755: | return false; |
756: | } |
757: | |
758: | |
759: | private function createImmediateHooks(PropertyNode $node): array |
760: | { |
761: | $hooks = []; |
762: | |
763: | foreach ($node->hooks as $hook) { |
764: | $hookName = $hook->name->name; |
765: | assert($hookName === 'get' || $hookName === 'set'); |
766: | |
767: | $hookType = $node->type; |
768: | assert($hookType === null || $hookType instanceof Node\Identifier || $hookType instanceof Node\Name || $hookType instanceof Node\NullableType || $hookType instanceof Node\UnionType || $hookType instanceof Node\IntersectionType); |
769: | |
770: | $hooks[$hookName] = ReflectionMethod::createFromPropertyHook( |
771: | $this->reflector, |
772: | $hook, |
773: | $this->getDeclaringClass()->getLocatedSource(), |
774: | sprintf('$%s::%s', $this->name, $hookName), |
775: | $hookType, |
776: | $this->getDeclaringClass(), |
777: | $this->getImplementingClass(), |
778: | $this->getDeclaringClass(), |
779: | $this, |
780: | ); |
781: | } |
782: | |
783: | return $hooks; |
784: | } |
785: | |
786: | private function createCachedVirtual(): bool |
787: | { |
788: | if (! $this->immediateVirtual) { |
789: | return false; |
790: | } |
791: | |
792: | return (($nullsafeVariable1 = $this->getParentProperty()) ? $nullsafeVariable1->isVirtual() : null) ?? true; |
793: | } |
794: | |
795: | |
796: | private function createCachedHooks(): array |
797: | { |
798: | $hooks = $this->immediateHooks; |
799: | |
800: | |
801: | if (isset($hooks['get'], $hooks['set'])) { |
802: | return $hooks; |
803: | } |
804: | |
805: | $parentHooks = (($nullsafeVariable2 = $this->getParentProperty()) ? $nullsafeVariable2->getHooks() : null) ?? []; |
806: | |
807: | foreach ($parentHooks as $hookName => $parentHook) { |
808: | if (isset($hooks[$hookName])) { |
809: | continue; |
810: | } |
811: | |
812: | $hooks[$hookName] = $parentHook; |
813: | } |
814: | |
815: | return $hooks; |
816: | } |
817: | |
818: | private function getParentProperty(): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty |
819: | { |
820: | return ($nullsafeVariable3 = $this->getDeclaringClass()->getParentClass()) ? $nullsafeVariable3->getProperty($this->name) : null; |
821: | } |
822: | } |
823: | |