MethodParser::run()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 18
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace bultonFr\MethodsHeaderGenerator;
4
5
use \phpDocumentor\Reflection\DocBlockFactory;
6
use \phpDocumentor\Reflection\DocBlock\Tag;
7
8
/**
9
 * Parse a method to find all parameters infos and return infos
10
 */
11
class MethodParser
12
{
13
    /**
14
     * @var \bultonFr\MethodsHeaderGenerator\ClassParser $classParser The class
15
     * parser instance corresponding to the class where is the current method
16
     */
17
    protected $classParser;
18
    
19
    /**
20
     * @var \ReflectionMethod $reflection The reflection instance who describe
21
     * the method
22
     */
23
    protected $reflection;
24
    
25
    /**
26
     * @var string $name The method name
27
     */
28
    protected $name;
29
    
30
    /**
31
     * @var \ReflectionParameter[] $reflectionParamsList The list of all
32
     * parameter find by reflection system
33
     */
34
    protected $reflectionParamsList;
35
    
36
    /**
37
     * @var \stdClass[] $params List of all method parameters found and parser
38
     */
39
    protected $params = [];
40
    
41
    /**
42
     * @var \stdClass $return Infos about the method returned data
43
     */
44
    protected $return;
45
    
46
    /**
47
     * Construct
48
     * Populate properties and get all parameters from reflection system.
49
     * 
50
     * @param \bultonFr\MethodsHeaderGenerator\ClassParser $classParser
51
     * @param \ReflectionMethod $method
52
     */
53
    public function __construct(
54
        ClassParser $classParser,
55
        \ReflectionMethod $method
56
    )  {
57
        $this->classParser          = $classParser;
58
        $this->reflection           = $method;
59
        $this->name                 = $method->name;
60
        $this->reflectionParamsList = $this->reflection->getParameters();
61
        $this->return               = (object) [
62
            'type' => '???'
63
        ];
64
    }
65
    
66
    /**
67
     * Getter accessor to property classParser
68
     * 
69
     * @return \bultonFr\MethodsHeaderGenerator\ClassParser
70
     */
71
    public function getClassParser()
72
    {
73
        return $this->classParser;
74
    }
75
76
    /**
77
     * Getter accessor to property reflection
78
     * 
79
     * @return \ReflectionMethod
80
     */
81
    public function getReflection()
82
    {
83
        return $this->reflection;
84
    }
85
    
86
    /**
87
     * Getter accessor to property name
88
     * 
89
     * @return string
90
     */
91
    public function getName()
92
    {
93
        return $this->name;
94
    }
95
    
96
    /**
97
     * Getter accessor to property reflectionParamsList
98
     * 
99
     * @return \ReflectionParameter[]
100
     */
101
    public function getReflectionParamsList()
102
    {
103
        return $this->reflectionParamsList;
104
    }
105
106
    /**
107
     * Getter accessor to property params
108
     * 
109
     * @return \stdClass[]
110
     */
111
    public function getParams()
112
    {
113
        return $this->params;
114
    }
115
116
    /**
117
     * Getter accessor to property return
118
     * 
119
     * @return \stdClass
120
     */
121
    public function getReturn()
122
    {
123
        return $this->return;
124
    }
125
    
126
    /**
127
     * Run the method analyse to find all about parameters and returned value
128
     * 
129
     * @return void
130
     */
131
    public function run()
132
    {
133
        $methodDocBlock = $this->reflection->getDocComment();
134
        if ($methodDocBlock === false) {
135
            return;
136
        }
137
        
138
        $docBlockFactory = DocBlockFactory::createInstance();
139
        $docBlock        = $docBlockFactory->create($methodDocBlock);
0 ignored issues
show
Bug introduced by
It seems like $methodDocBlock can also be of type true; however, parameter $docblock of phpDocumentor\Reflection\DocBlockFactory::create() does only seem to accept object|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
        $docBlock        = $docBlockFactory->create(/** @scrutinizer ignore-type */ $methodDocBlock);
Loading history...
140
        $tagsList        = $docBlock->getTags();
141
        
142
        $this->return->type = null;
143
        
144
        foreach ($tagsList as $tagInfos) {
145
            $this->analyseTag($tagInfos);
146
        }
147
148
        $this->checkReturnType();
149
    }
150
    
151
    /**
152
     * Analyse all tag find into the class docBlock
153
     * 
154
     * @param \phpDocumentor\Reflection\DocBlock\Tag $tagInfos The tag instance
155
     * 
156
     * @return void
157
     */
158
    protected function analyseTag(Tag $tagInfos)
159
    {
160
        if ($tagInfos->getName() === 'param') {
161
            $this->tagParam($tagInfos);
162
        } elseif ($tagInfos->getName() === 'return') {
163
            $this->tagReturn($tagInfos);
164
        }
165
    }
166
    
167
    /**
168
     * Parse the tag @param and get datas from it
0 ignored issues
show
Bug introduced by
The type bultonFr\MethodsHeaderGenerator\get was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
169
     * 
170
     * @param \phpDocumentor\Reflection\DocBlock\Tag $tagInfos The tag instance
171
     * 
172
     * @return void
173
     */
174
    protected function tagParam(Tag $tagInfos)
175
    {
176
        $parameterIndex = count($this->params);
177
        $reflParamInfos = $this->reflectionParamsList[$parameterIndex];
178
179
        $paramInfos = (object) [
180
            'type'         => $tagInfos->getType()->__toString(),
181
            'name'         => $tagInfos->getVariableName(),
182
            'optional'     => $reflParamInfos->isOptional(),
183
            'defaultValue' => null,
184
            'passedByRef'  => $reflParamInfos->isPassedByReference(),
185
            'variadic'     => $reflParamInfos->isVariadic()
186
        ];
187
        
188
        if (empty($paramInfos->type)) {
189
            $paramInfos->type = $reflParamInfos->getType();
190
        }
191
        
192
        if (empty($paramInfos->name)) {
193
            $paramInfos->name = $reflParamInfos->name;
194
        }
195
196
        if ($paramInfos->optional === true && $paramInfos->variadic === false) {
197
            $paramInfos->defaultValue = $reflParamInfos->getDefaultValue();
198
        }
199
200
        $this->params[] = $paramInfos;
201
    }
202
    
203
    /**
204
     * Parse the tag @return and get datas from it
0 ignored issues
show
Bug introduced by
The type bultonFr\MethodsHeaderGenerator\and was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
205
     * 
206
     * @param \phpDocumentor\Reflection\DocBlock\Tag $tagInfos The tag instance
207
     * 
208
     * @return void
209
     */
210
    protected function tagReturn(Tag $tagInfos)
211
    {
212
        $type = $tagInfos->getType()->__toString();
213
        if ($type === '$this') {
214
            $type = 'self';
215
        }
216
217
        $this->return->type = $type;
218
    }
219
    
220
    /**
221
     * Check if a return type has been found or not into method docblock
222
     * If no type has been found, we use the type returned by reflection class.
223
     * But if there are nothing too, we search into parent and interface.
224
     * If really nothing is found, we define to "???"
225
     * 
226
     * @return void
227
     */
228
    protected function checkReturnType()
229
    {
230
        if ($this->return->type !== null) {
231
            return;
232
        }
233
        
234
        if ($this->reflection->name === '__construct') {
235
            $this->return->type = 'self';
236
        } elseif ($this->reflection->getReturnType() !== null) {
237
            $this->return->type = $this->reflection->getReturnType();
238
        } else {
239
            $type = $this->searchReturnTypeIntoParentsAndInterfaces();
240
241
            if ($type !== '') {
242
                $this->return->type = $type;
243
            } else {
244
                $this->return->type = '???';
245
            }
246
        }
247
    }
248
    
249
    /**
250
     * PHP Magic method __toString
251
     * Called when a class is treated like a string.
252
     * 
253
     * Display class name, parent class name and interfaces names. After show
254
     * all methods find into docblock with tag @method, and show all methods
255
     * declared into the class.
256
     * 
257
     * @link http://php.net/manual/en/language.oop5.magic.php#object.tostring
258
     * 
259
     * @return string
260
     */
261
    public function __toString()
262
    {
263
        $str = $this->return->type.' '
264
            .$this->obtainVisibility().' '
265
            .$this->obtainMethodName().'(';
266
        
267
        $nbOptionalParams = 0;
268
        foreach ($this->params as $paramIndex => $paramInfos) {
269
            if ($paramIndex > 0) {
270
                $str .= ', ';
271
            }
272
            
273
            if ($paramInfos->optional === true) {
274
                $nbOptionalParams++;
275
                $str .= '[';
276
            }
277
            
278
            $str .= $paramInfos->type.' '
279
                .$this->obtainParamName($paramInfos)
280
                .$this->obtainDefaultValue($paramInfos)
281
            ;
282
        }
283
284
        for ($i = 0; $i < $nbOptionalParams; $i++) {
285
            $str .= ']';
286
        }
287
288
        $str .= ')';
289
        
290
        return $str;
291
    }
292
    
293
    /**
294
     * Find the method visibility
295
     * 
296
     * @return string
297
     */
298
    protected function obtainVisibility()
299
    {
300
        if ($this->reflection->isPublic() === true) {
301
            return 'public';
302
        } elseif ($this->reflection->isProtected() === true) {
303
            return 'protected';
304
        } elseif ($this->reflection->isPrivate() === true) {
305
            return 'private';
306
        }
307
        
308
        return '';
309
    }
310
    
311
    /**
312
     * Obtain the method name with some prefix like static or abstract
313
     * 
314
     * @return string
315
     */
316
    protected function obtainMethodName()
317
    {
318
        $str = '';
319
        
320
        if ($this->reflection->isStatic() === true) {
321
            $str .= 'static ';
322
        }
323
        
324
        if (
325
            $this->reflection->isAbstract() === true &&
326
            $this->classParser->getReflection()->isInterface() === false
327
        ) {
328
            $str .= 'abstract ';
329
        }
330
        
331
        $str .= $this->reflection->name;
332
        
333
        return $str;
334
    }
335
    
336
    /**
337
     * Obtain a parameter name with some prefix like the symbole for a var
338
     * passed by ref or the symbole for a variadic parameter
339
     * 
340
     * @param \stdClass $paramInfos Parsed Informations about the parameter
341
     * 
342
     * @return string
343
     */
344
    protected function obtainParamName(\stdClass $paramInfos)
345
    {
346
        $str = '';
347
        
348
        if ($paramInfos->variadic === true) {
349
            $str .= '...';
350
        }
351
        if ($paramInfos->passedByRef === true) {
352
            $str .= '&';
353
        }
354
355
        $str .= '$'.$paramInfos->name;
356
        return $str;
357
    }
358
    
359
    /**
360
     * Obtain the default value for a parameter.
361
     * The default value obtained from reflection is on its PHP type. So a 
362
     * boolean for default value are really a boolean. So need to convert him
363
     * to string to be displayed. Idem for others types.
364
     * 
365
     * @param \stdClass $paramInfos Parsed Informations about the parameter
366
     * 
367
     * @return string
368
     */
369
    protected function obtainDefaultValue(\stdClass $paramInfos)
370
    {
371
        if ($paramInfos->optional === false) {
372
            return '';
373
        }
374
        
375
        if (is_string($paramInfos->defaultValue)) {
376
            $paramInfos->defaultValue = '"'.$paramInfos->defaultValue.'"';
377
        } elseif (is_bool($paramInfos->defaultValue)) {
378
            if ($paramInfos->defaultValue === true) {
379
                $paramInfos->defaultValue = 'true';
380
            } else {
381
                $paramInfos->defaultValue = 'false';
382
            }
383
        } elseif (is_null($paramInfos->defaultValue)) {
384
            $paramInfos->defaultValue = 'null';
385
        } elseif (is_array($paramInfos->defaultValue)) {
386
            if (count($paramInfos->defaultValue) === 0) {
387
                $paramInfos->defaultValue = 'array()';
388
            } else {
389
                $paramInfos->defaultValue = 'array(???)';
390
            }
391
        } elseif (is_object($paramInfos->defaultValue)) {
392
            //Not sure is possible...
393
            $paramInfos->defaultValue = get_class($paramInfos->defaultValue);
394
        }
395
396
        return '='.(string) $paramInfos->defaultValue;
397
    }
398
    
399
    /**
400
     * When the returned type has not be found into the current class, we need
401
     * to search in parent class and/or interface class implemented.
402
     * 
403
     * @return string
404
     */
405
    protected function searchReturnTypeIntoParentsAndInterfaces()
406
    {
407
        $parent = $this->classParser->getParserParent();
408
        
409
        if (is_object($parent) && isset($parent->getMethods()[$this->name])) {
410
            $method    = $parent->getMethods()[$this->name];
411
            $returnObj = $method->getReturn();
412
413
            return $returnObj->type;
414
        }
415
        
416
        $interfaces = $this->classParser->getParserInterfaces();
417
        foreach ($interfaces as $interfaceParser) {
418
            if (isset($interfaceParser->getMethods()[$this->name])) {
0 ignored issues
show
Bug introduced by
The method getMethods() does not exist on bultonFr\MethodsHeaderGenerator\ProjectParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

418
            if (isset($interfaceParser->/** @scrutinizer ignore-call */ getMethods()[$this->name])) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
419
                $method    = $interfaceParser->getMethods()[$this->name];
420
                $returnObj = $method->getReturn();
421
422
                return $returnObj->type;
423
            }
424
        }
425
        
426
        return '';
427
    }
428
}
429