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: /**
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:
103: if ($this->reflectionClass->hasProperty($propertyName)) {
104: continue;
105: }
106:
107: $propertyNode = $this->createPropertyNodeFromRuntimePropertyReflection($property, $this->object);
108:
109: $runtimeProperties[$propertyName] = ReflectionProperty::createFromNode($this->reflector, $propertyNode, $propertyNode->props[0], $this, $this, false, false);
110: }
111:
112: return $runtimeProperties;
113: }
114:
115: /**
116: * Create an AST PropertyNode given a reflection
117: *
118: * Note that we don't copy across DocBlock, protected, private or static
119: * because runtime properties can't have these attributes.
120: */
121: private function createPropertyNodeFromRuntimePropertyReflection(CoreReflectionProperty $property, object $instance): PropertyNode
122: {
123: $builder = new PropertyNodeBuilder($property->getName());
124: $builder->setDefault($property->getValue($instance));
125: $builder->makePublic();
126:
127: return $builder->getNode();
128: }
129:
130: public function getShortName(): string
131: {
132: return $this->reflectionClass->getShortName();
133: }
134:
135: public function getName(): string
136: {
137: return $this->reflectionClass->getName();
138: }
139:
140: public function getNamespaceName(): ?string
141: {
142: return $this->reflectionClass->getNamespaceName();
143: }
144:
145: public function inNamespace(): bool
146: {
147: return $this->reflectionClass->inNamespace();
148: }
149:
150: /** @return non-empty-string|null */
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: /** @return non-empty-string|null */
262: public function getFileName(): ?string
263: {
264: return $this->reflectionClass->getFileName();
265: }
266:
267: public function getLocatedSource(): LocatedSource
268: {
269: return $this->reflectionClass->getLocatedSource();
270: }
271:
272: public function getStartLine(): int
273: {
274: return $this->reflectionClass->getStartLine();
275: }
276:
277: public function getEndLine(): int
278: {
279: return $this->reflectionClass->getEndLine();
280: }
281:
282: public function getStartColumn(): int
283: {
284: return $this->reflectionClass->getStartColumn();
285: }
286:
287: public function getEndColumn(): int
288: {
289: return $this->reflectionClass->getEndColumn();
290: }
291:
292: public function getParentClass(): ?\PHPStan\BetterReflection\Reflection\ReflectionClass
293: {
294: return $this->reflectionClass->getParentClass();
295: }
296:
297: /** @return class-string|null */
298: public function getParentClassName(): ?string
299: {
300: return $this->reflectionClass->getParentClassName();
301: }
302:
303: /**
304: * {@inheritDoc}
305: */
306: public function getParentClassNames(): array
307: {
308: return $this->reflectionClass->getParentClassNames();
309: }
310:
311: /** @return non-empty-string|null */
312: public function getDocComment(): ?string
313: {
314: return $this->reflectionClass->getDocComment();
315: }
316:
317: public function isAnonymous(): bool
318: {
319: return $this->reflectionClass->isAnonymous();
320: }
321:
322: public function isInternal(): bool
323: {
324: return $this->reflectionClass->isInternal();
325: }
326:
327: public function isUserDefined(): bool
328: {
329: return $this->reflectionClass->isUserDefined();
330: }
331:
332: public function isDeprecated(): bool
333: {
334: return $this->reflectionClass->isDeprecated();
335: }
336:
337: public function isAbstract(): bool
338: {
339: return $this->reflectionClass->isAbstract();
340: }
341:
342: public function isFinal(): bool
343: {
344: return $this->reflectionClass->isFinal();
345: }
346:
347: public function isReadOnly(): bool
348: {
349: return $this->reflectionClass->isReadOnly();
350: }
351:
352: public function getModifiers(): int
353: {
354: return $this->reflectionClass->getModifiers();
355: }
356:
357: public function isTrait(): bool
358: {
359: return $this->reflectionClass->isTrait();
360: }
361:
362: public function isInterface(): bool
363: {
364: return $this->reflectionClass->isInterface();
365: }
366:
367: /**
368: * {@inheritDoc}
369: */
370: public function getTraitClassNames(): array
371: {
372: return $this->reflectionClass->getTraitClassNames();
373: }
374:
375: /**
376: * {@inheritDoc}
377: */
378: public function getTraits(): array
379: {
380: return $this->reflectionClass->getTraits();
381: }
382:
383: /**
384: * {@inheritDoc}
385: */
386: public function getTraitNames(): array
387: {
388: return $this->reflectionClass->getTraitNames();
389: }
390:
391: /**
392: * {@inheritDoc}
393: */
394: public function getTraitAliases(): array
395: {
396: return $this->reflectionClass->getTraitAliases();
397: }
398:
399: /**
400: * {@inheritDoc}
401: */
402: public function getInterfaceClassNames(): array
403: {
404: return $this->reflectionClass->getInterfaceClassNames();
405: }
406:
407: /**
408: * {@inheritDoc}
409: */
410: public function getInterfaces(): array
411: {
412: return $this->reflectionClass->getInterfaces();
413: }
414:
415: /**
416: * {@inheritDoc}
417: */
418: public function getImmediateInterfaces(): array
419: {
420: return $this->reflectionClass->getImmediateInterfaces();
421: }
422:
423: /**
424: * {@inheritDoc}
425: */
426: public function getInterfaceNames(): array
427: {
428: return $this->reflectionClass->getInterfaceNames();
429: }
430:
431: public function isInstance(object $object): bool
432: {
433: return $this->reflectionClass->isInstance($object);
434: }
435:
436: public function isSubclassOf(string $className): bool
437: {
438: return $this->reflectionClass->isSubclassOf($className);
439: }
440:
441: public function implementsInterface(string $interfaceName): bool
442: {
443: return $this->reflectionClass->implementsInterface($interfaceName);
444: }
445:
446: public function isInstantiable(): bool
447: {
448: return $this->reflectionClass->isInstantiable();
449: }
450:
451: public function isCloneable(): bool
452: {
453: return $this->reflectionClass->isCloneable();
454: }
455:
456: public function isIterateable(): bool
457: {
458: return $this->reflectionClass->isIterateable();
459: }
460:
461: public function isEnum(): bool
462: {
463: return $this->reflectionClass->isEnum();
464: }
465:
466: /**
467: * {@inheritDoc}
468: */
469: public function getStaticProperties(): array
470: {
471: return $this->reflectionClass->getStaticProperties();
472: }
473:
474: /**
475: * @param mixed $value
476: */
477: public function setStaticPropertyValue(string $propertyName, $value): void
478: {
479: $this->reflectionClass->setStaticPropertyValue($propertyName, $value);
480: }
481:
482: /**
483: * @return mixed
484: */
485: public function getStaticPropertyValue(string $propertyName)
486: {
487: return $this->reflectionClass->getStaticPropertyValue($propertyName);
488: }
489:
490: /** @return list<ReflectionAttribute> */
491: public function getAttributes(): array
492: {
493: return $this->reflectionClass->getAttributes();
494: }
495:
496: /** @return list<ReflectionAttribute> */
497: public function getAttributesByName(string $name): array
498: {
499: return $this->reflectionClass->getAttributesByName($name);
500: }
501:
502: /**
503: * @param class-string $className
504: *
505: * @return list<ReflectionAttribute>
506: */
507: public function getAttributesByInstance(string $className): array
508: {
509: return $this->reflectionClass->getAttributesByInstance($className);
510: }
511: }
512: