Completed
Push — master ( f989f4...cd6802 )
by Jaap
03:03
created

ContextFactory   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 82.72%

Importance

Changes 0
Metric Value
wmc 71
lcom 1
cbo 1
dl 0
loc 301
ccs 134
cts 162
cp 0.8272
rs 2.7199
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
B createFromReflector() 0 24 6
A createFromReflectionParameter() 0 7 2
A createFromReflectionMethod() 0 4 1
A createFromReflectionProperty() 0 4 1
A createFromReflectionClassConstant() 0 4 1
A createFromReflectionClass() 0 16 4
C createForNamespace() 0 48 14
A parseNamespace() 0 14 4
A parseUseStatement() 0 16 3
A skipToNextStringOrNamespaceSeparator() 0 6 4
F extractUseStatements() 0 105 31

How to fix   Complexity   

Complex Class

Complex classes like ContextFactory 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 ContextFactory, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2018 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Reflection\Types;
14
15
use UnexpectedValueException;
16
17
/**
18
 * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
19
 *
20
 * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
21
 * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
22
 * Fully Qualified names.
23
 *
24
 * @see Context for more information.
25
 */
26
final class ContextFactory
27
{
28
    /** The literal used at the end of a use statement. */
29
    const T_LITERAL_END_OF_USE = ';';
30
31
    /** The literal used between sets of use statements */
32
    const T_LITERAL_USE_SEPARATOR = ',';
33
34
    /**
35
     * Build a Context given a Class Reflection.
36
     *
37
     * @see Context for more information on Contexts.
38
     */
39 4
    public function createFromReflector(\Reflector $reflector): Context
40
    {
41 4
        if ($reflector instanceof \ReflectionClass) {
42 4
            return $this->createFromReflectionClass($reflector);
43
        }
44
45
        if ($reflector instanceof \ReflectionParameter) {
46
            return $this->createFromReflectionParameter($reflector);
47
        }
48
49
        if ($reflector instanceof \ReflectionMethod) {
50
            return $this->createFromReflectionMethod($reflector);
51
        }
52
53
        if ($reflector instanceof \ReflectionProperty) {
54
            return $this->createFromReflectionProperty($reflector);
55
        }
56
57
        if ($reflector instanceof \ReflectionClassConstant) {
0 ignored issues
show
Bug introduced by
The class ReflectionClassConstant does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
58
            return $this->createFromReflectionClassConstant($reflector);
59
        }
60
61
        throw new UnexpectedValueException('Unhandled \Reflector instance given:  ' . get_class($reflector));
62
    }
63
64
    private function createFromReflectionParameter(\ReflectionParameter $parameter): Context
65
    {
66
        $class = $parameter->getDeclaringClass();
67
        if ($class) {
68
            return $this->createFromReflectionClass($class);
69
        }
70
    }
71
72
    private function createFromReflectionMethod(\ReflectionMethod $method): Context
73
    {
74
        return $this->createFromReflectionClass($method->getDeclaringClass());
75
    }
76
77
    private function createFromReflectionProperty(\ReflectionProperty $property): Context
78
    {
79
        return $this->createFromReflectionClass($property->getDeclaringClass());
80
    }
81
82
    private function createFromReflectionClassConstant(\ReflectionClassConstant $constant): Context
83
    {
84
        return $this->createFromReflectionClass($constant->getDeclaringClass());
85
    }
86
87 4
    private function createFromReflectionClass(\ReflectionClass $class): Context
88
    {
89 4
        $fileName = $class->getFileName();
90 4
        $namespace = $class->getNamespaceName();
91
92 4
        if (is_string($fileName) && file_exists($fileName)) {
93 2
            $contents = file_get_contents($fileName);
94 2
            if (false === $contents) {
95
                throw new \RuntimeException('Unable to read file "' . $fileName . '"');
96
            }
97
98 2
            return $this->createForNamespace($namespace, $contents);
99
        }
100
101 2
        return new Context($namespace, []);
102
    }
103
104
    /**
105
     * Build a Context for a namespace in the provided file contents.
106
     *
107
     * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes.
108
     * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace.
109
     *
110
     * @see Context for more information on Contexts.
111
     *
112
     * @return Context
113
     */
114 6
    public function createForNamespace($namespace, $fileContents)
115
    {
116 6
        $namespace = trim($namespace, '\\');
117 6
        $useStatements = [];
118 6
        $currentNamespace = '';
119 6
        $tokens = new \ArrayIterator(token_get_all($fileContents));
120
121 6
        while ($tokens->valid()) {
122 6
            switch ($tokens->current()[0]) {
123 6
                case T_NAMESPACE:
124 6
                    $currentNamespace = $this->parseNamespace($tokens);
125 6
                    break;
126 6
                case T_CLASS:
127
                    // Fast-forward the iterator through the class so that any
128
                    // T_USE tokens found within are skipped - these are not
129
                    // valid namespace use statements so should be ignored.
130 6
                    $braceLevel = 0;
131 6
                    $firstBraceFound = false;
132 6
                    while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
133 6
                        if ($tokens->current() === '{'
134 6
                            || $tokens->current()[0] === T_CURLY_OPEN
135 6
                            || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
136 6
                            if (!$firstBraceFound) {
137 6
                                $firstBraceFound = true;
138
                            }
139
140 6
                            ++$braceLevel;
141
                        }
142
143 6
                        if ($tokens->current() === '}') {
144 6
                            --$braceLevel;
145
                        }
146
147 6
                        $tokens->next();
148
                    }
149 6
                    break;
150 6
                case T_USE:
151 4
                    if ($currentNamespace === $namespace) {
152 4
                        $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
153
                    }
154 4
                    break;
155
            }
156
157 6
            $tokens->next();
158
        }
159
160 6
        return new Context($namespace, $useStatements);
161
    }
