ExtendedReflectionClass::hasUseStatement()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 3
nop 1
1
<?php
2
3
namespace Atreyu;
4
5
class ExtendedReflectionClass extends \ReflectionClass
6
{
7
    /**
8
     * Array of use statements for class.
9
     *
10
     * @var array
11
     */
12
    protected $useStatements = [];
13
14
    /**
15
     * Check if use statements have been parsed.
16
     *
17
     * @var boolean
18
     */
19
    protected $useStatementsParsed = false;
20
21
    /**
22
     * Parse class file and get use statements from current namespace.
23
     *
24
     * @return array
25
     * @throws \RuntimeException
26
     */
27
    protected function parseUseStatements()
28
    {
29
        if ($this->useStatementsParsed) {
30
            return $this->useStatements;
31
        }
32
33
        if (!$this->isUserDefined()) {
34
            throw new \RuntimeException('Must parse use statements from user defined classes.');
35
        }
36
37
        $source                    = $this->readFileSource();
38
        $this->useStatements       = $this->tokenizeSource($source);
39
        $this->useStatementsParsed = true;
40
41
        return $this->useStatements;
42
    }
43
44
    /**
45
     * Read file source up to the line where our class is defined.
46
     *
47
     * @return string
48
     */
49
    private function readFileSource()
50
    {
51
        $file   = fopen($this->getFileName(), 'r');
52
        $line   = 0;
53
        $source = '';
54
55
        while (!feof($file)) {
56
            ++$line;
57
58
            if ($line >= $this->getStartLine()) {
59
                break;
60
            }
61
62
            $source .= fgets($file);
63
        }
64
65
        fclose($file);
66
67
        return $source;
68
    }
69
70
    /**
71
     * Parse the use statements from read source by
72
     * tokenizing and reading the tokens. Returns
73
     * an array of use statements and aliases.
74
     *
75
     * @param string $source
76
     *
77
     * @return array
78
     */
79
    private function tokenizeSource($source)
80
    {
81
        $tokens = token_get_all($source);
82
83
        $builtNamespace    = '';
84
        $buildingNamespace = false;
85
        $matchedNamespace  = false;
86
87
        $useStatements = [];
88
        $record        = false;
89
        $currentUse    = [
90
            'class' => '',
91
            'as'    => ''
92
        ];
93
94
        foreach ($tokens as $token) {
95
96
            if ($token[0] === T_NAMESPACE) {
97
                $buildingNamespace = true;
98
99
                if ($matchedNamespace) {
100
                    break;
101
                }
102
            }
103
104
            if ($buildingNamespace) {
105
106
                if ($token === ';') {
107
                    $buildingNamespace = false;
108
                    continue;
109
                }
110
111
                switch ($token[0]) {
112
113
                    case T_STRING:
114
                    case T_NS_SEPARATOR:
115
                        $builtNamespace .= $token[1];
116
                        break;
117
                }
118
119
                continue;
120
            }
121
122
            if ($token === ';' || !is_array($token)) {
123
124
                if ($record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
125
                    $useStatements[] = $currentUse;
126
                    $record          = false;
127
                    $currentUse      = [
128
                        'class' => '',
129
                        'as'    => ''
130
                    ];
131
                }
132
133
                continue;
134
            }
135
136
            if ($token[0] === T_CLASS) {
137
                break;
138
            }
139
140
            if (strcasecmp($builtNamespace, $this->getNamespaceName()) === 0) {
141
                $matchedNamespace = true;
142
            }
143
144
            if ($matchedNamespace) {
145
146
                if ($token[0] === T_USE) {
147
                    $record = 'class';
148
                }
149
150
                if ($token[0] === T_AS) {
151
                    $record = 'as';
152
                }
153
154
                if ($record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
155
                    switch ($token[0]) {
156
157
                        case T_STRING:
158
                        case T_NS_SEPARATOR:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
159
160
                            if ($record) {
161
                                $currentUse[$record] .= $token[1];
162
                            }
163
164
                            break;
165
                    }
166
                }
167
            }
168
169
            if ($token[2] >= $this->getStartLine()) {
170
                break;
171
            }
172
        }
173
174
        // Make sure the as key has the name of the class even
175
        // if there is no alias in the use statement.
176
        foreach ($useStatements as $k => $useStatement) {
177
178
            $reflection = new \ReflectionClass($useStatement['class']);
179
180
            $useStatements[$reflection->getShortName()] = $useStatement['class'];
181
182
            unset($useStatements[$k]);
183
        }
184
185
        return $useStatements;
186
    }
187
188
    /**
189
     * Return array of use statements from class.
190
     *
191
     * @return array
192
     */
193
    public function getUseStatements()
194
    {
195
        return $this->parseUseStatements();
196
    }
197
198
    /**
199
     * Check if class is using a class or an alias of a class.
200
     *
201
     * @param string $class
202
     *
203
     * @return boolean
204
     */
205
    public function hasUseStatement($class)
206
    {
207
        foreach ($this->parseUseStatements() as $shortName => $fqcn) {
208
209
            if (($class === $shortName) || ($class === $fqcn)) {
210
                return true;
211
            }
212
        }
213
214
        return false;
215
    }
216
}
217