Completed
Pull Request — master (#287)
by Sascha-Oliver
03:13 queued 51s
created

ClassMirror   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 233
Duplicated Lines 3 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 5
dl 7
loc 233
rs 6.5957
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
C reflect() 7 39 7
A reflectInterfaceToNode() 0 8 2
C reflectClassToNode() 0 32 8
D reflectMethodToNode() 0 42 13
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
    private static $reflectableMethods = array(
29
        '__construct',
30
        '__destruct',
31
        '__sleep',
32
        '__wakeup',
33
        '__toString',
34
        '__call',
35
        '__invoke'
36
    );
37
38
    /**
39
     * Reflects provided arguments into class node.
40
     *
41
     * @param ReflectionClass   $class
42
     * @param ReflectionClass[] $interfaces
43
     *
44
     * @return Node\ClassNode
45
     *
46
     * @throws \Prophecy\Exception\InvalidArgumentException
47
     */
48
    public function reflect(ReflectionClass $class = null, array $interfaces)
49
    {
50
        $node = new Node\ClassNode;
51
52
        if (null !== $class) {
53
            if (true === $class->isInterface()) {
54
                throw new InvalidArgumentException(sprintf(
55
                    "Could not reflect %s as a class, because it\n".
56
                    "is interface - use the second argument instead.",
57
                    $class->getName()
58
                ));
59
            }
60
61
            $this->reflectClassToNode($class, $node);
62
        }
63
64
        foreach ($interfaces as $interface) {
65 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...
66
                throw new InvalidArgumentException(sprintf(
67
                    "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n".
68
                    "a second argument to `ClassMirror::reflect(...)`, but got %s.",
69
                    is_object($interface) ? get_class($interface).' class' : gettype($interface)
70
                ));
71
            }
72
            if (false === $interface->isInterface()) {
73
                throw new InvalidArgumentException(sprintf(
74
                    "Could not reflect %s as an interface, because it\n".
75
                    "is class - use the first argument instead.",
76
                    $interface->getName()
77
                ));
78
            }
79
80
            $this->reflectInterfaceToNode($interface, $node);
81
        }
82
83
        $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface');
84
85
        return $node;
86
    }
87
88
    private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
89
    {
90
        if (true === $class->isFinal()) {
91
            throw new ClassMirrorException(sprintf(
92
                'Could not reflect class %s as it is marked final.', $class->getName()
93
            ), $class);
94
        }
95
96
        $node->setParentClass($class->getName());
97
98
        foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
99
            if (false === $method->isProtected()) {
100
                continue;
101
            }
102
103
            $this->reflectMethodToNode($method, $node);
104
        }
105
106
        foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
107
            if (0 === strpos($method->getName(), '_')
108
                && !in_array($method->getName(), self::$reflectableMethods)) {
109
                continue;
110
            }
111
112
            if (true === $method->isFinal()) {
113
                $node->addUnextendableMethod($method->getName());
114
                continue;
115
            }
116
117
            $this->reflectMethodToNode($method, $node);
118
        }
119
    }
120
121
    private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
122
    {
123
        $node->addInterface($interface->getName());
124
125
        foreach ($interface->getMethods() as $method) {
126
            $this->reflectMethodToNode($method, $node);
127
        }
128
    }
129
130
    private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
131
    {
132
        $node = new Node\MethodNode($method->getName());
133
134
        if (true === $method->isProtected()) {
135
            $node->setVisibility('protected');
136
        }
137
138
        if (true === $method->isStatic()) {
139
            $node->setStatic();
140
        }
141
142
        if (true === $method->returnsReference()) {
143
            $node->setReturnsReference();
144
        }
145
146
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $method->hasReturnType()) {
147
            $returnType = (string) $method->getReturnType();
148
            $returnTypeLower = strtolower($returnType);
149
150
            if ('self' === $returnTypeLower) {
151
                $returnType = $method->getDeclaringClass()->getName();
152
            }
153
            if ('parent' === $returnTypeLower) {
154
                $returnType = $method->getDeclaringClass()->getParentClass()->getName();
155
            }
156
157
            $node->setReturnType($returnType);
158
159
            if (version_compare(PHP_VERSION, '7.1', '>=') && true === $method->getReturnType()->allowsNull()) {
160
                $node->setNullableReturnType(true);
161
            }
162
        }
163
164
        if (is_array($params = $method->getParameters()) && count($params)) {
165
            foreach ($params as $param) {
166
                $this->reflectArgumentToNode($param, $node);
167
            }
168
        }
169
170
        $classNode->addMethod($node);
171
    }
172
173
    private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
174
    {
175
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
176
        $node = new Node\ArgumentNode($name);
177
178
        $node->setTypeHint($this->getTypeHint($parameter));
179
180
        if ($this->isVariadic($parameter)) {
181
            $node->setAsVariadic();
182
        }
183
184
        if ($this->hasDefaultValue($parameter)) {
185
            $node->setDefault($this->getDefaultValue($parameter));
186
        }
187
188
        if ($parameter->isPassedByReference()) {
189
            $node->setAsPassedByReference();
190
        }
191
192
        $methodNode->addArgument($node);
193
    }
194
195
    private function hasDefaultValue(ReflectionParameter $parameter)
196
    {
197
        if ($this->isVariadic($parameter)) {
198
            return false;
199
        }
200
201
        if ($parameter->isDefaultValueAvailable()) {
202
            return true;
203
        }
204
205
        return $parameter->isOptional() || $this->isNullable($parameter);
206
    }
207
208
    private function getDefaultValue(ReflectionParameter $parameter)
209
    {
210
        if (!$parameter->isDefaultValueAvailable()) {
211
            return null;
212
        }
213
214
        return $parameter->getDefaultValue();
215
    }
216
217
    private function getTypeHint(ReflectionParameter $parameter)
218
    {
219
        if (null !== $className = $this->getParameterClassName($parameter)) {
220
            return $className;
221
        }
222
223
        if (true === $parameter->isArray()) {
224
            return 'array';
225
        }
226
227
        if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
228
            return 'callable';
229
        }
230
231
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
232
            return (string) $parameter->getType();
233
        }
234
235
        return null;
236
    }
237
238
    private function isVariadic(ReflectionParameter $parameter)
239
    {
240
        return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
241
    }
242
243
    private function isNullable(ReflectionParameter $parameter)
244
    {
245
        return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
246
    }
247
248
    private function getParameterClassName(ReflectionParameter $parameter)
249
    {
250
        try {
251
            return $parameter->getClass() ? $parameter->getClass()->getName() : null;
252
        } catch (\ReflectionException $e) {
253
            preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
254
255
            return isset($matches[1]) ? $matches[1] : null;
256
        }
257
    }
258
}
259