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