Completed
Push — master ( ad39fb...287095 )
by Chuck
10s
created

ContextFactory::createFromReflector()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 9.1595

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
ccs 5
cts 9
cp 0.5556
rs 8.5125
cc 6
eloc 12
nc 6
nop 1
crap 9.1595
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 4
     */
39
    public function createFromReflector(\Reflector $reflector): Context
40 4
    {
41
        if ($reflector instanceof \ReflectionClass) {
42
            return $this->createFromReflectionClass($reflector);
43
        }
44 4
45 4
        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 4
61
        throw new UnexpectedValueException('Unhandled \Reflector instance given:  ' . get_class($reflector));
62 4
    }
63 4
64
    private function createFromReflectionParameter(\ReflectionParameter $parameter): Context
65 4
    {
66 2
        return $this->createFromReflectionClass($parameter->getDeclaringClass());
67
    }
68
69 2
    private function createFromReflectionMethod(\ReflectionMethod $method): Context
70
    {
71
        return $this->createFromReflectionClass($method->getDeclaringClass());
72
    }
73
74
    private function createFromReflectionProperty(\ReflectionProperty $property): Context
75
    {
76
        return $this->createFromReflectionClass($property->getDeclaringClass());
77
    }
78
79
    private function createFromReflectionClassConstant(\ReflectionClassConstant $constant): Context
80
    {
81
        return $this->createFromReflectionClass($constant->getDeclaringClass());
82 6
    }
83
84 6
    private function createFromReflectionClass(\ReflectionClass $class): Context
85 6
    {
86 6
        $fileName = $class->getFileName();
87 6
        $namespace = $class->getNamespaceName();
88
89 6
        if (is_string($fileName) && file_exists($fileName)) {
90 6
            return $this->createForNamespace($namespace, file_get_contents($fileName));
91 6
        }
92 6
93 6
        return new Context($namespace, []);
94 6
    }
95
96
    /**
97
     * Build a Context for a namespace in the provided file contents.
98 6
     *
99 6
     * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes.
100 6
     * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace.
101 6
     *
102 6
     * @see Context for more information on Contexts.
103 6
     *
104 6
     * @return Context
105 6
     */
106
    public function createForNamespace($namespace, $fileContents)
107
    {
108 6
        $namespace = trim($namespace, '\\');
109
        $useStatements = [];
110
        $currentNamespace = '';
111 6
        $tokens = new \ArrayIterator(token_get_all($fileContents));
112 6
113
        while ($tokens->valid()) {
114
            switch ($tokens->current()[0]) {
115 6
                case T_NAMESPACE:
116
                    $currentNamespace = $this->parseNamespace($tokens);
117 6
                    break;
118 6
                case T_CLASS:
119 4
                    // Fast-forward the iterator through the class so that any
120 4
                    // T_USE tokens found within are skipped - these are not
121
                    // valid namespace use statements so should be ignored.
122 4
                    $braceLevel = 0;
123
                    $firstBraceFound = false;
124
                    while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
125 6
                        if ($tokens->current() === '{'
126
                            || $tokens->current()[0] === T_CURLY_OPEN
127
                            || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
128 6
                            if (!$firstBraceFound) {
129
                                $firstBraceFound = true;
130
                            }
131
132
                            ++$braceLevel;
133
                        }
134
135
                        if ($tokens->current() === '}') {
136 6
                            --$braceLevel;
137
                        }
138
139 6
                        $tokens->next();
140
                    }
141 6
                    break;
142 6
                case T_USE:
143
                    if ($currentNamespace === $namespace) {
144 6
                        $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
145 6
                    }
146
                    break;
147
            }
148 6
149
            $tokens->next();
150
        }
151
152
        return new Context($namespace, $useStatements);
153
    }
154
155
    /**
156 4
     * Deduce the name from tokens when we are at the T_NAMESPACE token.
157
     *
158 4
     * @return string
159 4
     */
160
    private function parseNamespace(\ArrayIterator $tokens)
161 4
    {
162 4
        // skip to the first string or namespace separator
163
        $this->skipToNextStringOrNamespaceSeparator($tokens);
164 4
165 4
        $name = '';
166 4
        while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR)
167 4
        ) {
168
            $name .= $tokens->current()[1];
169
            $tokens->next();
170
        }
171 4
172
        return $name;
173
    }
174
175
    /**
176
     * Deduce the names of all imports when we are at the T_USE token.
177 6
     *
178
     * @return string[]
179 6
     */
180 6
    private function parseUseStatement(\ArrayIterator $tokens)
181
    {
182 6
        $uses = [];
183
        $continue = true;
184
185
        while ($continue) {
186
            $this->skipToNextStringOrNamespaceSeparator($tokens);
187
188
            list($alias, $fqnn) = $this->extractUseStatement($tokens);
189
            $uses[$alias] = $fqnn;
190 4
            if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
191
                $continue = false;
192 4
            }
193 4
        }
194 4
195 4
        return $uses;
196
    }
197 4
198 4
    /**
199
     * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
200
     */
201 4
    private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
202 4
    {
203
        while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) {
204
            $tokens->next();
205 4
        }
206
    }
207
208 4
    /**
209 4
     * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
210
     * a USE statement yet.
211 4
     *
212 4
     * @return array
213
     */
214 4
    private function extractUseStatement(\ArrayIterator $tokens)
215
    {
216
        $result = [''];
217
        while ($tokens->valid()
218 4
            && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR)
219
            && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE)
220
        ) {
221
            if ($tokens->current()[0] === T_AS) {
222
                $result[] = '';
223
            }
224
225
            if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) {
226
                $result[count($result) - 1] .= $tokens->current()[1];
227
            }
228
229
            $tokens->next();
230
        }
231
232
        if (count($result) === 1) {
233
            $backslashPos = strrpos($result[0], '\\');
234
235
            if (false !== $backslashPos) {
236
                $result[] = substr($result[0], $backslashPos + 1);
237
            } else {
238
                $result[] = $result[0];
239
            }
240
        }
241
242
        return array_reverse($result);
243
    }
244
}
245