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(
104: $this->reflector,
105: $propertyNode,
106: $propertyNode->props[0],
107: $this,
108: $this,
109: false,
110: false,
111: );
112: }
113:
114: return $runtimeProperties;
115: }
116:
117: /**
118: * Create an AST PropertyNode given a reflection
119: *
120: * Note that we don't copy across DocBlock, protected, private or static
121: * because runtime properties can't have these attributes.
122: */
123: private function createPropertyNodeFromRuntimePropertyReflection(CoreReflectionProperty $property, object $instance): PropertyNode
124: {
125: $builder = new PropertyNodeBuilder($property->getName());
126: $builder->setDefault($property->getValue($instance));
127: $builder->makePublic();
128:
129: return $builder->getNode();
130: }
131:
132: public function getShortName(): string
133: {
134: return $this->reflectionClass->getShortName();
135: }
136:
137: public function getName(): string
138: {
139: return $this->reflectionClass->getName();
140: }
141:
142: public function getNamespaceName(): ?string
143: {
144: return $this->reflectionClass->getNamespaceName();
145: }
146:
147: public function inNamespace(): bool
148: {
149: return $this->reflectionClass->inNamespace();
150: }
151:
152: /** @return non-empty-string|null */
153: public function getExtensionName(): ?string
154: {
155: return $this->reflectionClass->getExtensionName();
156: }
157:
158: /**
159: * {@inheritDoc}
160: */
161: public function getMethods(int $filter = 0): array
162: {
163: return $this->reflectionClass->getMethods($filter);
164: }
165:
166: /**
167: * {@inheritDoc}
168: */
169: public function getImmediateMethods(int $filter = 0): array
170: {
171: return $this->reflectionClass->getImmediateMethods($filter);
172: }
173:
174: /** @param non-empty-string $methodName */
175: public function getMethod(string $methodName): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
176: {
177: return $this->reflectionClass->getMethod($methodName);
178: }
179:
180: /** @param non-empty-string $methodName */
181: public function hasMethod(string $methodName): bool
182: {
183: return $this->reflectionClass->hasMethod($methodName);
184: }
185:
186: /**
187: * {@inheritDoc}
188: */
189: public function getImmediateConstants(int $filter = 0): array
190: {
191: return $this->reflectionClass->getImmediateConstants($filter);
192: }
193:
194: /**
195: * {@inheritDoc}
196: */
197: public function getConstants(int $filter = 0): array
198: {
199: return $this->reflectionClass->getConstants($filter);
200: }
201:
202: public function hasConstant(string $name): bool
203: {
204: return $this->reflectionClass->hasConstant($name);
205: }
206:
207: public function getConstant(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionClassConstant
208: {
209: return $this->reflectionClass->getConstant($name);
210: }
211:
212: public function getConstructor(): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
213: {
214: return $this->reflectionClass->getConstructor();
215: }
216:
217: /**
218: * {@inheritDoc}
219: */
220: public function getProperties(int $filter = 0): array
221: {
222: return array_merge(
223: $this->reflectionClass->getProperties($filter),
224: $this->getRuntimeProperties($filter),
225: );
226: }
227:
228: /**
229: * {@inheritDoc}
230: */
231: public function getImmediateProperties(int $filter = 0): array
232: {
233: return array_merge(
234: $this->reflectionClass->getImmediateProperties($filter),
235: $this->getRuntimeProperties($filter),
236: );
237: }
238:
239: public function getProperty(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty
240: {
241: $runtimeProperties = $this->getRuntimeProperties();
242:
243: if (isset($runtimeProperties[$name])) {
244: return $runtimeProperties[$name];
245: }
246:
247: return $this->reflectionClass->getProperty($name);
248: }
249:
250: public function hasProperty(string $name): bool
251: {
252: $runtimeProperties = $this->getRuntimeProperties();
253:
254: return isset($runtimeProperties[$name]) || $this->reflectionClass->hasProperty($name);
255: }
256:
257: /**
258: * {@inheritDoc}
259: */
260: public function getDefaultProperties(): array
261: {
262: return array_map(
263: static fn (ReflectionProperty $property) => $property->getDefaultValue(),
264: array_filter($this->getProperties(), static fn (ReflectionProperty $property): bool => $property->isDefault()),
265: );
266: }
267:
268: /** @return non-empty-string|null */
269: public function getFileName(): ?string
270: {
271: return $this->reflectionClass->getFileName();
272: }
273:
274: public function getLocatedSource(): LocatedSource
275: {
276: return $this->reflectionClass->getLocatedSource();
277: }
278:
279: public function getStartLine(): int
280: {
281: return $this->reflectionClass->getStartLine();
282: }
283:
284: public function getEndLine(): int
285: {
286: return $this->reflectionClass->getEndLine();
287: }
288:
289: public function getStartColumn(): int
290: {
291: return $this->reflectionClass->getStartColumn();
292: }
293:
294: public function getEndColumn(): int
295: {
296: return $this->reflectionClass->getEndColumn();
297: }
298:
299: public function getParentClass(): ?\PHPStan\BetterReflection\Reflection\ReflectionClass
300: {
301: return $this->reflectionClass->getParentClass();
302: }
303:
304: /** @return class-string|null */
305: public function getParentClassName(): ?string
306: {
307: return $this->reflectionClass->getParentClassName();
308: }
309:
310: /**
311: * {@inheritDoc}
312: */
313: public function getParentClassNames(): array
314: {
315: return $this->reflectionClass->getParentClassNames();
316: }
317:
318: /** @return non-empty-string|null */
319: public function getDocComment(): ?string
320: {
321: return $this->reflectionClass->getDocComment();
322: }
323:
324: public function isAnonymous(): bool
325: {
326: return $this->reflectionClass->isAnonymous();
327: }
328:
329: public function isInternal(): bool
330: {
331: return $this->reflectionClass->isInternal();
332: }
333:
334: public function isUserDefined(): bool
335: {
336: return $this->reflectionClass->isUserDefined();
337: }
338:
339: public function isDeprecated(): bool
340: {
341: return $this->reflectionClass->isDeprecated();
342: }
343:
344: public function isAbstract(): bool
345: {
346: return $this->reflectionClass->isAbstract();
347: }
348:
349: public function isFinal(): bool
350: {
351: return $this->reflectionClass->isFinal();
352: }
353:
354: public function isReadOnly(): bool
355: {
356: return $this->reflectionClass->isReadOnly();
357: }
358:
359: public function getModifiers(): int
360: {
361: return $this->reflectionClass->getModifiers();
362: }
363:
364: public function isTrait(): bool
365: {
366: return $this->reflectionClass->isTrait();
367: }
368:
369: public function isInterface(): bool
370: {
371: return $this->reflectionClass->isInterface();
372: }
373:
374: /**
375: * {@inheritDoc}
376: */
377: public function getTraitClassNames(): array
378: {
379: return $this->reflectionClass->getTraitClassNames();
380: }
381:
382: /**
383: * {@inheritDoc}
384: */
385: public function getTraits(): array
386: {
387: return $this->reflectionClass->getTraits();
388: }
389:
390: /**
391: * {@inheritDoc}
392: */
393: public function getTraitNames(): array
394: {
395: return $this->reflectionClass->getTraitNames();
396: }
397:
398: /**
399: * {@inheritDoc}
400: */
401: public function getTraitAliases(): array
402: {
403: return $this->reflectionClass->getTraitAliases();
404: }
405:
406: /**
407: * {@inheritDoc}
408: */
409: public function getInterfaceClassNames(): array
410: {
411: return $this->reflectionClass->getInterfaceClassNames();
412: }
413:
414: /**
415: * {@inheritDoc}
416: */
417: public function getInterfaces(): array
418: {
419: return $this->reflectionClass->getInterfaces();
420: }
421:
422: /**
423: * {@inheritDoc}
424: */
425: public function getImmediateInterfaces(): array
426: {
427: return $this->reflectionClass->getImmediateInterfaces();
428: }
429:
430: /**
431: * {@inheritDoc}
432: */
433: public function getInterfaceNames(): array
434: {
435: return $this->reflectionClass->getInterfaceNames();
436: }
437:
438: public function isInstance(object $object): bool
439: {
440: return $this->reflectionClass->isInstance($object);
441: }
442:
443: public function isSubclassOf(string $className): bool
444: {
445: return $this->reflectionClass->isSubclassOf($className);
446: }
447:
448: public function implementsInterface(string $interfaceName): bool
449: {
450: return $this->reflectionClass->implementsInterface($interfaceName);
451: }
452:
453: public function isInstantiable(): bool
454: {
455: return $this->reflectionClass->isInstantiable();
456: }
457:
458: public function isCloneable(): bool
459: {
460: return $this->reflectionClass->isCloneable();
461: }
462:
463: public function isIterateable(): bool
464: {
465: return $this->reflectionClass->isIterateable();
466: }
467:
468: public function isEnum(): bool
469: {
470: return $this->reflectionClass->isEnum();
471: }
472:
473: /**
474: * {@inheritDoc}
475: */
476: public function getStaticProperties(): array
477: {
478: return $this->reflectionClass->getStaticProperties();
479: }
480:
481: /**
482: * @param mixed $value
483: */
484: public function setStaticPropertyValue(string $propertyName, $value): void
485: {
486: $this->reflectionClass->setStaticPropertyValue($propertyName, $value);
487: }
488:
489: /**
490: * @return mixed
491: */
492: public function getStaticPropertyValue(string $propertyName)
493: {
494: return $this->reflectionClass->getStaticPropertyValue($propertyName);
495: }
496:
497: /** @return list<ReflectionAttribute> */
498: public function getAttributes(): array
499: {
500: return $this->reflectionClass->getAttributes();
501: }
502:
503: /** @return list<ReflectionAttribute> */
504: public function getAttributesByName(string $name): array
505: {
506: return $this->reflectionClass->getAttributesByName($name);
507: }
508:
509: /**
510: * @param class-string $className
511: *
512: * @return list<ReflectionAttribute>
513: */
514: public function getAttributesByInstance(string $className): array
515: {
516: return $this->reflectionClass->getAttributesByInstance($className);
517: }
518: }
519: