Completed
Push — master ( 4fa57b...9d48ec )
by Wim
03:41
created

ForbiddenCallTimePassByReferenceSniff::process()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 72
Code Lines 38

Duplication

Lines 3
Ratio 4.17 %

Importance

Changes 0
Metric Value
dl 3
loc 72
rs 5.519
c 0
b 0
f 0
cc 12
eloc 38
nc 12
nop 2

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
                $isError   = false;
112
                $errorCode = 'Deprecated';
113
114
                if($this->supportsAbove('5.4')) {
115
                    $error    .= ' and prohibited since PHP 5.4';
116
                    $isError   = true;
117
                    $errorCode = 'NotAllowed';
118
                }
119
120
                $this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode);
121
            }
122
        }
123
    }//end process()
124
125
126
    protected function isCallTimePassByReferenceParam(PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel)
127
    {
128
        $tokens   = $phpcsFile->getTokens();
129
130
        $searchStartToken = $parameter['start'] - 1;
131
        $searchEndToken   = $parameter['end'] + 1;
132
        $nextVariable     = $searchStartToken;
133
        do {
134
            $nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken);
135
            if ($nextVariable === false) {
136
                return false;
137
            }
138
139
            // Make sure the variable belongs directly to this function call
140
            // and is not inside a nested function call or array.
141
            if (isset($tokens[$nextVariable]['nested_parenthesis']) === false ||
142
               (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
143
            ) {
144
                continue;
145
            }
146
147
148
            // 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...
149
            $tokenBefore = $phpcsFile->findPrevious(
150
                PHP_CodeSniffer_Tokens::$emptyTokens,
151
                ($nextVariable - 1),
152
                $searchStartToken,
153
                true
154
            );
155
156
            if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) {
157
                // Nothing before the token or no &.
158
                continue;
159
            }
160
161
            // 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...
162
            $tokenBefore = $phpcsFile->findPrevious(
163
                PHP_CodeSniffer_Tokens::$emptyTokens,
164
                ($tokenBefore - 1),
165
                $searchStartToken,
166
                true
167
            );
168
169
            // We have to exclude all uses of T_BITWISE_AND that are not
170
            // references. We use a blacklist approach as we prefer false
171
            // positives to not identifying a pass-by-reference call at all.
172
            // The blacklist may not yet be complete.
173
            switch ($tokens[$tokenBefore]['code']) {
174
                // In these cases T_BITWISE_AND represents
175
                // the bitwise and operator.
176
                case T_LNUMBER:
177
                case T_VARIABLE:
178
                case T_CLOSE_SQUARE_BRACKET:
179
                case T_CLOSE_PARENTHESIS:
180
                    break;
181
182
                // Prevent false positive on assign by reference and compare with reference
183
                // with function call parameters.
184
                case T_EQUAL:
185
                case T_AND_EQUAL:
186
                case T_OR_EQUAL:
187
                case T_CONCAT_EQUAL:
188
                case T_DIV_EQUAL:
189
                case T_MINUS_EQUAL:
190
                case T_MOD_EQUAL:
191
                case T_MUL_EQUAL:
192
                case T_PLUS_EQUAL:
193
                case T_XOR_EQUAL:
194
                case T_SL_EQUAL:
195
                case T_SR_EQUAL:
196
                case T_IS_EQUAL:
197
                case T_IS_IDENTICAL:
198
                    break;
199
200
                // Unfortunately the tokenizer fails to recognize global constants,
201
                // class-constants and -attributes. Any of these are returned is
202
                // treated as T_STRING.
203
                // So we step back another token and check if it is a class
204
                // operator (-> or ::), which means we have a false positive.
205
                // Global constants still remain uncovered.
206
                case T_STRING:
207
                    $tokenBeforePlus = $phpcsFile->findPrevious(
208
                        PHP_CodeSniffer_Tokens::$emptyTokens,
209
                        ($tokenBefore - 1),
210
                        $searchStartToken,
211
                        true
212
                    );
213
                    if ($tokens[$tokenBeforePlus]['code'] === T_DOUBLE_COLON ||
214
                        $tokens[$tokenBeforePlus]['code'] === T_OBJECT_OPERATOR
215
                    ) {
216
                        break;
217
                    }
218
                    // If not a class constant: fall through.
219
220
                default:
221
                    // Deal with T_POW_EQUAL which doesn't exist in PHPCS 1.x
222
                    if (defined('T_POW_EQUAL') && $tokens[$tokenBefore]['type'] === 'T_POW_EQUAL') {
223
                        break;
224
                    }
225
226
                    // The found T_BITWISE_AND represents a pass-by-reference.
227
                    return true;
228
            }
229
230
        } while($nextVariable < $searchEndToken);
231
232
        // This code should never be reached, but here in case of weird bugs ;-)
233
        return false;
234
    }
235
236
}//end class
237