FunctionParser   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 511
Duplicated Lines 7.83 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 12
dl 40
loc 511
rs 2
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getToken() 0 4 1
B getDefinitionListFromTokens() 9 34 7
B getDefinition() 15 31 10
B getDefinitionFromTokens() 0 65 3
B addAncestralAssertions() 0 47 5
D getParameterDefinitionList() 0 84 16
A getFunctionName() 0 15 4
B getFunctionBody() 0 44 11
D getFunctionTokens() 12 66 22
B getFunctionVisibility() 4 21 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\Parser\FunctionParser
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Bernhard Wick <[email protected]>
15
 * @copyright 2015 TechDivision GmbH - <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/doppelgaenger
18
 * @link      http://www.appserver.io/
19
 */
20
21
namespace AppserverIo\Doppelgaenger\Parser;
22
23
use AppserverIo\Doppelgaenger\Entities\Definitions\ParameterDefinition;
24
use AppserverIo\Doppelgaenger\Entities\Definitions\Structure;
25
use AppserverIo\Doppelgaenger\Entities\Joinpoint;
26
use AppserverIo\Doppelgaenger\Entities\Lists\FunctionDefinitionList;
27
use AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition;
28
use AppserverIo\Doppelgaenger\Entities\Lists\ParameterDefinitionList;
29
use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords;
30
use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Ensures;
31
use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Requires;
32
use AppserverIo\Doppelgaenger\Utils\Parser;
33
34
/**
35
 * This class implements a parser to find all useful information in function definitions
36
 *
37
 * @author    Bernhard Wick <[email protected]>
38
 * @copyright 2015 TechDivision GmbH - <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/appserver-io/doppelgaenger
41
 * @link      http://www.appserver.io/
42
 */
