Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ClassMirror.php

class ClassMirror

Class mirror. Core doubler class. Mirrors specific class and/or interfaces into class node tree.

@author Konstantin Kudryashov <ever.zet@gmail.com>

Hierarchy

  • class \Prophecy\Doubler\Generator\ClassMirror

Expanded class hierarchy of ClassMirror

1 file declares its use of ClassMirror
Doubler.php in vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php

File

vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php, line 32

Namespace

Prophecy\Doubler\Generator
View source
class ClassMirror {
    private const REFLECTABLE_METHODS = array(
        '__construct',
        '__destruct',
        '__sleep',
        '__wakeup',
        '__toString',
        '__call',
        '__invoke',
    );
    
    /**
     * Reflects provided arguments into class node.
     *
     * @param ReflectionClass<object>|null $class
     * @param ReflectionClass<object>[]    $interfaces
     *
     * @return Node\ClassNode
     *
     */
    public function reflect(?ReflectionClass $class, array $interfaces) {
        $node = new Node\ClassNode();
        if (null !== $class) {
            if (true === $class->isInterface()) {
                throw new InvalidArgumentException(sprintf("Could not reflect %s as a class, because it\n" . "is interface - use the second argument instead.", $class->getName()));
            }
            $this->reflectClassToNode($class, $node);
        }
        foreach ($interfaces as $interface) {
            if (!$interface instanceof ReflectionClass) {
                throw new InvalidArgumentException(sprintf("[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n" . "a second argument to `ClassMirror::reflect(...)`, but got %s.", is_object($interface) ? get_class($interface) . ' class' : gettype($interface)));
            }
            if (false === $interface->isInterface()) {
                throw new InvalidArgumentException(sprintf("Could not reflect %s as an interface, because it\n" . "is class - use the first argument instead.", $interface->getName()));
            }
            $this->reflectInterfaceToNode($interface, $node);
        }
        $node->addInterface('Prophecy\\Doubler\\Generator\\ReflectionInterface');
        return $node;
    }
    
    /**
     * @param ReflectionClass<object> $class
     */
    private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node) : void {
        if (true === $class->isFinal()) {
            throw new ClassMirrorException(sprintf('Could not reflect class %s as it is marked final.', $class->getName()), $class);
        }
        if (method_exists(ReflectionClass::class, 'isReadOnly')) {
            $node->setReadOnly($class->isReadOnly());
        }
        $node->setParentClass($class->getName());
        foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
            if (false === $method->isProtected()) {
                continue;
            }
            $this->reflectMethodToNode($method, $node);
        }
        foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            if (0 === strpos($method->getName(), '_') && !in_array($method->getName(), self::REFLECTABLE_METHODS)) {
                continue;
            }
            if (true === $method->isFinal()) {
                $node->addUnextendableMethod($method->getName());
                continue;
            }
            $this->reflectMethodToNode($method, $node);
        }
    }
    
    /**
     * @param ReflectionClass<object> $interface
     */
    private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node) : void {
        $node->addInterface($interface->getName());
        foreach ($interface->getMethods() as $method) {
            $this->reflectMethodToNode($method, $node);
        }
    }
    private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode) : void {
        $node = new Node\MethodNode($method->getName());
        if (true === $method->isProtected()) {
            $node->setVisibility('protected');
        }
        if (true === $method->isStatic()) {
            $node->setStatic();
        }
        if (true === $method->returnsReference()) {
            $node->setReturnsReference();
        }
        if ($method->hasReturnType()) {
            \assert($method->getReturnType() !== null);
            $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()
                ->allowsNull());
            $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes));
        }
        elseif (method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) {
            \assert($method->getTentativeReturnType() !== null);
            $returnTypes = $this->getTypeHints($method->getTentativeReturnType(), $method->getDeclaringClass(), $method->getTentativeReturnType()
                ->allowsNull());
            $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes));
        }
        if (is_array($params = $method->getParameters()) && count($params)) {
            foreach ($params as $param) {
                $this->reflectArgumentToNode($param, $method->getDeclaringClass(), $node);
            }
        }
        $classNode->addMethod($node);
    }
    
    /**
     * @param ReflectionClass<object> $declaringClass
     *
     * @return void
     */
    private function reflectArgumentToNode(ReflectionParameter $parameter, ReflectionClass $declaringClass, Node\MethodNode $methodNode) : void {
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
        $node = new Node\ArgumentNode($name);
        $typeHints = $this->getTypeHints($parameter->getType(), $declaringClass, $parameter->allowsNull());
        $node->setTypeNode(new ArgumentTypeNode(...$typeHints));
        if ($parameter->isVariadic()) {
            $node->setAsVariadic();
        }
        if ($this->hasDefaultValue($parameter)) {
            $node->setDefault($this->getDefaultValue($parameter));
        }
        if ($parameter->isPassedByReference()) {
            $node->setAsPassedByReference();
        }
        $methodNode->addArgument($node);
    }
    private function hasDefaultValue(ReflectionParameter $parameter) : bool {
        if ($parameter->isVariadic()) {
            return false;
        }
        if ($parameter->isDefaultValueAvailable()) {
            return true;
        }
        return $parameter->isOptional() || $parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100;
    }
    
    /**
     * @return mixed
     */
    private function getDefaultValue(ReflectionParameter $parameter) {
        if (!$parameter->isDefaultValueAvailable()) {
            return null;
        }
        return $parameter->getDefaultValue();
    }
    
    /**
     * @param ReflectionClass<object> $class
     *
     * @return list<string>
     */
    private function getTypeHints(?ReflectionType $type, ReflectionClass $class, bool $allowsNull) : array {
        $types = [];
        if ($type instanceof ReflectionNamedType) {
            $types = [
                $type->getName(),
            ];
        }
        elseif ($type instanceof ReflectionUnionType) {
            $types = $type->getTypes();
            if (\PHP_VERSION_ID >= 80200) {
                foreach ($types as $reflectionType) {
                    if ($reflectionType instanceof ReflectionIntersectionType) {
                        throw new ClassMirrorException('Doubling intersection types is not supported', $class);
                    }
                }
            }
        }
        elseif ($type instanceof ReflectionIntersectionType) {
            throw new ClassMirrorException('Doubling intersection types is not supported', $class);
        }
        elseif (is_object($type)) {
            throw new ClassMirrorException('Unknown reflection type ' . get_class($type), $class);
        }
        $types = array_map(function (string $type) use ($class) {
            if ($type === 'self') {
                return $class->getName();
            }
            if ($type === 'parent') {
                if (false === $class->getParentClass()) {
                    throw new ClassMirrorException(sprintf('Invalid type "parent" in class "%s" without a parent', $class->getName()), $class);
                }
                return $class->getParentClass()
                    ->getName();
            }
            return $type;
        }, $types);
        if ($types && $types != [
            'mixed',
        ] && $allowsNull) {
            $types[] = 'null';
        }
        return $types;
    }

}

Members

Title Sort descending Modifiers Object type Summary
ClassMirror::getDefaultValue private function
ClassMirror::getTypeHints private function
ClassMirror::hasDefaultValue private function
ClassMirror::reflect public function Reflects provided arguments into class node.
ClassMirror::REFLECTABLE_METHODS private constant
ClassMirror::reflectArgumentToNode private function
ClassMirror::reflectClassToNode private function
ClassMirror::reflectInterfaceToNode private function
ClassMirror::reflectMethodToNode private function

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal