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: | || $this->declaringClass->isInterface(); |
295: | } |
296: | |
297: | public function isPromoted(): bool |
298: | { |
299: | return $this->isPromoted; |
300: | } |
301: | |
302: | public function isInitialized(?object $object = null): bool |
303: | { |
304: | if ($object === null && $this->isStatic()) { |
305: | return ! $this->hasType() || $this->hasDefaultValue(); |
306: | } |
307: | |
308: | try { |
309: | $this->getValue($object); |
310: | |
311: | return true; |
312: | |
313: | |
314: | } catch (Error $e) { |
315: | if (strpos($e->getMessage(), 'must not be accessed before initialization') !== false) { |
316: | return false; |
317: | } |
318: | |
319: | throw $e; |
320: | } |
321: | } |
322: | |
323: | public function isReadOnly(): bool |
324: | { |
325: | return ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY) |
326: | || $this->getDeclaringClass()->isReadOnly(); |
327: | } |
328: | |
329: | public function getDeclaringClass(): ReflectionClass |
330: | { |
331: | return $this->declaringClass; |
332: | } |
333: | |
334: | public function getImplementingClass(): ReflectionClass |
335: | { |
336: | return $this->implementingClass; |
337: | } |
338: | |
339: | |
340: | public function getDocComment(): ?string |
341: | { |
342: | return $this->docComment; |
343: | } |
344: | |
345: | public function hasDefaultValue(): bool |
346: | { |
347: | return ! $this->hasType() || $this->default !== null; |
348: | } |
349: | |
350: | public function getDefaultValueExpression(): ?\PhpParser\Node\Expr |
351: | { |
352: | return $this->default; |
353: | } |
354: | |
355: | |
356: | |
357: | |
358: | |
359: | |
360: | public function getDefaultValue() |
361: | { |
362: | if ($this->default === null) { |
363: | return null; |
364: | } |
365: | |
366: | if ($this->compiledDefaultValue === null) { |
367: | $this->compiledDefaultValue = (new CompileNodeToValue())->__invoke( |
368: | $this->default, |
369: | new CompilerContext( |
370: | $this->reflector, |
371: | $this, |
372: | ), |
373: | ); |
374: | } |
375: | |
376: | |
377: | $value = $this->compiledDefaultValue->value; |
378: | |
379: | return $value; |
380: | } |
381: | |
382: | public function isDeprecated(): bool |
383: | { |
384: | return DeprecatedHelper::isDeprecated($this); |
385: | } |
386: | |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | |
394: | public function getStartLine(): int |
395: | { |
396: | if ($this->startLine === null) { |
397: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
398: | } |
399: | |
400: | return $this->startLine; |
401: | } |
402: | |
403: | |
404: | |
405: | |
406: | |
407: | |
408: | |
409: | |
410: | public function getEndLine(): int |
411: | { |
412: | if ($this->endLine === null) { |
413: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
414: | } |
415: | |
416: | return $this->endLine; |
417: | } |
418: | |
419: | |
420: | |
421: | |
422: | |
423: | |
424: | public function getStartColumn(): int |
425: | { |
426: | if ($this->startColumn === null) { |
427: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
428: | } |
429: | |
430: | return $this->startColumn; |
431: | } |
432: | |
433: | |
434: | |
435: | |
436: | |
437: | |
438: | public function getEndColumn(): int |
439: | { |
440: | if ($this->endColumn === null) { |
441: | throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName())); |
442: | } |
443: | |
444: | return $this->endColumn; |
445: | } |
446: | |
447: | |
448: | public function getAttributes(): array |
449: | { |
450: | return $this->attributes; |
451: | } |
452: | |
453: | |
454: | public function getAttributesByName(string $name): array |
455: | { |
456: | return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); |
457: | } |
458: | |
459: | |
460: | |
461: | |
462: | |
463: | |
464: | public function getAttributesByInstance(string $className): array |
465: | { |
466: | return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); |
467: | } |
468: | |
469: | |
470: | |
471: | |
472: | |
473: | |
474: | |
475: | public function getValue(?object $object = null) |
476: | { |
477: | $implementingClassName = $this->getImplementingClass()->getName(); |
478: | |
479: | if ($this->isStatic()) { |
480: | $this->assertClassExist($implementingClassName); |
481: | |
482: | $closure = Closure::bind(fn (string $implementingClassName, string $propertyName) => $implementingClassName::${$propertyName}, null, $implementingClassName); |
483: | |
484: | |
485: | assert($closure instanceof Closure); |
486: | |
487: | return $closure->__invoke($implementingClassName, $this->getName()); |
488: | } |
489: | |
490: | $instance = $this->assertObject($object); |
491: | |
492: | $closure = Closure::bind(fn (object $instance, string $propertyName) => $instance->{$propertyName}, $instance, $implementingClassName); |
493: | |
494: | |
495: | assert($closure instanceof Closure); |
496: | |
497: | return $closure->__invoke($instance, $this->getName()); |
498: | } |
499: | |
500: | |
501: | |
502: | |
503: | |
504: | |
505: | |
506: | |
507: | |
508: | public function setValue($object, $value = null): void |
509: | { |
510: | $implementingClassName = $this->getImplementingClass()->getName(); |
511: | |
512: | if ($this->isStatic()) { |
513: | $this->assertClassExist($implementingClassName); |
514: | |
515: | $closure = Closure::bind(function (string $_implementingClassName, string $_propertyName, $value): void { |
516: | |
517: | $_implementingClassName::${$_propertyName} = $value; |
518: | }, null, $implementingClassName); |
519: | |
520: | |
521: | assert($closure instanceof Closure); |
522: | |
523: | $closure->__invoke($implementingClassName, $this->getName(), func_num_args() === 2 ? $value : $object); |
524: | |
525: | return; |
526: | } |
527: | |
528: | $instance = $this->assertObject($object); |
529: | |
530: | $closure = Closure::bind(function (object $instance, string $propertyName, $value): void { |
531: | $instance->{$propertyName} = $value; |
532: | }, $instance, $implementingClassName); |
533: | |
534: | |
535: | assert($closure instanceof Closure); |
536: | |
537: | $closure->__invoke($instance, $this->getName(), $value); |
538: | } |
539: | |
540: | |
541: | |
542: | |
543: | public function allowsNull(): bool |
544: | { |
545: | return $this->type === null || $this->type->allowsNull(); |
546: | } |
547: | |
548: | |
549: | |
550: | |
551: | private function createType(PropertyNode $node) |
552: | { |
553: | $type = $node->type; |
554: | |
555: | if ($type === null) { |
556: | return null; |
557: | } |
558: | |
559: | assert($type instanceof Node\Identifier || $type instanceof Node\Name || $type instanceof Node\NullableType || $type instanceof Node\UnionType || $type instanceof Node\IntersectionType); |
560: | |
561: | return ReflectionType::createFromNode($this->reflector, $this, $type); |
562: | } |
563: | |
564: | |
565: | |
566: | |
567: | |
568: | |
569: | |
570: | |
571: | public function getType() |
572: | { |
573: | return $this->type; |
574: | } |
575: | |
576: | |
577: | |
578: | |
579: | |
580: | |
581: | public function hasType(): bool |
582: | { |
583: | return $this->type !== null; |
584: | } |
585: | |
586: | public function isVirtual(): bool |
587: | { |
588: | $this->cachedVirtual ??= $this->createCachedVirtual(); |
589: | |
590: | return $this->cachedVirtual; |
591: | } |
592: | |
593: | public function hasHooks(): bool |
594: | { |
595: | return $this->getHooks() !== []; |
596: | } |
597: | |
598: | |
599: | |
600: | |
601: | public function hasHook(string $hookType): bool |
602: | { |
603: | return isset($this->getHooks()[$hookType]); |
604: | } |
605: | |
606: | |
607: | |
608: | |
609: | public function getHook(string $hookType): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod |
610: | { |
611: | return $this->getHooks()[$hookType] ?? null; |
612: | } |
613: | |
614: | |
615: | public function getHooks(): array |
616: | { |
617: | $this->cachedHooks ??= $this->createCachedHooks(); |
618: | |
619: | return $this->cachedHooks; |
620: | } |
621: | |
622: | |
623: | |
624: | |
625: | |
626: | |
627: | private function assertClassExist(string $className): void |
628: | { |
629: | if (! ClassExistenceChecker::classExists($className, true) && ! ClassExistenceChecker::traitExists($className, true)) { |
630: | throw new ClassDoesNotExist('Property cannot be retrieved as the class does not exist'); |
631: | } |
632: | } |
633: | |
634: | |
635: | |
636: | |
637: | |
638: | |
639: | |
640: | |
641: | |
642: | private function assertObject($object): object |
643: | { |
644: | if ($object === null) { |
645: | throw NoObjectProvided::create(); |
646: | } |
647: | |
648: | if (! is_object($object)) { |
649: | throw NotAnObject::fromNonObject($object); |
650: | } |
651: | |
652: | $implementingClassName = $this->getImplementingClass()->getName(); |
653: | |
654: | if (get_class($object) !== $implementingClassName) { |
655: | throw ObjectNotInstanceOfClass::fromClassName($implementingClassName); |
656: | } |
657: | |
658: | return $object; |
659: | } |
660: | |
661: | |
662: | private function computeModifiers(PropertyNode $node): int |
663: | { |
664: | $modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY : 0; |
665: | $modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0; |
666: | $modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0; |
667: | $modifiers += ! $node->isPrivate() && $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0; |
668: | $modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0; |
669: | $modifiers += ! $node->isProtected() && $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0; |
670: | $modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0; |
671: | $modifiers += $node->isFinal() ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0; |
672: | $modifiers += $node->isAbstract() ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0; |
673: | |
674: | if ( |
675: | ! ($modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY) |
676: | && ($modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY) |
677: | ) { |
678: | $modifiers += ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY; |
679: | } |
680: | |
681: | if ( |
682: | ! ($modifiers & (ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY)) |
683: | && ! $node->isPublicSet() |
684: | && $node->isPublic() |
685: | && ($modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY) |
686: | ) { |
687: | $modifiers += ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY; |
688: | } |
689: | |
690: | |
691: | return $modifiers; |
692: | } |
693: | |
694: | private function computeImmediateVirtual(PropertyNode $node): bool |
695: | { |
696: | if ($node->hooks === []) { |
697: | return false; |
698: | } |
699: | |
700: | $setHook = null; |
701: | $getHook = null; |
702: | |
703: | foreach ($node->hooks as $hook) { |
704: | if ($hook->name->name === 'set') { |
705: | $setHook = $hook; |
706: | } elseif ($hook->name->name === 'get') { |
707: | $getHook = $hook; |
708: | } |
709: | } |
710: | |
711: | if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnHook($setHook)) { |
712: | return false; |
713: | } |
714: | |
715: | if ($getHook === null) { |
716: | return true; |
717: | } |
718: | |
719: | return $this->computeImmediateVirtualBasedOnHook($getHook); |
720: | } |
721: | |
722: | private function computeImmediateVirtualBasedOnHook(Node\PropertyHook $hook): bool |
723: | { |
724: | $hookBody = $hook->getStmts(); |
725: | |
726: | |
727: | if ($hookBody === null) { |
728: | return true; |
729: | } |
730: | |
731: | $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch); |
732: | $traverser = new NodeTraverser($visitor); |
733: | $traverser->traverse($hookBody); |
734: | |
735: | foreach ($visitor->getFoundNodes() as $propertyFetchNode) { |
736: | assert($propertyFetchNode instanceof Node\Expr\PropertyFetch); |
737: | |
738: | if ( |
739: | $propertyFetchNode->var instanceof Node\Expr\Variable |
740: | && $propertyFetchNode->var->name === 'this' |
741: | && $propertyFetchNode->name instanceof Node\Identifier |
742: | && $propertyFetchNode->name->name === $this->name |
743: | ) { |
744: | return false; |
745: | } |
746: | } |
747: | |
748: | return true; |
749: | } |
750: | |
751: | |
752: | private function createImmediateHooks(PropertyNode $node): array |
753: | { |
754: | $hooks = []; |
755: | |
756: | foreach ($node->hooks as $hook) { |
757: | $hookName = $hook->name->name; |
758: | assert($hookName === 'get' || $hookName === 'set'); |
759: | |
760: | $hookType = $node->type; |
761: | assert($hookType === null || $hookType instanceof Node\Identifier || $hookType instanceof Node\Name || $hookType instanceof Node\NullableType || $hookType instanceof Node\UnionType || $hookType instanceof Node\IntersectionType); |
762: | |
763: | $hooks[$hookName] = ReflectionMethod::createFromPropertyHook( |
764: | $this->reflector, |
765: | $hook, |
766: | $this->getDeclaringClass()->getLocatedSource(), |
767: | sprintf('$%s::%s', $this->name, $hookName), |
768: | $hookType, |
769: | $this->getDeclaringClass(), |
770: | $this->getImplementingClass(), |
771: | $this->getDeclaringClass(), |
772: | $this, |
773: | ); |
774: | } |
775: | |
776: | return $hooks; |
777: | } |
778: | |
779: | private function createCachedVirtual(): bool |
780: | { |
781: | if (! $this->immediateVirtual) { |
782: | return false; |
783: | } |
784: | |
785: | return (($nullsafeVariable1 = $this->getParentProperty()) ? $nullsafeVariable1->isVirtual() : null) ?? true; |
786: | } |
787: | |
788: | |
789: | private function createCachedHooks(): array |
790: | { |
791: | $hooks = $this->immediateHooks; |
792: | |
793: | |
794: | if (isset($hooks['get'], $hooks['set'])) { |
795: | return $hooks; |
796: | } |
797: | |
798: | $parentHooks = (($nullsafeVariable2 = $this->getParentProperty()) ? $nullsafeVariable2->getHooks() : null) ?? []; |
799: | |
800: | foreach ($parentHooks as $hookName => $parentHook) { |
801: | if (isset($hooks[$hookName])) { |
802: | continue; |
803: | } |
804: | |
805: | $hooks[$hookName] = $parentHook; |
806: | } |
807: | |
808: | return $hooks; |
809: | } |
810: | |
811: | private function getParentProperty(): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty |
812: | { |
813: | return ($nullsafeVariable3 = $this->getDeclaringClass()->getParentClass()) ? $nullsafeVariable3->getProperty($this->name) : null; |
814: | } |
815: | } |
816: | |