43
class FunctionParser extends AbstractParser
44
{
45
46
    /**
47
     * Token representing the structure this parser is used for
48
     *
49
     * @var integer TOKEN
50
     */
51
    const TOKEN = T_FUNCTION;
52
53
    /**
54
     * Will return the token representing the construct
55
     *
56
     * @return integer
57
     */
58
    public function getToken()
59
    {
60
        return self::TOKEN;
61
    }
62
63
    /**
64
     * Will return a list of function definition objects extracted from a given token array
65
     *
66
     * @param array   $tokens       The token array
67
     * @param boolean $getRecursive Do we have to get the ancestral contents as well?
68
     *
69
     * @return boolean|\AppserverIo\Doppelgaenger\Entities\Lists\FunctionDefinitionList
70
     */
71
    public function getDefinitionListFromTokens(array $tokens, $getRecursive = true)
72
    {
73
        // First of all we need to get the function tokens
74
        $tokens = $this->getFunctionTokens($tokens);
75
76
        // Did we get something valuable?
77
        $functionDefinitionList = new FunctionDefinitionList();
78
        $tokenCount = count($tokens);
79
        if ($tokens === false) {
80
            return false;
81
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
82 View Code Duplication
        } elseif ($tokenCount === 1) {
83
            // We got what we came for, or did we?
84
            if (isset($tokens[0])) {
85
                $functionDefinitionList->add($this->getDefinitionFromTokens($tokens[0], $getRecursive));
86
            }
87
88
            return $functionDefinitionList;
89
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
90
        } elseif ($tokenCount > 1) {
91
            // We are still here, but got a function name to look for
92
            foreach ($tokens as $token) {
0 ignored issues
show
Bug introduced by
The expression $tokens of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
93
                try {
94
                    $functionDefinitionList->add($this->getDefinitionFromTokens($token, $getRecursive));
95
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
96
                } catch (\UnexpectedValueException $e) {
97
                    // Just try the next one
98
                    continue;
99
                }
100
            }
101
        }
102
103
        return $functionDefinitionList;
104
    }
105
106
    /**
107
     * Will return a function definition objects for a certain function
108
     *
109
     * @param string  $functionName The name of the function to parse
110
     * @param boolean $getRecursive Do we have to get the ancestral contents as well?
111
     *
112
     * @return boolean|\AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition
113
     */
114
    public function getDefinition($functionName, $getRecursive = true)
115
    {
116
        // First of all we need to get the function tokens
117
        $tokens = $this->getFunctionTokens($this->tokens);
118
        $tokenCount = count($tokens);
119
120
        // Did we get something valuable?
121
        if ($tokens === false) {
122
            return false;
123
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
124 View Code Duplication
        } elseif ($tokenCount === 1) {
125
            // We got what we came for, or did we?
126
            if (isset($tokens[0])) {
127
                return $this->getDefinitionFromTokens($tokens[0], $getRecursive);
128
            }
129
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
130
        } elseif ($tokenCount > 1) {
131
            // We are still here, but got a function name to look for
132 View Code Duplication
            foreach ($tokens as $token) {
0 ignored issues
show
Bug introduced by
The expression $tokens of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
133
                // Now iterate over the array and search for the class we want
134
                for ($i = 0; $i < count($token); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
135
                    if (is_array($token[$i]) && $token[$i] === T_FUNCTION && $token[$i + 2] === $functionName) {
136
                        return $this->getDefinitionFromTokens($token, $getRecursive);
137
                    }
138
                }
139
            }
140
        }
141
142
        // Still here? That sounds bad
143
        return false;
144
    }
145
146
    /**
147
     * Returns a FunctionDefinition from a token array.
148
     *
149
     * This method will use a set of other methods to parse a token array and retrieve any
150
     * possible information from it. This information will be entered into a FunctionDefinition object.
151
     *
152
     * @param array   $tokens       The token array
153
     * @param boolean $getRecursive Do we have to get the ancestral conditions as well?
154
     *
155
     * @return \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition
156
     */
157
    protected function getDefinitionFromTokens(array $tokens, $getRecursive)
158
    {
159
        // First of all we need a new FunctionDefinition to fill
160
        $functionDefinition = new FunctionDefinition();
161
162
        // For our next step we would like to get the doc comment (if any)
163
        $functionDefinition->setDocBlock($this->getDocBlock($tokens, T_FUNCTION));
164
165
        // Get start and end line
166
        $functionDefinition->setStartLine($this->getStartLine($tokens));
167
        $functionDefinition->setEndLine($this->getEndLine($tokens));
168
169
        // Get the function signature
170
        $functionDefinition->setIsFinal($this->hasSignatureToken($tokens, T_FINAL, T_FUNCTION));
171
        $functionDefinition->setIsAbstract($this->hasSignatureToken($tokens, T_ABSTRACT, T_FUNCTION));
172
        $functionDefinition->setVisibility($this->getFunctionVisibility($tokens));
173
        $functionDefinition->setIsStatic($this->hasSignatureToken($tokens, T_STATIC, T_FUNCTION));
174
        $functionDefinition->setName($this->getFunctionName($tokens));
175
        $functionDefinition->setStructureName($this->currentDefinition->getQualifiedName());
176
177
        // Lets also get out parameters
178
        $functionDefinition->setParameterDefinitions($this->getParameterDefinitionList($tokens));
179
180
        // Do we have a private context here? If so we have to tell the annotation parser
181
        $privateContext = false;
182
        if ($functionDefinition->getVisibility() === 'private') {
183
            $privateContext = true;
184
        }
185
186
        // So we got our docBlock, now we can parse the precondition annotations from it
187
        $annotationParser = new AnnotationParser($this->file, $this->config, $this->tokens, $this->currentDefinition);
188
        $functionDefinition->setPreconditions($annotationParser->getConditions(
0 ignored issues
show
Security Bug introduced by
It seems like $annotationParser->getCo...ATION, $privateContext) targeting AppserverIo\Doppelgaenge...Parser::getConditions() can also be of type false; however, AppserverIo\Doppelgaenge...ion::setPreconditions() does only seem to accept object<AppserverIo\Doppe...es\Lists\AssertionList>, did you maybe forget to handle an error condition?
Loading history...
189
            $functionDefinition->getDocBlock(),
190
            Requires::ANNOTATION,
191
            $privateContext
192
        ));
193
194
        // get the advices
195
        $functionDefinition->getPointcutExpressions()->attach($annotationParser->getPointcutExpressions(
196
            $functionDefinition->getDocBlock(),
197
            Joinpoint::TARGET_METHOD,
198
            $functionDefinition->getName()
199
        ));
200
201
        // Does this method require the use of our "old" mechanism?
202
        $functionDefinition->setUsesOld($this->usesKeyword($functionDefinition->getDocBlock(), ReservedKeywords::OLD));
203
204
        // We have to get the body of the function, so we can recreate it
205
        $functionDefinition->setBody($this->getFunctionBody($tokens));
206
207
        // So we got our docBlock, now we can parse the postcondition annotations from it
208
        $functionDefinition->setPostconditions($annotationParser->getConditions(
0 ignored issues
show
Security Bug introduced by
It seems like $annotationParser->getCo...ATION, $privateContext) targeting AppserverIo\Doppelgaenge...Parser::getConditions() can also be of type false; however, AppserverIo\Doppelgaenge...on::setPostconditions() does only seem to accept object<AppserverIo\Doppe...es\Lists\AssertionList>, did you maybe forget to handle an error condition?
Loading history...
209
            $functionDefinition->getDocBlock(),
210
            Ensures::ANNOTATION,
211
            $privateContext
212
        ));
