Completed
Push — feature/minor-testfile-syntax-... ( db56ec...142740 )
by Juliette
04:05 queued 02:34
created

isTargetPHPErrormsgVar()   D

Complexity

Conditions 25
Paths 216

Size

Total Lines 103
Code Lines 53

Duplication

Lines 10
Ratio 9.71 %

Importance

Changes 0
Metric Value
dl 10
loc 103
rs 4.0725
c 0
b 0
f 0
cc 25
eloc 53
nc 216
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\RemovedGlobalVariablesSniff.
4
 *
5
 * @category PHP
6
 * @package  PHPCompatibility
7
 * @author   Wim Godden <[email protected]>
8
 */
9
10
namespace PHPCompatibility\Sniffs\PHP;
11
12
use PHPCompatibility\AbstractRemovedFeatureSniff;
13
14
/**
15
 * \PHPCompatibility\Sniffs\PHP\RemovedGlobalVariablesSniff.
16
 *
17
 * Discourages the use of removed global variables. Suggests alternative extensions if available
18
 *
19
 * @category PHP
20
 * @package  PHPCompatibility
21
 * @author   Wim Godden <[email protected]>
22
 */
23
class RemovedGlobalVariablesSniff extends AbstractRemovedFeatureSniff
24
{
25
26
    /**
27
     * A list of removed global variables with their alternative, if any.
28
     *
29
     * The array lists : version number with false (deprecated) and true (removed).
30
     * If's sufficient to list the first version where the variable was deprecated/removed.
31
     *
32
     * @var array(string|null)
33
     */
34
    protected $removedGlobalVariables = array(
35
        'HTTP_POST_VARS' => array(
36
            '5.3' => false,
37
            '5.4' => true,
38
            'alternative' => '$_POST',
39
        ),
40
        'HTTP_GET_VARS' => array(
41
            '5.3' => false,
42
            '5.4' => true,
43
            'alternative' => '$_GET',
44
        ),
45
        'HTTP_ENV_VARS' => array(
46
            '5.3' => false,
47
            '5.4' => true,
48
            'alternative' => '$_ENV',
49
        ),
50
        'HTTP_SERVER_VARS' => array(
51
            '5.3' => false,
52
            '5.4' => true,
53
            'alternative' => '$_SERVER',
54
        ),
55
        'HTTP_COOKIE_VARS' => array(
56
            '5.3' => false,
57
            '5.4' => true,
58
            'alternative' => '$_COOKIE',
59
        ),
60
        'HTTP_SESSION_VARS' => array(
61
            '5.3' => false,
62
            '5.4' => true,
63
            'alternative' => '$_SESSION',
64
        ),
65
        'HTTP_POST_FILES' => array(
66
            '5.3' => false,
67
            '5.4' => true,
68
            'alternative' => '$_FILES',
69
        ),
70
71
        'HTTP_RAW_POST_DATA' => array(
72
            '5.6' => false,
73
            '7.0' => true,
74
            'alternative' => 'php://input',
75
        ),
76
77
        'php_errormsg' => array(
78
            '7.2' => false,
79
            'alternative' => 'error_get_last()',
80
        ),
81
    );
82
83
84
    /**
85
     * Returns an array of tokens this test wants to listen for.
86
     *
87
     * @return array
88
     */
89
    public function register()
90
    {
91
        return array(T_VARIABLE);
92
93
    }//end register()
94
95
96
    /**
97
     * Processes this test, when one of its tokens is encountered.
98
     *
99
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
100
     * @param int                   $stackPtr  The position of the current token in the
101
     *                                         stack passed in $tokens.
102
     *
103
     * @return void
104
     */
105
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
106
    {
107
        if ($this->supportsAbove('5.3') === false) {
108
            return;
109
        }
110
111
        $tokens  = $phpcsFile->getTokens();
112
        $varName = substr($tokens[$stackPtr]['content'], 1);
113
114
        if (isset($this->removedGlobalVariables[$varName]) === false) {
115
            return;
116
        }
117
118
        if ($this->isClassProperty($phpcsFile, $stackPtr) === true) {
119
            // Ok, so this was a class property declaration, not our concern.
120
            return;
121
        }
122
123
        // Check for static usage of class properties shadowing the removed global variables.
124
        if ($this->inClassScope($phpcsFile, $stackPtr, false) === true) {
125
            $prevToken = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
126
            if ($prevToken !== false && $tokens[$prevToken]['code'] === T_DOUBLE_COLON) {
127
                return;
128
            }
129
        }
130
131
        // Do some additional checks for the $php_errormsg variable.
132
        if ($varName === 'php_errormsg'
133
            && $this->isTargetPHPErrormsgVar($phpcsFile, $stackPtr, $tokens) === false
134
        ) {
135
            return;
136
        }
137
138
        // Still here, so throw an error/warning.
139
        $itemInfo = array(
140
            'name' => $varName,
141
        );
142
        $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
143
144
    }//end process()
145
146
147
    /**
148
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
149
     *
150
     * @param array $itemInfo Base information about the item.
151
     *
152
     * @return array Version and other information about the item.
153
     */
154
    public function getItemArray(array $itemInfo)
155
    {
156
        return $this->removedGlobalVariables[$itemInfo['name']];
157
    }
158
159
160
    /**
161
     * Get the error message template for this sniff.
162
     *
163
     * @return string
164
     */
165
    protected function getErrorMsgTemplate()
166
    {
167
        return "Global variable '\$%s' is ";
168
    }
169
170
171
    /**
172
     * Filter the error message before it's passed to PHPCS.
173
     *
174
     * @param string $error     The error message which was created.
175
     * @param array  $itemInfo  Base information about the item this error message applied to.
176
     * @param array  $errorInfo Detail information about an item this error message applied to.
177
     *
178
     * @return string
179
     */
180
    protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
181
    {
182
        if ($itemInfo['name'] === 'php_errormsg') {
183
            $error = str_replace('Global', 'The', $error);
184
        }
185
        return $error;
186
    }
187
188
    /**
189
     * Run some additional checks for the `$php_errormsg` variable.
190
     *
191
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
192
     * @param int                   $stackPtr  The position of the current token in the
193
     *                                         stack passed in $tokens.
194
     * @param array                 $tokens    Token array of the current file.
195
     *
196
     * @return bool
197
     */
198
    private function isTargetPHPErrormsgVar(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, array $tokens)
199
    {
200
        $scopeStart = 0;
201
202
        /*
203
         * If the variable is detected within the scope of a function/closure, limit the checking.
204
         */
205
        $function = $phpcsFile->getCondition($stackPtr, T_CLOSURE);
206
        if ($function === false) {
207
            $function = $phpcsFile->getCondition($stackPtr, T_FUNCTION);
208
        }
209
210
        // It could also be a function param, which is not in the function scope.
211
        if ($function === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
212
            $parenthesisCloser = end($tokens[$stackPtr]['nested_parenthesis']);
213
            if (isset($tokens[$parenthesisCloser]['parenthesis_owner'])
214
                && ( $tokens[$tokens[$parenthesisCloser]['parenthesis_owner']]['code'] === T_FUNCTION
215
                    || $tokens[$tokens[$parenthesisCloser]['parenthesis_owner']]['code'] === T_CLOSURE)
216
            ) {
217
                $function = $tokens[$parenthesisCloser]['parenthesis_owner'];
218
            }
219
        }
220
221
        if ($function !== false) {
222
            $scopeStart = $tokens[$function]['scope_opener'];
223
        }
224
225
        /*
226
         * Now, let's do some additional checks.
227
         */
228
        $nextNonEmpty = $phpcsFile->findNext(
229
            \PHP_CodeSniffer_Tokens::$emptyTokens,
230
            ($stackPtr + 1),
231
            null,
232
            true
233
        );
234
235
        // Is the variable being used as an array ?
236
        if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_OPEN_SQUARE_BRACKET) {
237
            // The PHP native variable is a string, so this is probably not it
238
            // (except for array access to string, but why would you in this case ?).
239
            return false;
240
        }
241
242
        // Is this a variable assignment ?
243 View Code Duplication
        if ($nextNonEmpty !== false
244
            && in_array($tokens[$nextNonEmpty]['code'], \PHP_CodeSniffer_Tokens::$assignmentTokens, true)
245
        ) {
246
            return false;
247
        }
248
249
        // Is this a function param shadowing the PHP native one ?
250
        if ($function !== false) {
251
            $parameters = $this->getMethodParameters($phpcsFile, $function);
252
            if (is_array($parameters) === true && empty($parameters) === false) {
253
                foreach ($parameters as $param) {
254
                    if ($param['name'] === '$php_errormsg') {
255
                        return false;
256
                    }
257
                }
258
            }
259
        }
260
261
        $skipPast = array(
262
            'T_CLASS'      => true,
263
            'T_ANON_CLASS' => true,
264
            'T_INTERFACE'  => true,
265
            'T_TRAIT'      => true,
266
            'T_FUNCTION'   => true,
267
            'T_CLOSURE'    => true,
268
        );
269
270
        // Walk back and see if there is an assignment to the variable within the same scope.
271
        for ($i = ($stackPtr - 1); $i >= $scopeStart; $i--) {
272
            if ($tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
273
                && isset($tokens[$i]['scope_condition'])
274
                && isset($skipPast[$tokens[$tokens[$i]['scope_condition']]['type']])
275
            ) {
276
                // Skip past functions, classes etc.
277
                $i = $tokens[$i]['scope_condition'];
278
                continue;
279
            }
280
281
            if ($tokens[$i]['code'] !== T_VARIABLE || $tokens[$i]['content'] !== '$php_errormsg') {
282
                continue;
283
            }
284
285
            $nextNonEmpty = $phpcsFile->findNext(
286
                \PHP_CodeSniffer_Tokens::$emptyTokens,
287
                ($i + 1),
288
                null,
289
                true
290
            );
291
292 View Code Duplication
            if ($nextNonEmpty !== false
293
                && in_array($tokens[$nextNonEmpty]['code'], \PHP_CodeSniffer_Tokens::$assignmentTokens, true)
294
            ) {
295
                return false;
296
            }
297
        }
298
299
        return true;
300
    }
301
302
303
}//end class
304