Completed
Push — php7 ( ab40a7 )
by personal
04:06
created

MethodExtractor::extractVisibility()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
rs 9.2
cc 4
eloc 14
nc 4
nop 3
1
<?php
2
3
/*
4
 * (c) Jean-François Lépine <https://twitter.com/Halleck45>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Hal\Component\OOP\Extractor;
11
use Hal\Component\OOP\Reflected\MethodUsage;
12
use Hal\Component\OOP\Reflected\ReflectedArgument;
13
use Hal\Component\OOP\Reflected\ReflectedClass\ReflectedAnonymousClass;
14
use Hal\Component\OOP\Reflected\ReflectedMethod;
15
use Hal\Component\OOP\Reflected\ReflectedReturn;
16
use Hal\Component\OOP\Resolver\TypeResolver;
17
use Hal\Component\Token\TokenCollection;
18
19
20
/**
21
 * Extracts info about classes in one file
22
 * Remember that one file can contains multiple classes
23
 *
24
 * @author Jean-François Lépine <https://twitter.com/Halleck45>
25
 */
26
class MethodExtractor implements ExtractorInterface {
27
28
    /**
29
     * @var Searcher
30
     */
31
    private $searcher;
32
33
    /**
34
     * Constructor
35
     *
36
     * @param Searcher $searcher
37
     */
38
    public function __construct(Searcher $searcher)
39
    {
40
        $this->searcher = $searcher;
41
    }
42
43
    /**
44
     * Extract method from position
45
     *
46
     * @param int $n
47
     * @param TokenCollection$tokens
48
     * @return ReflectedMethod
49
     * @throws \Exception
50
     */
51
    public function extract(&$n, TokenCollection $tokens)
52
    {
53
        $start = $n;
54
55
        $declaration = $this->searcher->getUnder(array(')'), $n, $tokens);
56
        if(!preg_match('!function\s+(.*)\(\s*(.*)!is', $declaration, $matches)) {
57
            throw new \Exception(sprintf("Closure detected instead of method\nDetails:\n%s", $declaration));
58
        }
59
        list(, $name, $args) = $matches;
60
        $method = new ReflectedMethod($name);
61
62
        // visibility
63
        $this->extractVisibility($method, $p = $start, $tokens); // please keep "p = start"
64
65
        // state
66
        $this->extractState($method, $p = $start, $tokens); // please keep "p = start"
67
68
        $arguments = preg_split('!\s*,\s*!m', $args);
69
        foreach($arguments as $argDecl) {
70
71
            if(0 == strlen($argDecl)) {
72
                continue;
73
            }
74
75
            $elems = preg_split('!([\s=]+)!', $argDecl);
76
            $isRequired = 2 == sizeof($elems, COUNT_NORMAL);
77
78
            if(sizeof($elems, COUNT_NORMAL) == 1) {
79
                list($name, $type) = array_pad($elems, 2, null);
80
            } else {
81
                if('$' == $elems[0][0]) {
82
                    $name = $elems[0];
83
                    $type  = null;
84
                    $isRequired = false;
85
                } else {
86
                    list($type, $name) = array_pad($elems, 2, null);
87
                }
88
            }
89
90
            $argument = new ReflectedArgument($name, $type, $isRequired);
91
            $method->pushArgument($argument);
92
        }
93
94
95
96
        //
97
        // Body
98
        $this->extractContent($method, $n, $tokens);
99
100
        // Tokens
101
        $end = $this->searcher->getPositionOfClosingBrace($n, $tokens);
102
        if($end > 0) {
103
            $method->setTokens($tokens->extract($n, $end));
104
        }
105
106
        //
107
        // Dependencies
108
        $this->extractDependencies($method, $n, $tokens);
109
110
        // returns
111
        $this->extractReturns($method, $p = $start, $tokens);
112
113
        // usage
114
        $this->extractUsage($method);
115
116
117
118
        return $method;
119
    }
120
121
    /**
122
     * Extracts visibility
123
     *
124
     * @param ReflectedMethod $method
125
     * @param $n
126
     * @param TokenCollection $tokens
127
     * @return $this
128
     */
129
    public function extractVisibility(ReflectedMethod $method, $n, TokenCollection $tokens) {
130
        switch(true) {
131
            case $this->searcher->isPrecededBy(T_PRIVATE, $n, $tokens, 4):
132
                $visibility = ReflectedMethod::VISIBILITY_PRIVATE;
133
                break;
134
            case $this->searcher->isPrecededBy(T_PROTECTED, $n, $tokens, 4):
135
                $visibility = ReflectedMethod::VISIBILITY_PROTECTED;
136
                break;
137
        case $this->searcher->isPrecededBy(T_PUBLIC, $n, $tokens, 4):
138
                default:
139
                $visibility = ReflectedMethod::VISIBILITY_PUBLIC;
140
                break;
141
        }
142
        $method->setVisibility($visibility);
143
        return $this;
144
    }
145
146
    /**
147
     * Extracts state
148
     *
149
     * @param ReflectedMethod $method
150
     * @param $n
151
     * @param TokenCollection $tokens
152
     * @return $this
153
     */
154
    public function extractState(ReflectedMethod $method, $n, TokenCollection $tokens) {
155
        if($this->searcher->isPrecededBy(T_STATIC, $n, $tokens, 4)) {
156
            $method->setState(ReflectedMethod::STATE_STATIC);
157
        }
158
        return $this;
159
    }
160
161
    /**
162
     * Extracts content of method
163
     *
164
     * @param ReflectedMethod $method
165
     * @param integer $n
166
     * @param TokenCollection $tokens
167
     * @return $this
168
     */
169
    private function extractContent(ReflectedMethod $method, $n, TokenCollection $tokens) {
170
        $end = $this->searcher->getPositionOfClosingBrace($n, $tokens);
171
        if($end > 0) {
172
            $collection = $tokens->extract($n, $end);
173
            $method->setContent($collection->asString());
174
        }
175
        return $this;
176
    }
177
178
    /**
179
     * Extracts content of method
180
     *
181
     * @param ReflectedMethod $method
182
     * @param integer $n
183
     * @param TokenCollection $tokens
184
     * @return $this
185
     */
186
    private function extractDependencies(ReflectedMethod $method, $n, TokenCollection $tokens) {
187
188
        //
189
        // Object creation
190
        $extractor = new CallExtractor($this->searcher);
191
        $start = $n;
192
        $len = sizeof($tokens, COUNT_NORMAL);
193
        for($i = $start; $i < $len; $i++) {
194
            $token = $tokens[$i];
195
            switch($token->getType()) {
196
                case T_PAAMAYIM_NEKUDOTAYIM:
197
                case T_NEW:
198
                    $call = $extractor->extract($i, $tokens);
199
                    if($call !== 'class') { // anonymous class
200
                        $method->pushDependency($call);
201
                    }
202
                    break;
203
            }
204
        }
205
206
        //
207
        // Parameters in Method API
208
        foreach($method->getArguments() as $argument) {
209
            $name = $argument->getType();
210
            if(!in_array($argument->getType(), array(null, 'array'))) {
211
                $method->pushDependency($name);
212
            }
213
        }
214
215
        return $this;
216
    }
217
218
    /**
219
     * Extract the list of returned values
220
     *
221
     * @param ReflectedMethod $method
222
     * @return $this
223
     */
224
    private function extractReturns(ReflectedMethod $method, $n, TokenCollection $tokens) {
225
226
        $resolver = new TypeResolver();
227
228
        // PHP 7
229
        // we cannot use specific token. The ":" delimiter is a T_STRING token
230
        $following = $this->searcher->getUnder(array('{'), $n, $tokens);
231
        if(preg_match('!:(.*)!', $following, $matches)) {
232
            $type = trim($matches[1]);
233
            if(empty($type)) {
234
                return $this;
235
            }
236
            $return = new ReflectedReturn($type, ReflectedReturn::VALUE_UNKNOW, ReflectedReturn::STRICT_TYPE_HINT);
237
            $method->pushReturn($return);
238
            return $this;
239
        }
240
241
        // array of available values based on code
242
        if(preg_match_all('!([\s;]return\s|^return\s+)(.*?);!', $method->getContent(), $matches)) {
243
            foreach($matches[2] as $m) {
244
                $value = trim($m, ";\t\n\r\0\x0B");
245
                $return = new ReflectedReturn($resolver->resolve($m), $value, ReflectedReturn::ESTIMATED_TYPE_HINT);
246
                $method->pushReturn($return);
247
            }
248
        }
249
        return $this;
250
    }
251
252
    /**
253
     * Extracts usage of method
254
     *
255
     * @param ReflectedMethod $method
256
     * @return $this
257
     */
258
    private function extractUsage(ReflectedMethod $method) {
259
        $tokens = $method->getTokens();
260
        $codes = $values = array();
261
        foreach($tokens as $token) {
262
            if(in_array($token->getType(), array(T_WHITESPACE, T_BOOL_CAST, T_INT_CAST, T_STRING_CAST, T_DOUBLE_CAST, T_OBJECT_CAST))) {
263
                continue;
264
            }
265
            array_push($codes, $token->getType());
266
            array_push($values, $token->getValue());
267
        }
268
        switch(true) {
269
            case preg_match('!^(get)|(is)|(has).*!',$method->getName()) && $codes == array(T_RETURN, T_VARIABLE, T_OBJECT_OPERATOR, T_STRING, T_STRING):
270
                $method->setUsage(MethodUsage::USAGE_GETTER);
271
                break;
272
            // basic setter
273
            case preg_match('!^set.*!',$method->getName()) && $codes == array(T_VARIABLE, T_OBJECT_OPERATOR,T_STRING,T_STRING, T_VARIABLE, T_STRING) && $values[3] == '=':
274
            // fluent setter
275
            case preg_match('!^set.*!',$method->getName()) && $codes == array(T_VARIABLE, T_OBJECT_OPERATOR,T_STRING,T_STRING, T_VARIABLE, T_STRING, T_RETURN, T_VARIABLE, T_STRING)
276
                && $values[3] == '=' && $values[7] == '$this':
277
                $method->setUsage(MethodUsage::USAGE_SETTER);
278
                break;
279
            default:
280
                $method->setUsage(MethodUsage::USAGE_UNKNWON);
281
        }
282
        return $this;
283
    }
284
}
285