213
214
        // If we have to parse the definition in a recursive manner, we have to get the parent invariants
215
        if ($getRecursive === true) {
216
            // Add all the assertions we might get from ancestral dependencies
217
            $this->addAncestralAssertions($functionDefinition);
218
        }
219
220
        return $functionDefinition;
221
    }
222
223
    /**
224
     * This method will add all assertions any ancestral structures (parent classes, implemented interfaces) might have
225
     * to the passed class definition.
226
     *
227
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition $functionDefinition The function definition
228
     *                                                                                      we are working on
229
     *
230
     * @return void
231
     */
232
    protected function addAncestralAssertions(FunctionDefinition $functionDefinition)
233
    {
234
        $dependencies = $this->currentDefinition->getDependencies();
235
        foreach ($dependencies as $dependency) {
236
            // freshly set the dependency definition to avoid side effects
237
            $dependencyDefinition = null;
0 ignored issues
show
Unused Code introduced by
$dependencyDefinition is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
238
239
            $fileEntry = $this->structureMap->getEntry($dependency);
240
            if (!$fileEntry instanceof Structure) {
241
                // Continue, don't fail as we might have dependencies which are not under Doppelgaenger surveillance
242
                continue;
243
            }
244
245
            // Get the needed parser
246
            $structureParserFactory = new StructureParserFactory();
247
            $parser = $structureParserFactory->getInstance(
248
                $fileEntry->getType(),
249
                $fileEntry->getPath(),
250
                $this->config,
251
                $this->structureMap,
252
                $this->structureDefinitionHierarchy
253
            );
254
255
            // Get the definition
256
            $dependencyDefinition = $parser->getDefinition(
257
                $dependency,
258
                true
259
            );
260
261
            // Get the function definitions of the dependency structure
262
            $dependencyFunctionDefinitions = $dependencyDefinition->getFunctionDefinitions();
263
264
            // If we have a method with the name of the current one we have to get the conditions as ancestrals
265
            if ($dependencyFunctionDefinitions->entryExists($functionDefinition->getName())) {
266
                // Get the definition
267
                $dependencyFunctionDefinition = $dependencyFunctionDefinitions->get($functionDefinition->getName());
268
                // If the ancestral function uses the old keyword we have to do too
269
                if ($dependencyFunctionDefinition->usesOld() !== false) {
270
                    $functionDefinition->setUsesOld(true);
271
                }
272
273
                // Get the conditions
274
                $functionDefinition->setAncestralPreconditions($dependencyFunctionDefinition->getAllPreconditions(true));
275
                $functionDefinition->setAncestralPostconditions($dependencyFunctionDefinition->getAllPostconditions(true));
276
            }
277
        }
278
    }
279
280
    /**
281
     * Will return a list of parameter definition objects extracted from a given token array
282
     *
283
     * @param array $tokens The token array
284
     *
285
     * @return \AppserverIo\Doppelgaenger\Entities\Lists\ParameterDefinitionList
286
     *
287
     * TODO Does this have to be this long?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
288
     */
289
    public function getParameterDefinitionList(
290
        array $tokens
291
    ) {
292
        // Check the tokens
293
        $parameterString = '';
294
        $parameterDefinitionList = new ParameterDefinitionList();
295
        $tokenCount = count($tokens);
296
        for ($i = 0; $i < $tokenCount; $i++) {
297
            // If we got the function definition, no scan everything from the first ( to the next )
298
            if ($tokens[$i][0] === T_FUNCTION) {
299
                $bracketPassed = null;
300
                for ($j = $i; $j < $tokenCount; $j++) {
301
                    // If we got the function definition, no scan everything from the first ( to the closing )
302
                    if ($tokens[$j] === '(') {
303
                        if ($bracketPassed === null) {
304
                            $bracketPassed = 1;
305
                            // We do not want to get this token as well.
306
                            continue;
307
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
308
                        } else {
309
                            $bracketPassed++;
310
                        }
311
                    }
312
313
                    // We got A closing bracket, decrease the counter
314
                    if ($tokens[$j] === ')') {
315
                        $bracketPassed--;
316
                    }
317
318
                    if ($bracketPassed > 0 && $bracketPassed !== null) {
319
                        // Collect what we get
320
                        if (is_array($tokens[$j])) {
321
                            $parameterString .= $tokens[$j][1];
322
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
323
                        } else {
324
                            $parameterString .= $tokens[$j];
325
                        }
326
                    } elseif ($bracketPassed !== null) {
327
                        // If we got the closing bracket we can leave both loops
328
                        break 2;
329
                    }
330
                }
331
            }
332
        }
333
334
        // Now lets analyse what we got
335
        $parameterStrings = explode(',', $parameterString);
336
        $parserUtils = new Parser();
337
        foreach ($parameterStrings as $key => $param) {
338
            if ($parserUtils->getBracketCount($param, '(') > 0) {
339
                $param = $param . ', ' . $parameterStrings[$key + 1];
340
                unset($parameterStrings[$key + 1]);
341
            }
342
343
            $param = trim($param);
344
            $paramPieces = explode('$', $param);
345
346
            // Get a new ParameterDefinition
347
            $parameterDefinition = new ParameterDefinition();
348
349
            // we either get one or two pieces
350
            if (count($paramPieces) === 1) {
351
                continue;
352
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
353
            } elseif (count($paramPieces) === 2) {
354
                $parameterDefinition->type = trim($paramPieces[0]);
355
356
                // We might have an overload going on
357
                $nameArray = explode('=', $paramPieces[1]);
358
                $parameterDefinition->name = '$' . trim($nameArray[0]);
359
360
                // check if we got a default value for overloading
361
                if (isset($nameArray[1])) {
362
                    unset($nameArray[0]);
363
                    $parameterDefinition->defaultValue = trim(implode('=', $nameArray));
364
                }
365
            }
366
367
            // Add the definition to the list
368
            $parameterDefinitionList->add($parameterDefinition);
369
        }
370
371
        return $parameterDefinitionList;
372
    }
373
374
    /**
375
     * Will return the name of the function passed as a token array
376
     *
377
     * @param array $tokens The token array
378
     *
379
     * @return string
380
     */
381
    public function getFunctionName(
382
        array $tokens
383
    ) {
384
        // Check the tokens
385
        $functionName = '';
386
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
387
            // If we got the function name
388
            if ($tokens[$i][0] === T_FUNCTION && $tokens[$i + 2][0] === T_STRING) {
389
                $functionName = $tokens[$i + 2][1];
390
            }
391
        }
392
393
        // Return what we did or did not found
394
        return $functionName;
395
    }
396
397
    /**
398
     * Will return the body of the function passed as a token array
399
     *
400
     * @param array $tokens The token array
401
     *
402
     * @return string
403
     */
404
    public function getFunctionBody(
405
        array $tokens
406
    ) {
407
        // We will iterate over the token array and collect everything
408
        // from the first opening curly bracket until the last
409
        $functionBody = '';
410
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
411
            // If we passed the function token
412
            if ($tokens[$i][0] === T_FUNCTION) {
413
                $passedFunction = true;
414
            }
415
416
            // If we got the curly bracket that opens the function
417
            if ($tokens[$i] === '{' && $passedFunction === true) {
0 ignored issues
show
Bug introduced by
The variable $passedFunction does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
418
                // Get everything until we reach the closing bracket
419
                $bracketCounter = 1;
420
                for ($j = $i + 1; $j < count($tokens); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
421
                    // We have to count brackets. When they are even again we will break.
422
                    if ($tokens[$j] === '{' || $tokens[$j][0] === T_CURLY_OPEN) {
423
                        $bracketCounter++;
424
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
425
                    } elseif ($tokens[$j] === '}') {
426
                        $bracketCounter--;
427
                    }
428
429
                    // Do we have an even amount of brackets yet?
430
                    if ($bracketCounter === 0) {
431
                        return $functionBody;
432
                    }
433
434
                    // Collect what we get
435
                    if (is_array($tokens[$j])) {
436
                        $functionBody .= $tokens[$j][1];
437
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
438
                    } else {
439
                        $functionBody .= $tokens[$j];
440
                    }
441
                }
442
            }
443
        }
444
445
        // Return what we did or did not found
446
        return $functionBody;
447
    }
448
449
    /**
450
     * Will extract tokens belonging to one function (and one function only)
451
     *
452
     * @param array $tokens The token array
453
     *
454
     * @return array|boolean
455
     */
456
    protected function getFunctionTokens(array $tokens)
457
    {
458
        // Iterate over all the tokens and filter the different function portions out
459
        $result = array();
460
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
461
            // If we got a function keyword, we have to check how far the function extends,
462
            // then copy the array within that bounds, but first of all we have to check if we got
463
            // a function name.
464
            // Otherwise anonymous functions will make us go crazy.
465
            if (is_array($tokens[$i]) && $tokens[$i][0] === T_FUNCTION &&
466
                !empty($tokens[$i + 2]) && $tokens[$i + 2] !== '(' && $tokens[$i + 1] !== '('
467
            ) {
468
                // The lower bound should be the last semicolon|closing curly bracket|PHP tag before the function
469
                $lowerBound = 0;
470
                for ($j = $i - 1; $j >= 0; $j--) {
471
                    if ($tokens[$j] === ';' || $tokens[$j] === '{' ||
472
                        (is_array($tokens[$j]) && $tokens[$j][0] === T_OPEN_TAG) ||
473
                        $tokens[$j][0] === T_CURLY_OPEN
474
                    ) {
475
                        $lowerBound = $j + 1;
476
                        break;
477
                    }
478
                }
479
480
                // The upper bound should be the first time the curly brackets are even again or the first occurrence
481
                // of the semicolon. The semicolon is important, as we have to get function declarations in interfaces
482
                // as well.
483
                $upperBound = count($tokens) - 1;
484
                $bracketCounter = null;
485
                for ($j = $i + 1; $j < count($tokens); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
486
                    if ($tokens[$j] === '{' || $tokens[$j][0] === T_CURLY_OPEN) {
487
                        // If we still got null set to 0
488
                        if ($bracketCounter === null) {
489
                            $bracketCounter = 0;
490
                        }
491
492
                        $bracketCounter++;
493
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
494 View Code Duplication
                    } elseif ($tokens[$j] === '}') {
495
                        // If we still got null set to 0
496
                        if ($bracketCounter === null) {
497
                            $bracketCounter = 0;
498
                        }
499
500
                        $bracketCounter--;
501
                    }
502
503
                    // Did we reach a semicolon before reaching a opening curly bracket?
504 View Code Duplication
                    if ($bracketCounter === null && $tokens[$j] === ';') {
505
                        $upperBound = $j + 1;
506
                        break;
507
                    }
508
509
                    // Do we have an even amount of brackets yet?
510
                    if ($bracketCounter === 0) {
511
                        $upperBound = $j + 1;
512
                        break;
513
                    }
514
                }
515
516
                $result[] = array_slice($tokens, $lowerBound, $upperBound - $lowerBound);
517
            }
518
        }
519
520
        return $result;
521
    }
522
523
    /**
524
     * Will return the visibility of the function passed as a token array
525
     *
526
     * @param array $tokens The token array
527
     *
528
     * @return string
529
     *
530
     * TODO I am sure this can be done more generally usable
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
531
     */
532
    public function getFunctionVisibility(
533
        array $tokens
534
    ) {
535
        // Check out all the tokens and look if we find the right thing. We can do that as these keywords are not valid
536
        // within a function definition. Public is default.
537
        $visibility = 'public';
538
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
539
            // Search for the visibility
540 View Code Duplication
            if (is_array($tokens[$i]) && ($tokens[$i][0] === T_PRIVATE || $tokens[$i][0] === T_PROTECTED)) {
541
                // Got it!
542
                $visibility = $tokens[$i][1];
543
            }
544
545
            // Did we reach the function already?
546
            if ($tokens[$i][0] === T_FUNCTION) {
547
                break;
548
            }
549
        }
550
551
        return $visibility;
552
    }
553
}
554