%PDF- %PDF-
Direktori : /home/graphicd/www/vebto/vendor/roave/better-reflection/src/Reflection/ |
Current File : /home/graphicd/www/vebto/vendor/roave/better-reflection/src/Reflection/ReflectionClass.php |
<?php declare(strict_types=1); namespace Roave\BetterReflection\Reflection; use InvalidArgumentException; use OutOfBoundsException; use PhpParser\Node; use PhpParser\Node\Stmt\Class_ as ClassNode; use PhpParser\Node\Stmt\ClassConst as ConstNode; use PhpParser\Node\Stmt\ClassLike as ClassLikeNode; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Interface_ as InterfaceNode; use PhpParser\Node\Stmt\Namespace_ as NamespaceNode; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Property as PropertyNode; use PhpParser\Node\Stmt\Trait_ as TraitNode; use PhpParser\Node\Stmt\TraitUse; use ReflectionClass as CoreReflectionClass; use ReflectionException; use ReflectionProperty as CoreReflectionProperty; use Roave\BetterReflection\BetterReflection; use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\NotAClassReflection; use Roave\BetterReflection\Reflection\Exception\NotAnInterfaceReflection; use Roave\BetterReflection\Reflection\Exception\NotAnObject; use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass; use Roave\BetterReflection\Reflection\Exception\PropertyDoesNotExist; use Roave\BetterReflection\Reflection\Exception\Uncloneable; use Roave\BetterReflection\Reflection\StringCast\ReflectionClassStringCast; use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound; use Roave\BetterReflection\Reflector\Reflector; use Roave\BetterReflection\SourceLocator\Located\LocatedSource; use Roave\BetterReflection\Util\CalculateReflectionColum; use Roave\BetterReflection\Util\GetFirstDocComment; use Traversable; use function array_combine; use function array_filter; use function array_key_exists; use function array_map; use function array_merge; use function array_reverse; use function array_slice; use function array_values; use function assert; use function implode; use function in_array; use function is_object; use function ltrim; use function sha1; use function sprintf; use function strtolower; class ReflectionClass implements Reflection { public const ANONYMOUS_CLASS_NAME_PREFIX = 'class@anonymous'; /** @var Reflector */ private $reflector; /** @var NamespaceNode|null */ private $declaringNamespace; /** @var LocatedSource */ private $locatedSource; /** @var ClassLikeNode */ private $node; /** @var array<string, ReflectionClassConstant>|null indexed by name, when present */ private $cachedReflectionConstants; /** @var array<string, ReflectionProperty>|null */ private $cachedImmediateProperties; /** @var array<string, ReflectionProperty>|null */ private $cachedProperties; /** @var array<lowercase-string, ReflectionMethod>|null */ private $cachedMethods; /** @var array<string, string>|null */ private $cachedTraitAliases; /** @var array<string, string>|null */ private $cachedTraitPrecedences; private function __construct() { } public function __toString() : string { return ReflectionClassStringCast::toString($this); } /** * Create a ReflectionClass by name, using default reflectors etc. * * @throws IdentifierNotFound */ public static function createFromName(string $className) : self { return (new BetterReflection())->classReflector()->reflect($className); } /** * Create a ReflectionClass from an instance, using default reflectors etc. * * This is simply a helper method that calls ReflectionObject::createFromInstance(). * * @see ReflectionObject::createFromInstance * * @param object $instance * * @throws IdentifierNotFound * @throws ReflectionException * @throws InvalidArgumentException * * @psalm-suppress DocblockTypeContradiction */ public static function createFromInstance($instance) : self { if (! is_object($instance)) { throw new InvalidArgumentException('Instance must be an instance of an object'); } return ReflectionObject::createFromInstance($instance); } /** * Create from a Class Node. * * @internal * * @param ClassLikeNode $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver * @param NamespaceNode|null $namespace optional - if omitted, we assume it is global namespaced class */ public static function createFromNode( Reflector $reflector, ClassLikeNode $node, LocatedSource $locatedSource, ?NamespaceNode $namespace = null ) : self { $class = new self(); $class->reflector = $reflector; $class->locatedSource = $locatedSource; $class->node = $node; if ($namespace !== null) { $class->declaringNamespace = $namespace; } return $class; } /** * Get the "short" name of the class (e.g. for A\B\Foo, this will return * "Foo"). */ public function getShortName() : string { if (! $this->isAnonymous()) { assert($this->node->name instanceof Node\Identifier); return $this->node->name->name; } $fileName = $this->getFileName(); if ($fileName === null) { $fileName = sha1($this->locatedSource->getSource()); } return sprintf('%s%c%s(%d)', self::ANONYMOUS_CLASS_NAME_PREFIX, "\0", $fileName, $this->getStartLine()); } /** * Get the "full" name of the class (e.g. for A\B\Foo, this will return * "A\B\Foo"). * * @return class-string */ public function getName() : string { if (! $this->inNamespace()) { return $this->getShortName(); } return $this->node->namespacedName->toString(); } /** * Get the "namespace" name of the class (e.g. for A\B\Foo, this will * return "A\B"). * * @psalm-suppress PossiblyNullPropertyFetch */ public function getNamespaceName() : string { if (! $this->inNamespace()) { return ''; } return implode('\\', $this->declaringNamespace->name->parts); } /** * Decide if this class is part of a namespace. Returns false if the class * is in the global namespace or does not have a specified namespace. */ public function inNamespace() : bool { return $this->declaringNamespace !== null && $this->declaringNamespace->name !== null; } public function getExtensionName() : ?string { return $this->locatedSource->getExtensionName(); } /** * Construct a flat list of all methods from current class, traits, * parent classes and interfaces in this precise order. * * @return ReflectionMethod[] */ private function getAllMethods() : array { return array_merge( [], array_map( function (ClassMethod $methodNode) : ReflectionMethod { return ReflectionMethod::createFromNode( $this->reflector, $methodNode, $this->declaringNamespace, $this, $this ); }, $this->node->getMethods() ), ...array_map( function (ReflectionClass $trait) : array { return array_merge( [], ...array_map( function (ReflectionMethod $method) : array { return $this->createMethodsFromTrait($method); }, $trait->getMethods() ) ); }, $this->getTraits() ), ...array_map( static function (ReflectionClass $ancestor) : array { return $ancestor->getMethods(); }, array_values(array_merge( array_filter([$this->getParentClass()]), $this->getInterfaces() )) ) ); } /** * @return ReflectionMethod[] */ private function createMethodsFromTrait(ReflectionMethod $method) : array { $traitAliases = $this->getTraitAliases(); $traitPrecedences = $this->getTraitPrecedences(); $methodAst = $method->getAst(); assert($methodAst instanceof ClassMethod); $methodHash = $this->methodHash($method->getDeclaringClass()->getName(), $method->getName()); $aliases = []; foreach ($traitAliases as $aliasMethodName => $traitAliasDefinition) { if ($methodHash !== $traitAliasDefinition) { continue; } $aliases[] = ReflectionMethod::createFromNode( $this->reflector, $methodAst, $method->getDeclaringClass()->getDeclaringNamespaceAst(), $method->getDeclaringClass(), $this, $aliasMethodName ); } if ($aliases !== []) { return $aliases; } if (array_key_exists($methodHash, $traitPrecedences)) { return []; } $newMethod = ReflectionMethod::createFromNode( $this->reflector, $methodAst, $method->getDeclaringClass()->getDeclaringNamespaceAst(), $method->getDeclaringClass(), $this, $method->getAliasName() ); return [$newMethod]; } /** * Construct a flat list of methods that are available. This will search up * all parent classes/traits/interfaces/current scope for methods. * * Methods are not merged via their name as array index, since internal PHP method * sorting does not follow `\array_merge()` semantics. * * @return array<lowercase-string, ReflectionMethod> indexed by method name */ private function getMethodsIndexedByName() : array { if ($this->cachedMethods !== null) { return $this->cachedMethods; } $cachedMethods = []; foreach ($this->getAllMethods() as $method) { $methodName = strtolower($method->getName()); if (isset($cachedMethods[$methodName])) { continue; } $cachedMethods[$methodName] = $method; } $this->cachedMethods = $cachedMethods; return $this->cachedMethods; } /** * Fetch an array of all methods for this class. * * Filter the results to include only methods with certain attributes. Defaults * to no filtering. * Any combination of \ReflectionMethod::IS_STATIC, * \ReflectionMethod::IS_PUBLIC, * \ReflectionMethod::IS_PROTECTED, * \ReflectionMethod::IS_PRIVATE, * \ReflectionMethod::IS_ABSTRACT, * \ReflectionMethod::IS_FINAL. * For example if $filter = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_FINAL * the only the final public methods will be returned * * @return list<ReflectionMethod> */ public function getMethods(?int $filter = null) : array { if ($filter === null) { return array_values($this->getMethodsIndexedByName()); } return array_values( array_filter( $this->getMethodsIndexedByName(), static function (ReflectionMethod $method) use ($filter) : bool { return (bool) ($filter & $method->getModifiers()); } ) ); } /** * Get only the methods that this class implements (i.e. do not search * up parent classes etc.) * * @see ReflectionClass::getMethods for the usage of $filter * * @return ReflectionMethod[] */ public function getImmediateMethods(?int $filter = null) : array { /** @var ReflectionMethod[] $methods */ $methods = array_map( function (ClassMethod $methodNode) : ReflectionMethod { return ReflectionMethod::createFromNode( $this->reflector, $methodNode, $this->declaringNamespace, $this, $this ); }, $this->node->getMethods() ); $methodsByName = []; foreach ($methods as $method) { if ($filter !== null && ! ($filter & $method->getModifiers())) { continue; } $methodsByName[$method->getName()] = $method; } return $methodsByName; } /** * Get a single method with the name $methodName. * * @throws OutOfBoundsException */ public function getMethod(string $methodName) : ReflectionMethod { $lowercaseMethodName = strtolower($methodName); $methods = $this->getMethodsIndexedByName(); if (! isset($methods[$lowercaseMethodName])) { throw new OutOfBoundsException('Could not find method: ' . $methodName); } return $methods[$lowercaseMethodName]; } /** * Does the class have the specified method method? */ public function hasMethod(string $methodName) : bool { try { $this->getMethod($methodName); return true; } catch (OutOfBoundsException $exception) { return false; } } /** * Get an associative array of only the constants for this specific class (i.e. do not search * up parent classes etc.), with keys as constant names and values as constant values. * * @return array<string, scalar|array<scalar>|null> */ public function getImmediateConstants() : array { return array_map(static function (ReflectionClassConstant $classConstant) { return $classConstant->getValue(); }, $this->getImmediateReflectionConstants()); } /** * Get an associative array of the defined constants in this class, * with keys as constant names and values as constant values. * * @return array<string, scalar|array<scalar>|null> */ public function getConstants() : array { return array_map(static function (ReflectionClassConstant $classConstant) { return $classConstant->getValue(); }, $this->getReflectionConstants()); } /** * Get the value of the specified class constant. * * Returns null if not specified. * * @return scalar|array<scalar>|null */ public function getConstant(string $name) { $reflectionConstant = $this->getReflectionConstant($name); if (! $reflectionConstant) { return null; } return $reflectionConstant->getValue(); } /** * Does this class have the specified constant? */ public function hasConstant(string $name) : bool { return $this->getReflectionConstant($name) !== null; } /** * Get the reflection object of the specified class constant. * * Returns null if not specified. */ public function getReflectionConstant(string $name) : ?ReflectionClassConstant { return $this->getReflectionConstants()[$name] ?? null; } /** * Get an associative array of only the constants for this specific class (i.e. do not search * up parent classes etc.), with keys as constant names and values as {@see ReflectionClassConstant} objects. * * @return array<string, ReflectionClassConstant> indexed by name */ public function getImmediateReflectionConstants() : array { if ($this->cachedReflectionConstants !== null) { return $this->cachedReflectionConstants; } $constants = array_merge( [], ...array_map( function (ConstNode $constNode) : array { $constants = []; foreach ($constNode->consts as $constantPositionInNode => $constantNode) { $constants[] = ReflectionClassConstant::createFromNode($this->reflector, $constNode, $constantPositionInNode, $this); } return $constants; }, array_filter( $this->node->stmts, static function (Node\Stmt $stmt) : bool { return $stmt instanceof ConstNode; } ) ) ); return $this->cachedReflectionConstants = array_combine( array_map( static function (ReflectionClassConstant $constant) : string { return $constant->getName(); }, $constants ), $constants ); } /** * Get an associative array of the defined constants in this class, * with keys as constant names and values as {@see ReflectionClassConstant} objects. * * @return array<string, ReflectionClassConstant> indexed by name */ public function getReflectionConstants() : array { // Note: constants are not merged via their name as array index, since internal PHP constant // sorting does not follow `\array_merge()` semantics /** @var ReflectionClassConstant[] $allReflectionConstants */ $allReflectionConstants = array_merge( array_values($this->getImmediateReflectionConstants()), ...array_map( static function (ReflectionClass $ancestor) : array { return array_filter( array_values($ancestor->getReflectionConstants()), static function (ReflectionClassConstant $classConstant) : bool { return ! $classConstant->isPrivate(); } ); }, array_filter([$this->getParentClass()]) ), ...array_map( static function (ReflectionClass $interface) : array { return array_values($interface->getReflectionConstants()); }, array_values($this->getInterfaces()) ) ); $reflectionConstants = []; foreach ($allReflectionConstants as $constant) { $constantName = $constant->getName(); if (isset($reflectionConstants[$constantName])) { continue; } $reflectionConstants[$constantName] = $constant; } return $reflectionConstants; } /** * Get the constructor method for this class. * * @throws OutOfBoundsException */ public function getConstructor() : ReflectionMethod { $constructors = array_values(array_filter($this->getMethods(), static function (ReflectionMethod $method) : bool { return $method->isConstructor(); })); if (! isset($constructors[0])) { throw new OutOfBoundsException('Could not find method: __construct'); } return $constructors[0]; } /** * Get only the properties for this specific class (i.e. do not search * up parent classes etc.) * * @see ReflectionClass::getProperties() for the usage of filter * * @return array<string, ReflectionProperty> */ public function getImmediateProperties(?int $filter = null) : array { if ($this->cachedImmediateProperties === null) { $properties = []; foreach ($this->node->stmts as $stmt) { if (! ($stmt instanceof PropertyNode)) { continue; } foreach ($stmt->props as $propertyPositionInNode => $propertyNode) { $prop = ReflectionProperty::createFromNode( $this->reflector, $stmt, $propertyPositionInNode, $this->declaringNamespace, $this, $this ); $properties[$prop->getName()] = $prop; } } $this->cachedImmediateProperties = $properties; } if ($filter === null) { return $this->cachedImmediateProperties; } return array_filter( $this->cachedImmediateProperties, static function (ReflectionProperty $property) use ($filter) : bool { return (bool) ($filter & $property->getModifiers()); } ); } /** * Get the properties for this class. * * Filter the results to include only properties with certain attributes. Defaults * to no filtering. * Any combination of \ReflectionProperty::IS_STATIC, * \ReflectionProperty::IS_PUBLIC, * \ReflectionProperty::IS_PROTECTED, * \ReflectionProperty::IS_PRIVATE. * For example if $filter = \ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC * only the static public properties will be returned * * @return array<string, ReflectionProperty> */ public function getProperties(?int $filter = null) : array { if ($this->cachedProperties === null) { // merging together properties from parent class, traits, current class (in this precise order) $this->cachedProperties = array_merge( array_merge( [], ...array_map( static function (ReflectionClass $ancestor) use ($filter) : array { return array_filter( $ancestor->getProperties($filter), static function (ReflectionProperty $property) : bool { return ! $property->isPrivate(); } ); }, array_filter([$this->getParentClass()]) ), ...array_map( function (ReflectionClass $trait) use ($filter) { return array_map(function (ReflectionProperty $property) use ($trait) : ReflectionProperty { return ReflectionProperty::createFromNode( $this->reflector, $property->getAst(), $property->getPositionInAst(), $trait->declaringNamespace, $property->getDeclaringClass(), $this ); }, $trait->getProperties($filter)); }, $this->getTraits() ) ), $this->getImmediateProperties() ); } if ($filter === null) { return $this->cachedProperties; } return array_filter( $this->cachedProperties, static function (ReflectionProperty $property) use ($filter) : bool { return (bool) ($filter & $property->getModifiers()); } ); } /** * Get the property called $name. * * Returns null if property does not exist. */ public function getProperty(string $name) : ?ReflectionProperty { $properties = $this->getProperties(); if (! isset($properties[$name])) { return null; } return $properties[$name]; } /** * Does this class have the specified property? */ public function hasProperty(string $name) : bool { return $this->getProperty($name) !== null; } /** * @return array<string, scalar|array<scalar>|null> */ public function getDefaultProperties() : array { return array_map( static function (ReflectionProperty $property) { return $property->getDefaultValue(); }, array_filter($this->getProperties(), static function (ReflectionProperty $property) : bool { return $property->isDefault(); }) ); } public function getFileName() : ?string { return $this->locatedSource->getFileName(); } public function getLocatedSource() : LocatedSource { return $this->locatedSource; } /** * Get the line number that this class starts on. */ public function getStartLine() : int { return $this->node->getStartLine(); } /** * Get the line number that this class ends on. */ public function getEndLine() : int { return $this->node->getEndLine(); } public function getStartColumn() : int { return CalculateReflectionColum::getStartColumn($this->locatedSource->getSource(), $this->node); } public function getEndColumn() : int { return CalculateReflectionColum::getEndColumn($this->locatedSource->getSource(), $this->node); } /** * Get the parent class, if it is defined. If this class does not have a * specified parent class, this will throw an exception. * * @throws NotAClassReflection */ public function getParentClass() : ?ReflectionClass { if (! ($this->node instanceof ClassNode) || $this->node->extends === null) { return null; } $parent = $this->reflector->reflect($this->node->extends->toString()); // @TODO use actual `ClassReflector` or `FunctionReflector`? assert($parent instanceof self); if ($parent->isInterface() || $parent->isTrait()) { throw NotAClassReflection::fromReflectionClass($parent); } return $parent; } /** * Gets the parent class names. * * @return list<class-string> A numerical array with parent class names as the values. */ public function getParentClassNames() : array { return array_map(static function (self $parentClass) : string { return $parentClass->getName(); }, array_slice(array_reverse($this->getInheritanceClassHierarchy()), 1)); } public function getDocComment() : string { return GetFirstDocComment::forNode($this->node); } public function isAnonymous() : bool { return $this->node->name === null; } /** * Is this an internal class? */ public function isInternal() : bool { return $this->locatedSource->isInternal(); } /** * Is this a user-defined function (will always return the opposite of * whatever isInternal returns). */ public function isUserDefined() : bool { return ! $this->isInternal(); } /** * Is this class an abstract class. */ public function isAbstract() : bool { return $this->node instanceof ClassNode && $this->node->isAbstract(); } /** * Is this class a final class. */ public function isFinal() : bool { return $this->node instanceof ClassNode && $this->node->isFinal(); } /** * Get the core-reflection-compatible modifier values. */ public function getModifiers() : int { $val = 0; $val += $this->isAbstract() ? CoreReflectionClass::IS_EXPLICIT_ABSTRACT : 0; $val += $this->isFinal() ? CoreReflectionClass::IS_FINAL : 0; return $val; } /** * Is this reflection a trait? */ public function isTrait() : bool { return $this->node instanceof TraitNode; } /** * Is this reflection an interface? */ public function isInterface() : bool { return $this->node instanceof InterfaceNode; } /** * Get the traits used, if any are defined. If this class does not have any * defined traits, this will return an empty array. * * @return list<ReflectionClass> */ public function getTraits() : array { return array_map( function (Node\Name $importedTrait) : ReflectionClass { return $this->reflectClassForNamedNode($importedTrait); }, array_merge( [], ...array_map( static function (TraitUse $traitUse) : array { return $traitUse->traits; }, array_filter($this->node->stmts, static function (Node $node) : bool { return $node instanceof TraitUse; }) ) ) ); } /** * Given an AST Node\Name, create a new ReflectionClass for the element. */ private function reflectClassForNamedNode(Node\Name $node) : self { // @TODO use actual `ClassReflector` or `FunctionReflector`? if ($this->isAnonymous()) { $class = (new BetterReflection())->classReflector()->reflect($node->toString()); } else { $class = $this->reflector->reflect($node->toString()); assert($class instanceof self); } return $class; } /** * Get the names of the traits used as an array of strings, if any are * defined. If this class does not have any defined traits, this will * return an empty array. * * @return string[] */ public function getTraitNames() : array { return array_map( static function (ReflectionClass $trait) : string { return $trait->getName(); }, $this->getTraits() ); } /** * Return a list of the aliases used when importing traits for this class. * The returned array is in key/value pair in this format:. * * 'aliasedMethodName' => 'ActualClass::actualMethod' * * @return array<string, string> * * @example * // When reflecting a class such as: * class Foo * { * use MyTrait { * myTraitMethod as myAliasedMethod; * } * } * // This method would return * // ['myAliasedMethod' => 'MyTrait::myTraitMethod'] */ public function getTraitAliases() : array { $this->parseTraitUsages(); /** @return array<string, string> */ return $this->cachedTraitAliases; } /** * Return a list of the precedences used when importing traits for this class. * The returned array is in key/value pair in this format:. * * 'Class::method' => 'Class::method' * * @return array<string, string> * * @example * // When reflecting a class such as: * class Foo * { * use MyTrait, MyTrait2 { * MyTrait2::foo insteadof MyTrait1; * } * } * // This method would return * // ['MyTrait1::foo' => 'MyTrait2::foo'] */ private function getTraitPrecedences() : array { $this->parseTraitUsages(); /** @return array<string, string> */ return $this->cachedTraitPrecedences; } private function parseTraitUsages() : void { if ($this->cachedTraitAliases !== null && $this->cachedTraitPrecedences !== null) { return; } /** @var Node\Stmt\TraitUse[] $traitUsages */ $traitUsages = array_filter($this->node->stmts, static function (Node $node) : bool { return $node instanceof TraitUse; }); $this->cachedTraitAliases = []; $this->cachedTraitPrecedences = []; foreach ($traitUsages as $traitUsage) { $traitNames = $traitUsage->traits; $adaptations = $traitUsage->adaptations; foreach ($adaptations as $adaptation) { $usedTrait = $adaptation->trait; if ($usedTrait === null) { $usedTrait = $traitNames[0]; } if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias && $adaptation->newName) { $this->cachedTraitAliases[$adaptation->newName->name] = $this->methodHash($usedTrait->toString(), $adaptation->method->toString()); continue; } if (! $adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence || ! $adaptation->insteadof) { continue; } foreach ($adaptation->insteadof as $insteadof) { $adaptationNameHash = $this->methodHash($insteadof->toString(), $adaptation->method->toString()); $originalNameHash = $this->methodHash($usedTrait->toString(), $adaptation->method->toString()); $this->cachedTraitPrecedences[$adaptationNameHash] = $originalNameHash; } } } } /** * @psalm-pure */ private function methodHash(string $className, string $methodName) : string { return sprintf( '%s::%s', $className, $methodName ); } /** * Gets the interfaces. * * @link https://php.net/manual/en/reflectionclass.getinterfaces.php * * @return array<string, ReflectionClass> An associative array of interfaces, with keys as interface names and the array * values as {@see ReflectionClass} objects. */ public function getInterfaces() : array { return array_merge(...array_map( static function (self $reflectionClass) : array { return $reflectionClass->getCurrentClassImplementedInterfacesIndexedByName(); }, $this->getInheritanceClassHierarchy() )); } /** * Get only the interfaces that this class implements (i.e. do not search * up parent classes etc.) * * @return array<string, ReflectionClass> */ public function getImmediateInterfaces() : array { return $this->getCurrentClassImplementedInterfacesIndexedByName(); } /** * Gets the interface names. * * @link https://php.net/manual/en/reflectionclass.getinterfacenames.php * * @return list<string> A numerical array with interface names as the values. */ public function getInterfaceNames() : array { return array_values(array_map( static function (self $interface) : string { return $interface->getName(); }, $this->getInterfaces() )); } /** * Checks whether the given object is an instance. * * @link https://php.net/manual/en/reflectionclass.isinstance.php * * @param object $object * * @throws NotAnObject * * @psalm-suppress DocblockTypeContradiction */ public function isInstance($object) : bool { if (! is_object($object)) { throw NotAnObject::fromNonObject($object); } $className = $this->getName(); // note: since $object was loaded, we can safely assume that $className is available in the current // php script execution context return $object instanceof $className; } /** * Checks whether the given class string is a subclass of this class. * * @link https://php.net/manual/en/reflectionclass.isinstance.php */ public function isSubclassOf(string $className) : bool { return in_array( ltrim($className, '\\'), array_map( static function (self $reflectionClass) : string { return $reflectionClass->getName(); }, array_slice(array_reverse($this->getInheritanceClassHierarchy()), 1) ), true ); } /** * Checks whether this class implements the given interface. * * @link https://php.net/manual/en/reflectionclass.implementsinterface.php */ public function implementsInterface(string $interfaceName) : bool { return in_array(ltrim($interfaceName, '\\'), $this->getInterfaceNames(), true); } /** * Checks whether this reflection is an instantiable class * * @link https://php.net/manual/en/reflectionclass.isinstantiable.php */ public function isInstantiable() : bool { // @TODO doesn't consider internal non-instantiable classes yet. if ($this->isAbstract()) { return false; } if ($this->isInterface()) { return false; } if ($this->isTrait()) { return false; } try { return $this->getConstructor()->isPublic(); } catch (OutOfBoundsException $e) { return true; } } /** * Checks whether this is a reflection of a class that supports the clone operator * * @link https://php.net/manual/en/reflectionclass.iscloneable.php */ public function isCloneable() : bool { if (! $this->isInstantiable()) { return false; } if (! $this->hasMethod('__clone')) { return true; } return $this->getMethod('__clone')->isPublic(); } /** * Checks if iterateable * * @link https://php.net/manual/en/reflectionclass.isiterateable.php */ public function isIterateable() : bool { return $this->isInstantiable() && $this->implementsInterface(Traversable::class); } /** * @return array<string, ReflectionClass> */ private function getCurrentClassImplementedInterfacesIndexedByName() : array { $node = $this->node; if ($node instanceof ClassNode) { return array_merge( [], ...array_map( function (Node\Name $interfaceName) : array { return $this ->reflectClassForNamedNode($interfaceName) ->getInterfacesHierarchy(); }, $node->implements ) ); } // assumption: first key is the current interface return $this->isInterface() ? array_slice($this->getInterfacesHierarchy(), 1) : []; } /** * @return ReflectionClass[] ordered from inheritance root to leaf (this class) */ private function getInheritanceClassHierarchy() : array { $parentClass = $this->getParentClass(); return $parentClass ? array_merge($parentClass->getInheritanceClassHierarchy(), [$this]) : [$this]; } /** * This method allows us to retrieve all interfaces parent of the this interface. Do not use on class nodes! * * @return array<string, ReflectionClass> parent interfaces of this interface * * @throws NotAnInterfaceReflection */ private function getInterfacesHierarchy() : array { if (! $this->isInterface()) { throw NotAnInterfaceReflection::fromReflectionClass($this); } $node = $this->node; assert($node instanceof InterfaceNode); return array_merge( [$this->getName() => $this], ...array_map( function (Node\Name $interfaceName) : array { return $this ->reflectClassForNamedNode($interfaceName) ->getInterfacesHierarchy(); }, $node->extends ) ); } /** * @throws Uncloneable */ public function __clone() { throw Uncloneable::fromClass(static::class); } /** * Get the value of a static property, if it exists. Throws a * PropertyDoesNotExist exception if it does not exist or is not static. * (note, differs very slightly from internal reflection behaviour) * * @return mixed * * @throws ClassDoesNotExist * @throws NoObjectProvided * @throws NotAnObject * @throws ObjectNotInstanceOfClass */ public function getStaticPropertyValue(string $propertyName) { $property = $this->getProperty($propertyName); if (! $property || ! $property->isStatic()) { throw PropertyDoesNotExist::fromName($propertyName); } return $property->getValue(); } /** * Set the value of a static property * * @param mixed $value * * @throws ClassDoesNotExist * @throws NoObjectProvided * @throws NotAnObject * @throws ObjectNotInstanceOfClass */ public function setStaticPropertyValue(string $propertyName, $value) : void { $property = $this->getProperty($propertyName); if (! $property || ! $property->isStatic()) { throw PropertyDoesNotExist::fromName($propertyName); } $property->setValue($value); } /** * @return array<string, mixed> */ public function getStaticProperties() : array { $staticProperties = []; foreach ($this->getProperties() as $property) { if (! $property->isStatic()) { continue; } $staticProperties[$property->getName()] = $property->getValue(); } return $staticProperties; } /** * Retrieve the AST node for this class */ public function getAst() : ClassLikeNode { return $this->node; } public function getDeclaringNamespaceAst() : ?Namespace_ { return $this->declaringNamespace; } /** * Set whether this class is final or not * * @throws NotAClassReflection */ public function setFinal(bool $isFinal) : void { if (! $this->node instanceof ClassNode) { throw NotAClassReflection::fromReflectionClass($this); } if ($isFinal === true) { $this->node->flags |= ClassNode::MODIFIER_FINAL; return; } $this->node->flags &= ~ClassNode::MODIFIER_FINAL; } /** * Remove the named method from the class. * * Returns true if method was successfully removed. * Returns false if method was not found, or could not be removed. */ public function removeMethod(string $methodName) : bool { $lowerName = strtolower($methodName); foreach ($this->node->stmts as $key => $stmt) { if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { unset($this->node->stmts[$key]); $this->cachedMethods = null; return true; } } return false; } /** * Add a new method to the class. */ public function addMethod(string $methodName) : void { $this->node->stmts[] = new ClassMethod($methodName); $this->cachedMethods = null; } /** * Add a new property to the class. * * Visibility defaults to \ReflectionProperty::IS_PUBLIC, or can be ::IS_PROTECTED or ::IS_PRIVATE. */ public function addProperty( string $propertyName, int $visibility = CoreReflectionProperty::IS_PUBLIC, bool $static = false ) : void { $type = 0; switch ($visibility) { case CoreReflectionProperty::IS_PRIVATE: $type |= ClassNode::MODIFIER_PRIVATE; break; case CoreReflectionProperty::IS_PROTECTED: $type |= ClassNode::MODIFIER_PROTECTED; break; default: $type |= ClassNode::MODIFIER_PUBLIC; break; } if ($static) { $type |= ClassNode::MODIFIER_STATIC; } $this->node->stmts[] = new PropertyNode($type, [new Node\Stmt\PropertyProperty($propertyName)]); $this->cachedProperties = null; $this->cachedImmediateProperties = null; } /** * Remove a property from the class. */ public function removeProperty(string $propertyName) : bool { $lowerName = strtolower($propertyName); foreach ($this->node->stmts as $key => $stmt) { if (! ($stmt instanceof PropertyNode)) { continue; } $propertyNames = array_map(static function (Node\Stmt\PropertyProperty $propertyProperty) : string { return $propertyProperty->name->toLowerString(); }, $stmt->props); if (in_array($lowerName, $propertyNames, true)) { $this->cachedProperties = null; $this->cachedImmediateProperties = null; unset($this->node->stmts[$key]); return true; } } return false; } }