Completed
Pull Request — master (#294)
by Frank
03:03 queued 56s
created

ClassMirror   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 214
Duplicated Lines 3.27 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 5
dl 7
loc 214
rs 7.9487
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
C reflect() 7 39 7
B reflectClassToNode() 0 27 6
A reflectInterfaceToNode() 0 8 2
C reflectMethodToNode() 0 38 11
B reflectArgumentToNode() 0 21 5
A hasDefaultValue() 0 12 4
A getDefaultValue() 0 8 2
B getTypeHint() 0 20 7
A isVariadic() 0 4 2
A isNullable() 0 4 2
A getParameterClassName() 0 10 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ClassMirror often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMirror, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Prophecy.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *     Marcello Duarte <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Prophecy\Doubler\Generator;
13
14
use Prophecy\Exception\InvalidArgumentException;
15
use Prophecy\Exception\Doubler\ClassMirrorException;
16
use ReflectionClass;
17
use ReflectionMethod;
18
use ReflectionParameter;
19
20
/**
21
 * Class mirror.
22
 * Core doubler class. Mirrors specific class and/or interfaces into class node tree.
23
 *
24
 * @author Konstantin Kudryashov <[email protected]>
25
 */
26
class ClassMirror
27
{
28
    /**
29
     * Reflects provided arguments into class node.
30
     *
31
     * @param ReflectionClass   $class
32
     * @param ReflectionClass[] $interfaces
33
     *
34
     * @return Node\ClassNode
35
     *
36
     * @throws \Prophecy\Exception\InvalidArgumentException
37
     */
38
    public function reflect(ReflectionClass $class = null, array $interfaces)
39
    {
40
        $node = new Node\ClassNode;
41
42
        if (null !== $class) {
43
            if (true === $class->isInterface()) {
44
                throw new InvalidArgumentException(sprintf(
45
                    "Could not reflect %s as a class, because it\n".
46
                    "is interface - use the second argument instead.",
47
                    $class->getName()
48
                ));
49
            }
50
51
            $this->reflectClassToNode($class, $node);
52
        }
53
54
        foreach ($interfaces as $interface) {
55 View Code Duplication
            if (!$interface instanceof ReflectionClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
56
                throw new InvalidArgumentException(sprintf(
57
                    "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n".
58
                    "a second argument to `ClassMirror::reflect(...)`, but got %s.",
59
                    is_object($interface) ? get_class($interface).' class' : gettype($interface)
60
                ));
61
            }
62
            if (false === $interface->isInterface()) {
63
                throw new InvalidArgumentException(sprintf(
64
                    "Could not reflect %s as an interface, because it\n".
65
                    "is class - use the first argument instead.",
66
                    $interface->getName()
67
                ));
68
            }
69
70
            $this->reflectInterfaceToNode($interface, $node);
71
        }
72
73
        $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface');
74
75
        return $node;
76
    }
77
78
    private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
79
    {
80
        if (true === $class->isFinal()) {
81
            throw new ClassMirrorException(sprintf(
82
                'Could not reflect class %s as it is marked final.', $class->getName()
83
            ), $class);
84
        }
85
86
        $node->setParentClass($class->getName());
87
88
        foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
89
            if (false === $method->isProtected()) {
90
                continue;
91
            }
92
93
            $this->reflectMethodToNode($method, $node);
94
        }
95
96
        foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
97
            if (true === $method->isFinal()) {
98
                $node->addUnextendableMethod($method->getName());
99
                continue;
100
            }
101
102
            $this->reflectMethodToNode($method, $node);
103
        }
104
    }
105
106
    private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
107
    {
108
        $node->addInterface($interface->getName());
109
110
        foreach ($interface->getMethods() as $method) {
111
            $this->reflectMethodToNode($method, $node);
112
        }
113
    }
114
115
    private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
116
    {
117
        $node = new Node\MethodNode($method->getName());
118
119
        if (true === $method->isProtected()) {
120
            $node->setVisibility('protected');
121
        }
122
123
        if (true === $method->isStatic()) {
124
            $node->setStatic();
125
        }
126
127
        if (true === $method->returnsReference()) {
128
            $node->setReturnsReference();
129
        }
130
131
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $method->hasReturnType()) {
132
            $returnType = (string) $method->getReturnType();
133
            $returnTypeLower = strtolower($returnType);
134
135
            if ('self' === $returnTypeLower) {
136
                $returnType = $method->getDeclaringClass()->getName();
137
            }
138
            if ('parent' === $returnTypeLower) {
139
                $returnType = $method->getDeclaringClass()->getParentClass()->getName();
140
            }
141
142
            $node->setReturnType($returnType);
143
        }
144
145
        if (is_array($params = $method->getParameters()) && count($params)) {
146
            foreach ($params as $param) {
147
                $this->reflectArgumentToNode($param, $node);
148
            }
149
        }
150
151
        $classNode->addMethod($node);
152
    }
153
154
    private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
155
    {
156
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
157
        $node = new Node\ArgumentNode($name);
158
159
        $node->setTypeHint($this->getTypeHint($parameter));
160
161
        if ($this->isVariadic($parameter)) {
162
            $node->setAsVariadic();
163
        }
164
165
        if ($this->hasDefaultValue($parameter)) {
166
            $node->setDefault($this->getDefaultValue($parameter));
167
        }
168
169
        if ($parameter->isPassedByReference()) {
170
            $node->setAsPassedByReference();
171
        }
172
173
        $methodNode->addArgument($node);
174
    }
175
176
    private function hasDefaultValue(ReflectionParameter $parameter)
177
    {
178
        if ($this->isVariadic($parameter)) {
179
            return false;
180
        }
181
182
        if ($parameter->isDefaultValueAvailable()) {
183
            return true;
184
        }
185
186
        return $parameter->isOptional() || $this->isNullable($parameter);
187
    }
188
189
    private function getDefaultValue(ReflectionParameter $parameter)
190
    {
191
        if (!$parameter->isDefaultValueAvailable()) {
192
            return null;
193
        }
194
195
        return $parameter->getDefaultValue();
196
    }
197
198
    private function getTypeHint(ReflectionParameter $parameter)
199
    {
200
        if (null !== $className = $this->getParameterClassName($parameter)) {
201
            return $className;
202
        }
203
204
        if (true === $parameter->isArray()) {
205
            return 'array';
206
        }
207
208
        if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
209
            return 'callable';
210
        }
211
212
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
213
            return (string) $parameter->getType();
214
        }
215
216
        return null;
217
    }
218
219
    private function isVariadic(ReflectionParameter $parameter)
220
    {
221
        return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
222
    }
223
224
    private function isNullable(ReflectionParameter $parameter)
225
    {
226
        return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
227
    }
228
229
    private function getParameterClassName(ReflectionParameter $parameter)
230
    {
231
        try {
232
            return $parameter->getClass() ? $parameter->getClass()->getName() : null;
233
        } catch (\ReflectionException $e) {
234
            preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
235
236
            return isset($matches[1]) ? $matches[1] : null;
237
        }
238
    }
239
}
240