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