summaryrefslogtreecommitdiff
path: root/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php')
-rw-r--r--vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php254
1 files changed, 254 insertions, 0 deletions
diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php
new file mode 100644
index 000000000..5d9cd2d20
--- /dev/null
+++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php
@@ -0,0 +1,254 @@
+<?php
+
+/*
+ * This file is part of the Prophecy.
+ * (c) Konstantin Kudryashov <[email protected]>
+ * Marcello Duarte <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Prophecy\Doubler\Generator;
+
+use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
+use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
+use Prophecy\Exception\InvalidArgumentException;
+use Prophecy\Exception\Doubler\ClassMirrorException;
+use ReflectionClass;
+use ReflectionIntersectionType;
+use ReflectionMethod;
+use ReflectionNamedType;
+use ReflectionParameter;
+use ReflectionType;
+use ReflectionUnionType;
+
+/**
+ * Class mirror.
+ * Core doubler class. Mirrors specific class and/or interfaces into class node tree.
+ *
+ * @author Konstantin Kudryashov <[email protected]>
+ */
+class ClassMirror
+{
+ private static $reflectableMethods = array(
+ '__construct',
+ '__destruct',
+ '__sleep',
+ '__wakeup',
+ '__toString',
+ '__call',
+ '__invoke'
+ );
+
+ /**
+ * Reflects provided arguments into class node.
+ *
+ * @param ReflectionClass|null $class
+ * @param ReflectionClass[] $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;
+ }
+
+ private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
+ {
+ if (true === $class->isFinal()) {
+ throw new ClassMirrorException(sprintf(
+ 'Could not reflect class %s as it is marked final.', $class->getName()
+ ), $class);
+ }
+
+ $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::$reflectableMethods)) {
+ continue;
+ }
+
+ if (true === $method->isFinal()) {
+ $node->addUnextendableMethod($method->getName());
+ continue;
+ }
+
+ $this->reflectMethodToNode($method, $node);
+ }
+ }
+
+ private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
+ {
+ $node->addInterface($interface->getName());
+
+ foreach ($interface->getMethods() as $method) {
+ $this->reflectMethodToNode($method, $node);
+ }
+ }
+
+ private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
+ {
+ $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()) {
+ $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()->allowsNull());
+ $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes));
+ }
+ elseif (method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) {
+ $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, $node);
+ }
+ }
+
+ $classNode->addMethod($node);
+ }
+
+ private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
+ {
+ $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
+ $node = new Node\ArgumentNode($name);
+
+ $typeHints = $this->getTypeHints($parameter->getType(), $parameter->getDeclaringClass(), $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)
+ {
+ if ($parameter->isVariadic()) {
+ return false;
+ }
+
+ if ($parameter->isDefaultValueAvailable()) {
+ return true;
+ }
+
+ return $parameter->isOptional() || ($parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100);
+ }
+
+ private function getDefaultValue(ReflectionParameter $parameter)
+ {
+ if (!$parameter->isDefaultValueAvailable()) {
+ return null;
+ }
+
+ return $parameter->getDefaultValue();
+ }
+
+ 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();
+ }
+ 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') {
+ return $class->getParentClass()->getName();
+ }
+
+ return $type;
+ },
+ $types
+ );
+
+ if ($types && $types != ['mixed'] && $allowsNull) {
+ $types[] = 'null';
+ }
+
+ return $types;
+ }
+}