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: /**
298: * {@inheritdoc}
299: */
300: public function getParentClassNames(): array
301: {
302: return $this->reflectionClass->getParentClassNames();
303: }
304:
305: /** @return non-empty-string|null */
306: public function getDocComment(): ?string
307: {
308: return $this->reflectionClass->getDocComment();
309: }
310:
311: public function isAnonymous(): bool
312: {
313: return $this->reflectionClass->isAnonymous();
314: }
315:
316: public function isInternal(): bool
317: {
318: return $this->reflectionClass->isInternal();
319: }
320:
321: public function isUserDefined(): bool
322: {
323: return $this->reflectionClass->isUserDefined();
324: }
325:
326: public function isDeprecated(): bool
327: {
328: return $this->reflectionClass->isDeprecated();
329: }
330:
331: public function isAbstract(): bool
332: {
333: return $this->reflectionClass->isAbstract();
334: }
335:
336: public function isFinal(): bool
337: {
338: return $this->reflectionClass->isFinal();
339: }
340:
341: public function isReadOnly(): bool
342: {
343: return $this->reflectionClass->isReadOnly();
344: }
345:
346: public function getModifiers(): int
347: {
348: return $this->reflectionClass->getModifiers();
349: }
350:
351: public function isTrait(): bool
352: {
353: return $this->reflectionClass->isTrait();
354: }
355:
356: public function isInterface(): bool
357: {
358: return $this->reflectionClass->isInterface();
359: }
360:
361: /**
362: * {@inheritdoc}
363: */
364: public function getTraits(): array
365: {
366: return $this->reflectionClass->getTraits();
367: }
368:
369: /**
370: * {@inheritdoc}
371: */
372: public function getTraitNames(): array
373: {
374: return $this->reflectionClass->getTraitNames();
375: }
376:
377: /**
378: * {@inheritdoc}
379: */
380: public function getTraitAliases(): array
381: {
382: return $this->reflectionClass->getTraitAliases();
383: }
384:
385: /**
386: * {@inheritdoc}
387: */
388: public function getInterfaces(): array
389: {
390: return $this->reflectionClass->getInterfaces();
391: }
392:
393: /**
394: * {@inheritdoc}
395: */
396: public function getImmediateInterfaces(): array
397: {
398: return $this->reflectionClass->getImmediateInterfaces();
399: }
400:
401: /**
402: * {@inheritdoc}
403: */
404: public function getInterfaceNames(): array
405: {
406: return $this->reflectionClass->getInterfaceNames();
407: }
408:
409: public function isInstance(object $object): bool
410: {
411: return $this->reflectionClass->isInstance($object);
412: }
413:
414: public function isSubclassOf(string $className): bool
415: {
416: return $this->reflectionClass->isSubclassOf($className);
417: }
418:
419: public function implementsInterface(string $interfaceName): bool
420: {
421: return $this->reflectionClass->implementsInterface($interfaceName);
422: }
423:
424: public function isInstantiable(): bool
425: {
426: return $this->reflectionClass->isInstantiable();
427: }
428:
429: public function isCloneable(): bool
430: {
431: return $this->reflectionClass->isCloneable();
432: }
433:
434: public function isIterateable(): bool
435: {
436: return $this->reflectionClass->isIterateable();
437: }
438:
439: public function isEnum(): bool
440: {
441: return $this->reflectionClass->isEnum();
442: }
443:
444: /**
445: * {@inheritdoc}
446: */
447: public function getStaticProperties(): array
448: {
449: return $this->reflectionClass->getStaticProperties();
450: }
451:
452: /**
453: * @param mixed $value
454: */
455: public function setStaticPropertyValue(string $propertyName, $value): void
456: {
457: $this->reflectionClass->setStaticPropertyValue($propertyName, $value);
458: }
459:
460: /**
461: * @return mixed
462: */
463: public function getStaticPropertyValue(string $propertyName)
464: {
465: return $this->reflectionClass->getStaticPropertyValue($propertyName);
466: }
467:
468: /** @return list<ReflectionAttribute> */
469: public function getAttributes(): array
470: {
471: return $this->reflectionClass->getAttributes();
472: }
473:
474: /** @return list<ReflectionAttribute> */
475: public function getAttributesByName(string $name): array
476: {
477: return $this->reflectionClass->getAttributesByName($name);
478: }
479:
480: /**
481: * @param class-string $className
482: *
483: * @return list<ReflectionAttribute>
484: */
485: public function getAttributesByInstance(string $className): array
486: {
487: return $this->reflectionClass->getAttributesByInstance($className);
488: }
489: }
490: