isTargetPHPErrormsgVar()   F
last analyzed

Complexity

Conditions 25
Paths 216

Size

Total Lines 104

Duplication

Lines 10
Ratio 9.62 %

Importance

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