162
163
    /**
164
     * Deduce the name from tokens when we are at the T_NAMESPACE token.
165
     *
166
     * @return string
167
     */
168 6
    private function parseNamespace(\ArrayIterator $tokens)
169
    {
170
        // skip to the first string or namespace separator
171 6
        $this->skipToNextStringOrNamespaceSeparator($tokens);
172
173 6
        $name = '';
174 6
        while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR)
175
        ) {
176 6
            $name .= $tokens->current()[1];
177 6
            $tokens->next();
178
        }
179
180 6
        return $name;
181
    }
182
183
    /**
184
     * Deduce the names of all imports when we are at the T_USE token.
185
     *
186
     * @return string[]
187
     */
188 4
    private function parseUseStatement(\ArrayIterator $tokens)
189
    {
190 4
        $uses = [];
191 4
        $continue = true;
192
193 4
        while ($continue) {
194 4
            $this->skipToNextStringOrNamespaceSeparator($tokens);
195
196 4
            $uses = array_merge($uses, $this->extractUseStatements($tokens));
197 4
            if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
198 4
                $continue = false;
199
            }
200
        }
201
202 4
        return $uses;
203
    }
204
205
    /**
206
     * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
207
     */
208 6
    private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
209
    {
210 6
        while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) {
211 6
            $tokens->next();
212
        }
213 6
    }
214
215
    /**
216
     * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
217
     * a USE statement yet. This will return a key/value array of the alias => namespace.
218
     *
219
     * @return array
220
     */
221 4
    private function extractUseStatements(\ArrayIterator $tokens)
222
    {
223 4
        $extractedUseStatements = [];
224 4
        $groupedNs = '';
225 4
        $currentNs = '';
226 4
        $currentAlias = null;
227 4
        $state = "start";
228
229 4
        $i = 0;
230 4
        while ($tokens->valid()) {
231 4
            $i += 1;
232 4
            $currentToken = $tokens->current();
233 4
            $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0];
234 4
            $tokenValue = is_string($currentToken) ? null : $currentToken[1];
235
            switch ($state) {
236 4
                case "start":
237
                    switch ($tokenId) {
238 4
                        case T_STRING:
239 4
                        case T_NS_SEPARATOR:
240 4
                            $currentNs .= $tokenValue;
241 4
                            break;
242 4
                        case T_CURLY_OPEN:
243 4
                        case '{':
244 4
                            $state = 'grouped';
245 4
                            $groupedNs = $currentNs;
246 4
                            break;
247 4
                        case T_AS:
248 4
                            $state = 'start-alias';
249 4
                            break;
250 4
                        case self::T_LITERAL_USE_SEPARATOR:
251 4
                        case self::T_LITERAL_END_OF_USE:
252 4
                            $state = 'end';
253 4
                            break;
254
                        default:
255 4
                            break;
256
                    }
257 4
                    break;
258 4
                case "start-alias":
259
                    switch ($tokenId) {
260 4
                        case T_STRING:
261 4
                            $currentAlias .= $tokenValue;
262 4
                            break;
263 4
                        case self::T_LITERAL_USE_SEPARATOR:
264 4
                        case self::T_LITERAL_END_OF_USE:
265 4
                            $state = 'end';
266 4
                            break;
267
                        default:
268 4
                            break;
269
                    }
270 4
                    break;
271 4
                case "grouped":
272
                    switch ($tokenId) {
273 4
                        case T_STRING:
274 4
                        case T_NS_SEPARATOR:
275 4
                            $currentNs .= $tokenValue;
276 4
                            break;
277 4
                        case T_AS:
278 4
                            $state = 'grouped-alias';
279 4
                            break;
280 4
                        case self::T_LITERAL_USE_SEPARATOR:
281 4
                            $state = 'grouped';
282 4
                            $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
283 4
                            $currentNs = $groupedNs;
284 4
                            $currentAlias = null;
285 4
                            break;
286 4
                        case self::T_LITERAL_END_OF_USE:
287
                            $state = 'end';
288
                            break;
289
                        default:
290 4
                            break;
291
                    }
292 4
                    break;
293 4
                case "grouped-alias":
294
                    switch ($tokenId) {
295 4
                        case T_STRING:
296 4
                            $currentAlias .= $tokenValue;
297 4
                            break;
298 4
                        case self::T_LITERAL_USE_SEPARATOR:
299
                            $state = 'grouped';
300
                            $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
301
                            $currentNs = $groupedNs;
302
                            $currentAlias = null;
303
                            break;
304 4
                        case self::T_LITERAL_END_OF_USE:
305 4
                            $state = 'end';
306 4
                            break;
307
                        default:
308 4
                            break;
309
                    }
310
            }
311
312 4
            if ($state == "end") {
313 4
                break;
314
            }
315
316 4
            $tokens->next();
317
        }
318
319 4
        if ($groupedNs != $currentNs) {
320 4
            $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
321
        }
322
323
324 4
        return $extractedUseStatements;
325
    }
326
}
327