Completed
Push — master ( 6a4ee6...24bb48 )
by Juliette
01:33
created

ForbiddenCallTimePassByReferenceSniff   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 215
Duplicated Lines 1.4 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 3
dl 3
loc 215
rs 8.3157
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 5 1
C process() 3 70 12
D isCallTimePassByReferenceParam() 0 108 30

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 ForbiddenCallTimePassByReferenceSniff 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 ForbiddenCallTimePassByReferenceSniff, and based on these observations, apply Extract Interface, too.

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
 */
13
14
/**
15
 * PHPCompatibility_Sniffs_PHP_ForbiddenCallTimePassByReference.
16
 *
17
 * Discourages the use of call time pass by references
18
 *
19
 * PHP version 5.4
20
 *
21
 * @category  PHP
22
 * @package   PHPCompatibility
23
 * @author    Gary Rogers <[email protected]>
24
 * @author    Florian Grandel <[email protected]>
25
 * @copyright 2009 Florian Grandel
26
 */
27
class PHPCompatibility_Sniffs_PHP_ForbiddenCallTimePassByReferenceSniff extends PHPCompatibility_Sniff
28
{
29
30
    /**
31
     * Returns an array of tokens this test wants to listen for.
32
     *
33
     * @return array
34
     */
35
    public function register()
36
    {
37
        return array(T_STRING);
38
39
    }//end register()
40
41
    /**
42
     * Processes this test, when one of its tokens is encountered.
43
     *
44
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
45
     * @param int                  $stackPtr  The position of the current token
46
     *                                        in the stack passed in $tokens.
47
     *
48
     * @return void
49
     */
50
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
51
    {
52
        if ($this->supportsAbove('5.3') === false) {
53
            return;
54
        }
55
56
        $tokens = $phpcsFile->getTokens();
57
58
        // Skip tokens that are the names of functions or classes
59
        // within their definitions. For example: function myFunction...
60
        // "myFunction" is T_STRING but we should skip because it is not a
61
        // function or method *call*.
62
        $findTokens   = PHP_CodeSniffer_Tokens::$emptyTokens;
63
        $findTokens[] = T_BITWISE_AND;
64
65
        $prevNonEmpty = $phpcsFile->findPrevious(
66
            $findTokens,
67
            ($stackPtr - 1),
68
            null,
69
            true
70
        );
71
72
        if ($prevNonEmpty !== false && in_array($tokens[$prevNonEmpty]['code'], array(T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT), true)) {
73
            return;
74
        }
75
76
        // If the next non-whitespace token after the function or method call
77
        // is not an opening parenthesis then it can't really be a *call*.
78
        $openBracket = $phpcsFile->findNext(
79
            PHP_CodeSniffer_Tokens::$emptyTokens,
80
            ($stackPtr + 1),
81
            null,
82
            true
83
        );
84
85
        if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS
86
            || isset($tokens[$openBracket]['parenthesis_closer']) === false
87
        ) {
88
            return;
89
        }
90
91
        // Get the function call parameters.
92
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
93
        if (count($parameters) === 0) {
94
            return;
95
        }
96
97
        // Which nesting level is the one we are interested in ?
98
        $nestedParenthesisCount = 1;
99 View Code Duplication
        if (isset($tokens[$openBracket]['nested_parenthesis'])) {
0 ignored issues
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...
100
            $nestedParenthesisCount = count($tokens[$openBracket]['nested_parenthesis']) + 1;
101
        }
102
103
        foreach ($parameters as $parameter) {
104
            if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) {
105
                // T_BITWISE_AND represents a pass-by-reference.
106
                $error     = 'Using a call-time pass-by-reference is deprecated since PHP 5.3';
107
                $isError   = false;
108
                $errorCode = 'Deprecated';
109
110
                if ($this->supportsAbove('5.4')) {
111
                    $error    .= ' and prohibited since PHP 5.4';
112
                    $isError   = true;
113
                    $errorCode = 'NotAllowed';
114
                }
115
116
                $this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode);
117
            }
118
        }
119
    }//end process()
120
121
122
    /**
123
     * Determine whether a parameter is passed by reference.
124
     *
125
     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
126
     * @param array                $parameter    Information on the current parameter
127
     *                                           to be examined.
128
     * @param int                  $nestingLevel Target nesting level.
129
     *
130
     * @return bool
131
     */
