1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use InvalidArgumentException;
8: use PhpParser\Builder\Property as PropertyNodeBuilder;
9: use PhpParser\Node\Stmt\Property as PropertyNode;
10: use ReflectionException;
11: use ReflectionObject as CoreReflectionObject;
12: use ReflectionProperty as CoreReflectionProperty;
13: use PHPStan\BetterReflection\BetterReflection;
14: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter;
15: use PHPStan\BetterReflection\Reflector\DefaultReflector;
16: use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
17: use PHPStan\BetterReflection\Reflector\Reflector;
18: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
19: use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
20: use PHPStan\BetterReflection\SourceLocator\Type\AnonymousClassObjectSourceLocator;
21:
22: use function array_filter;
23: use function array_map;
24: use function array_merge;
25: use function preg_match;
26:
27: /** @psalm-immutable */
28: class ReflectionObject extends ReflectionClass
29: {
30: private Reflector $reflector;
31: private ReflectionClass $reflectionClass;
32: private object $object;
33: protected function __construct(Reflector $reflector, ReflectionClass $reflectionClass, object $object)
34: {
35: $this->reflector = $reflector;
36: $this->reflectionClass = $reflectionClass;
37: $this->object = $object;
38: }
39:
40: /**
41: * Pass an instance of an object to this method to reflect it
42: *
43: * @throws ReflectionException
44: * @throws IdentifierNotFound
45: */
46: public static function createFromInstance(object $instance): ReflectionClass
47: {
48: $className = get_class($instance);
49:
50: $betterReflection = new BetterReflection();
51:
52: if (preg_match(ReflectionClass::ANONYMOUS_CLASS_NAME_PREFIX_REGEXP, $className) === 1) {
53: $reflector = new DefaultReflector(new AggregateSourceLocator([
54: $betterReflection->sourceLocator(),
55: new AnonymousClassObjectSourceLocator(
56: $instance,
57: $betterReflection->phpParser(),
58: ),
59: ]));
60: } else {
61: $reflector = $betterReflection->reflector();
62: }
63:
64: return new self($reflector, $reflector->reflectClass($className), $instance);
65: }
66:
67: /**
68: * Reflect on runtime properties for the current instance
69: *
70: * @see ReflectionClass::getProperties() for the usage of $filter
71: *
72: * @param int-mask-of<ReflectionPropertyAdapter::IS_*> $filter
73: *
74: * @return array<non-empty-string, ReflectionProperty>
75: */
76: private function getRuntimeProperties(int $filter = 0): array
77: {
78: if (! $this->reflectionClass->isInstance($this->object)) {
79: throw new InvalidArgumentException('Cannot reflect runtime properties of a separate class');
80: }
81:
82: if ($filter !== 0 && ! ($filter & CoreReflectionProperty::IS_PUBLIC)) {
83: return [];
84: }
85:
86: // Ensure we have already cached existing properties so we can add to them
87: $this->reflectionClass->getProperties();
88:
89: // Only known current way is to use internal ReflectionObject to get
90: // the runtime-declared properties :/
91: $reflectionProperties = (new CoreReflectionObject($this->object))->getProperties();
92: $runtimeProperties = [];
93:
94: foreach ($reflectionProperties as $property) {
95: $propertyName = $property->getName();
96:
97: if ($this->reflectionClass->hasProperty($propertyName)) {
98: continue;
99: }
100:
101: $propertyNode = $this->createPropertyNodeFromRuntimePropertyReflection($property, $this->object);
102:
103: $runtimeProperties[$propertyName] = ReflectionProperty::createFromNode($this->reflector, $propertyNode, $propertyNode->props[0], $this, $this, false, false);
104: }
105:
106: return $runtimeProperties;
107: }
108:
109: /**
110: * Create an AST PropertyNode given a reflection
111: *
112: * Note that we don't copy across DocBlock, protected, private or static
113: * because runtime properties can't have these attributes.
114: */
115: private function createPropertyNodeFromRuntimePropertyReflection(CoreReflectionProperty $property, object $instance): PropertyNode
116: {
117: $builder = new PropertyNodeBuilder($property->getName());
118: $builder->setDefault($property->getValue($instance));
119: $builder->makePublic();
120:
121: return $builder->getNode();
122: }
123:
124: public function getShortName(): string
125: {
126: return $this->reflectionClass->getShortName();
127: }
128:
129: public function getName(): string
130: {
131: return $this->reflectionClass->getName();
132: }
133:
134: public function getNamespaceName(): ?string
135: {
136: return $this->reflectionClass->getNamespaceName();
137: }
138:
139: public function inNamespace(): bool
140: {
141: return $this->reflectionClass->inNamespace();
142: }
143:
144: /** @return non-empty-string|null */
145: public function getExtensionName(): ?string
146: {
147: return $this->reflectionClass->getExtensionName();
148: }
149:
150: /**
151: * {@inheritDoc}
152: */
153: public function getMethods(int $filter = 0): array
154: {
155: return $this->reflectionClass->getMethods($filter);
156: }
157:
158: /**
159: * {@inheritDoc}
160: */
161: public function getImmediateMethods(int $filter = 0): array
162: {
163: return $this->reflectionClass->getImmediateMethods($filter);
164: }
165:
166: /** @param non-empty-string $methodName */
167: public function getMethod(string $methodName): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
168: {
169: return $this->reflectionClass->getMethod($methodName);
170: }
171:
172: /** @param non-empty-string $methodName */
173: public function hasMethod(string $methodName): bool
174: {
175: return $this->reflectionClass->hasMethod($methodName);
176: }
177:
178: /**
179: * {@inheritDoc}
180: */
181: public function getImmediateConstants(int $filter = 0): array
182: {
183: return $this->reflectionClass->getImmediateConstants($filter);
184: }
185:
186: /**
187: * {@inheritDoc}
188: */
189: public function getConstants(int $filter = 0): array
190: {
191: return $this->reflectionClass->getConstants($filter);
192: }
193:
194: public function hasConstant(string $name): bool
195: {
196: return $this->reflectionClass->hasConstant($name);
197: }
198:
199: public function getConstant(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionClassConstant
200: {
201: return $this->reflectionClass->getConstant($name);
202: }
203:
204: public function getConstructor(): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
205: {
206: return $this->reflectionClass->getConstructor();
207: }
208:
209: /**
210: * {@inheritDoc}
211: */
212: public function getProperties(int $filter = 0): array
213: {
214: return array_merge(
215: $this->reflectionClass->getProperties($filter),
216: $this->getRuntimeProperties($filter),
217: );
218: }
219:
220: /**
221: * {@inheritDoc}
222: */
223: public function getImmediateProperties(int $filter = 0): array
224: {
225: return array_merge(
226: $this->reflectionClass->getImmediateProperties($filter),
227: $this->getRuntimeProperties($filter),
228: );
229: }
230:
231: public function getProperty(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty
232: {
233: $runtimeProperties = $this->getRuntimeProperties();
234:
235: if (isset($runtimeProperties[$name])) {
236: return $runtimeProperties[$name];
237: }
238:
239: return $this->reflectionClass->getProperty($name);
240: }
241:
242: public function hasProperty(string $name): bool
243: {
244: $runtimeProperties = $this->getRuntimeProperties();
245:
246: return isset($runtimeProperties[$name]) || $this->reflectionClass->hasProperty($name);
247: }
248:
249: /**
250: * {@inheritDoc}
251: */
252: public function getDefaultProperties(): array
253: {
254: return array_map(
255: static fn (ReflectionProperty $property) => $property->getDefaultValue(),
256: array_filter($this->getProperties(), static fn (ReflectionProperty $property): bool => $property->isDefault()),
257: );
258: }
259:
260: /** @return non-empty-string|null */
261: public function getFileName(): ?string
262: {
263: return $this->reflectionClass->getFileName();
264: }
265:
266: public function getLocatedSource(): LocatedSource
267: {
268: return $this->reflectionClass->getLocatedSource();
269: }
270:
271: public function getStartLine(): int
272: {
273: return $this->reflectionClass->getStartLine();
274: }
275:
276: public function getEndLine(): int
277: {
278: return $this->reflectionClass->getEndLine();
279: }
280:
281: public function getStartColumn(): int
282: {
283: return $this->reflectionClass->getStartColumn();
284: }
285:
286: public function getEndColumn(): int
287: {
288: return $this->reflectionClass->getEndColumn();
289: }
290:
291: public function getParentClass(): ?\PHPStan\BetterReflection\Reflection\ReflectionClass
292: {
293: return $this->reflectionClass->getParentClass();
294: }
295:
296: /** @return class-string|null */
297: public function getParentClassName(): ?string
298: {
299: return $this->reflectionClass->getParentClassName();
300: }
301:
302: /**
303: * {@inheritDoc}
304: */
305: public function getParentClassNames(): array
306: {
307: return $this->reflectionClass->getParentClassNames();
308: }
309:
310: /** @return non-empty-string|null */
311: public function getDocComment(): ?string
312: {
313: return $this->reflectionClass->getDocComment();
314: }
315:
316: public function isAnonymous(): bool
317: {
318: return $this->reflectionClass->isAnonymous();
319: }
320:
321: public function isInternal(): bool
322: {
323: return $this->reflectionClass->isInternal();
324: }
325:
326: public function isUserDefined(): bool
327: {
328: return $this->reflectionClass->isUserDefined();
329: }
330:
331: public function isDeprecated(): bool
332: {
333: return $this->reflectionClass->isDeprecated();
334: }
335:
336: public function isAbstract(): bool
337: {
338: return $this->reflectionClass->isAbstract();
339: }
340:
341: public function isFinal(): bool
342: {
343: return $this->reflectionClass->isFinal();
344: }
345:
346: public function isReadOnly(): bool
347: {
348: return $this->reflectionClass->isReadOnly();
349: }
350:
351: public function getModifiers(): int
352: {
353: return $this->reflectionClass->getModifiers();
354: }
355:
356: public function isTrait(): bool
357: {
358: return $this->reflectionClass->isTrait();
359: }
360:
361: public function isInterface(): bool
362: {
363: return $this->reflectionClass->isInterface();
364: }
365:
366: /**
367: * {@inheritDoc}
368: */
369: public function getTraitClassNames(): array
370: {
371: return $this->reflectionClass->getTraitClassNames();
372: }
373:
374: /**
375: * {@inheritDoc}
376: */
377: public function getTraits(): array
378: {
379: return $this->reflectionClass->getTraits();
380: }
381:
382: /**
383: * {@inheritDoc}
384: */
385: public function getTraitNames(): array
386: {
387: return $this->reflectionClass->getTraitNames();
388: }
389:
390: /**
391: * {@inheritDoc}
392: */
393: public function getTraitAliases(): array
394: {
395: return $this->reflectionClass->getTraitAliases();
396: }
397:
398: /**
399: * {@inheritDoc}
400: */
401: public function getInterfaceClassNames(): array
402: {
403: return $this->reflectionClass->getInterfaceClassNames();
404: }
405:
406: /**
407: * {@inheritDoc}
408: */
409: public function getInterfaces(): array
410: {
411: return $this->reflectionClass->getInterfaces();
412: }
413:
414: /**
415: * {@inheritDoc}
416: */
417: public function getImmediateInterfaces(): array
418: {
419: return $this->reflectionClass->getImmediateInterfaces();
420: }
421:
422: /**
423: * {@inheritDoc}
424: */
425: public function getInterfaceNames(): array
426: {
427: return $this->reflectionClass->getInterfaceNames();
428: }
429:
430: public function isInstance(object $object): bool
431: {
432: return $this->reflectionClass->isInstance($object);
433: }
434:
435: public function isSubclassOf(string $className): bool
436: {
437: return $this->reflectionClass->isSubclassOf($className);
438: }
439:
440: public function implementsInterface(string $interfaceName): bool
441: {
442: return $this->reflectionClass->implementsInterface($interfaceName);
443: }
444:
445: public function isInstantiable(): bool
446: {
447: return $this->reflectionClass->isInstantiable();
448: }
449:
450: public function isCloneable(): bool
451: {
452: return $this->reflectionClass->isCloneable();
453: }
454:
455: public function isIterateable(): bool
456: {
457: return $this->reflectionClass->isIterateable();
458: }
459:
460: public function isEnum(): bool
461: {
462: return $this->reflectionClass->isEnum();
463: }
464:
465: /**
466: * {@inheritDoc}
467: */
468: public function getStaticProperties(): array
469: {
470: return $this->reflectionClass->getStaticProperties();
471: }
472:
473: /**
474: * @param mixed $value
475: */
476: public function setStaticPropertyValue(string $propertyName, $value): void
477: {
478: $this->reflectionClass->setStaticPropertyValue($propertyName, $value);
479: }
480:
481: /**
482: * @return mixed
483: */
484: public function getStaticPropertyValue(string $propertyName)
485: {
486: return $this->reflectionClass->getStaticPropertyValue($propertyName);
487: }
488:
489: /** @return list<ReflectionAttribute> */
490: public function getAttributes(): array
491: {
492: return $this->reflectionClass->getAttributes();
493: }
494:
495: /** @return list<ReflectionAttribute> */
496: public function getAttributesByName(string $name): array
497: {
498: return $this->reflectionClass->getAttributesByName($name);
499: }
500:
501: /**
502: * @param class-string $className
503: *
504: * @return list<ReflectionAttribute>
505: */
506: public function getAttributesByInstance(string $className): array
507: {
508: return $this->reflectionClass->getAttributesByInstance($className);
509: }
510: }
511: