Completed
Push — master ( 695a68...adab54 )
by Wim
03:17
created

isCallTimePassByReferenceParam()   D

Complexity

Conditions 14
Paths 10

Size

Total Lines 86
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 86
rs 4.9516
c 0
b 0
f 0
cc 14
eloc 43
nc 10
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_ForbiddenCallTimePassByReference.
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Gary Rogers <[email protected]>
10
 * @author    Florian Grandel <[email protected]>
11
 * @copyright 2009 Florian Grandel
12
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13
 */
14
15
/**
16
 * PHPCompatibility_Sniffs_PHP_ForbiddenCallTimePassByReference.
17
 *
18
 * Discourages the use of call time pass by references
19
 *
20
 * PHP version 5.4
21
 *
22
 * @category  PHP
23
 * @package   PHPCompatibility
24
 * @author    Gary Rogers <[email protected]>
25
 * @author    Florian Grandel <[email protected]>
26
 * @copyright 2009 Florian Grandel
27
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
28
 */
29
class PHPCompatibility_Sniffs_PHP_ForbiddenCallTimePassByReferenceSniff extends PHPCompatibility_Sniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
30
{
31
32
    /**
33
     * Returns an array of tokens this test wants to listen for.
34
     *
35
     * @return array
36
     */
37
    public function register()
38
    {
39
        return array(T_STRING);
40
41
    }//end register()
42
43
    /**
44
     * Processes this test, when one of its tokens is encountered.
45
     *
46
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
47
     * @param int                  $stackPtr  The position of the current token
48
     *                                        in the stack passed in $tokens.
49
     *
50
     * @return void
51
     */
52
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
53
    {
54
        if ($this->supportsAbove('5.3') === false) {
55
            return;
56
        }
57
58
        $tokens = $phpcsFile->getTokens();
59
60
        // Skip tokens that are the names of functions or classes
61
        // within their definitions. For example: function myFunction...
62
        // "myFunction" is T_STRING but we should skip because it is not a
63
        // function or method *call*.
64
        $findTokens = array_merge(
65
            PHP_CodeSniffer_Tokens::$emptyTokens,
66
            array(T_BITWISE_AND)
67
        );
68
69
        $prevNonEmpty = $phpcsFile->findPrevious(
70
            $findTokens,
71
            ($stackPtr - 1),
72
            null,
73
            true
74
        );
75
76
        if ($prevNonEmpty !== false && in_array($tokens[$prevNonEmpty]['code'], array(T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT), true)) {
77
            return;
78
        }
79
80
        // If the next non-whitespace token after the function or method call
81
        // is not an opening parenthesis then it can't really be a *call*.
82
        $openBracket = $phpcsFile->findNext(
83
            PHP_CodeSniffer_Tokens::$emptyTokens,
84
            ($stackPtr + 1),
85
            null,
86
            true
87
        );
88
89
        if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS
90
           || isset($tokens[$openBracket]['parenthesis_closer']) === false
91
        ) {
92
            return;
93
        }
94
95
        // Get the function call parameters.
96
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
97
        if (count($parameters) === 0) {
98
            return;
99
        }
100
101
        // Which nesting level is the one we are interested in ?
102
        $nestedParenthesisCount = 1;
103 View Code Duplication
        if (isset($tokens[$openBracket]['nested_parenthesis'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
104
            $nestedParenthesisCount = count($tokens[$openBracket]['nested_parenthesis']) + 1;
105
        }
106
107
        foreach ($parameters as $parameter) {
108
            if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) {
109
                // T_BITWISE_AND represents a pass-by-reference.
110
                $error = 'Using a call-time pass-by-reference is deprecated since PHP 5.3';
111
                if($this->supportsAbove('5.4')) {
112
                    $error .= ' and prohibited since PHP 5.4';
113
                }
114
                $phpcsFile->addError($error, $parameter['start'], 'NotAllowed');
115
            }
116
        }
117
    }//end process()
118
119
120
    protected function isCallTimePassByReferenceParam(PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel)
121
    {
122
        $tokens   = $phpcsFile->getTokens();
123
124
        $searchStartToken = $parameter['start'] - 1;
125
        $searchEndToken   = $parameter['end'] + 1;
126
        $nextVariable     = $searchStartToken;
127
        do {
128
            $nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken);
129
            if ($nextVariable === false) {
130
                return false;
131
            }
132
133
            // Make sure the variable belongs directly to this function call
134
            // and is not inside a nested function call or array.
135
            if (isset($tokens[$nextVariable]['nested_parenthesis']) === false ||
136
               (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
137
            ) {
138
                continue;
139
            }
140
141
142
            // Checking this: $value = my_function(...[*]$arg...).
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
143
            $tokenBefore = $phpcsFile->findPrevious(
144
                PHP_CodeSniffer_Tokens::$emptyTokens,
145
                ($nextVariable - 1),
146
                $searchStartToken,
147
                true
148
            );
149
150
            if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) {
151
                // Nothing before the token or no &.
152
                continue;
153
            }
154
155
            // Checking this: $value = my_function(...[*]&$arg...).
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
156
            $tokenBefore = $phpcsFile->findPrevious(
157
                PHP_CodeSniffer_Tokens::$emptyTokens,
158
                ($tokenBefore - 1),
159
                $searchStartToken,
160
                true
161
            );
162
163
            // We have to exclude all uses of T_BITWISE_AND that are not
164
            // references. We use a blacklist approach as we prefer false
165
            // positives to not identifying a pass-by-reference call at all.
166
            // The blacklist may not yet be complete.
167
            switch ($tokens[$tokenBefore]['code']) {
168
                // In these cases T_BITWISE_AND represents
169
                // the bitwise and operator.
170
                case T_LNUMBER:
171
                case T_VARIABLE:
172
                case T_CLOSE_SQUARE_BRACKET:
173
                case T_CLOSE_PARENTHESIS:
174
                    break;
175
176
                // Unfortunately the tokenizer fails to recognize global constants,
177
                // class-constants and -attributes. Any of these are returned is
178
                // treated as T_STRING.
179
                // So we step back another token and check if it is a class
180
                // operator (-> or ::), which means we have a false positive.
181
                // Global constants still remain uncovered.
182
                case T_STRING:
183
                    $tokenBeforePlus = $phpcsFile->findPrevious(
184
                        PHP_CodeSniffer_Tokens::$emptyTokens,
185
                        ($tokenBefore - 1),
186
                        $searchStartToken,
187
                        true
188
                    );
189
                    if ($tokens[$tokenBeforePlus]['code'] === T_DOUBLE_COLON ||
190
                        $tokens[$tokenBeforePlus]['code'] === T_OBJECT_OPERATOR
191
                    ) {
192
                        break;
193
                    }
194
                    // If not a class constant: fall through.
195
196
                default:
197
                    // The found T_BITWISE_AND represents a pass-by-reference.
198
                    return true;
199
            }
200
201
        } while($nextVariable < $searchEndToken);
202
203
        // This code should never be reached, but here in case of weird bugs ;-)
204
        return false;
205
    }
206
207
}//end class
208