132
    protected function isCallTimePassByReferenceParam(PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel)
133
    {
134
        $tokens   = $phpcsFile->getTokens();
135
136
        $searchStartToken = $parameter['start'] - 1;
137
        $searchEndToken   = $parameter['end'] + 1;
138
        $nextVariable     = $searchStartToken;
139
        do {
140
            $nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken);
141
            if ($nextVariable === false) {
142
                return false;
143
            }
144
145
            // Make sure the variable belongs directly to this function call
146
            // and is not inside a nested function call or array.
147
            if (isset($tokens[$nextVariable]['nested_parenthesis']) === false
148
                || (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
149
            ) {
150
                continue;
151
            }
152
153
154
            // 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...
155
            $tokenBefore = $phpcsFile->findPrevious(
156
                PHP_CodeSniffer_Tokens::$emptyTokens,
157
                ($nextVariable - 1),
158
                $searchStartToken,
159
                true
160
            );
161
162
            if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) {
163
                // Nothing before the token or no &.
164
                continue;
165
            }
166
167
            // 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...
168
            $tokenBefore = $phpcsFile->findPrevious(
169
                PHP_CodeSniffer_Tokens::$emptyTokens,
170
                ($tokenBefore - 1),
171
                $searchStartToken,
172
                true
173
            );
174
175
            // We have to exclude all uses of T_BITWISE_AND that are not
176
            // references. We use a blacklist approach as we prefer false
177
            // positives to not identifying a pass-by-reference call at all.
178
            // The blacklist may not yet be complete.
179
            switch ($tokens[$tokenBefore]['code']) {
180
                // In these cases T_BITWISE_AND represents
181
                // the bitwise and operator.
182
                case T_LNUMBER:
183
                case T_VARIABLE:
184
                case T_CLOSE_SQUARE_BRACKET:
185
                case T_CLOSE_PARENTHESIS:
186
                    break;
187
188
                // Prevent false positive on assign by reference and compare with reference
189
                // with function call parameters.
190
                case T_EQUAL:
191
                case T_AND_EQUAL:
192
                case T_OR_EQUAL:
193
                case T_CONCAT_EQUAL:
194
                case T_DIV_EQUAL:
195
                case T_MINUS_EQUAL:
196
                case T_MOD_EQUAL:
197
                case T_MUL_EQUAL:
198
                case T_PLUS_EQUAL:
199
                case T_XOR_EQUAL:
200
                case T_SL_EQUAL:
201
                case T_SR_EQUAL:
202
                case T_IS_EQUAL:
203
                case T_IS_IDENTICAL:
204
                    break;
205
206
                // Unfortunately the tokenizer fails to recognize global constants,
207
                // class-constants and -attributes. Any of these are returned is
208
                // treated as T_STRING.
209
                // So we step back another token and check if it is a class
210
                // operator (-> or ::), which means we have a false positive.
211
                // Global constants still remain uncovered.
212
                case T_STRING:
213
                    $tokenBeforePlus = $phpcsFile->findPrevious(
214
                        PHP_CodeSniffer_Tokens::$emptyTokens,
215
                        ($tokenBefore - 1),
216
                        $searchStartToken,
217
                        true
218
                    );
219
                    if ($tokens[$tokenBeforePlus]['code'] === T_DOUBLE_COLON
220
                        || $tokens[$tokenBeforePlus]['code'] === T_OBJECT_OPERATOR
221
                    ) {
222
                        break;
223
                    }
224
                    // If not a class constant: fall through.
225
226
                default:
227
                    // Deal with T_POW_EQUAL which doesn't exist in PHPCS 1.x.
228
                    if (defined('T_POW_EQUAL') && $tokens[$tokenBefore]['type'] === 'T_POW_EQUAL') {
229
                        break;
230
                    }
231
232
                    // The found T_BITWISE_AND represents a pass-by-reference.
233
                    return true;
234
            }
235
        } while ($nextVariable < $searchEndToken);
236
237
        // This code should never be reached, but here in case of weird bugs ;-)
238
        return false;
239
    }
240
241
}//end class
242