isTargetPHPErrormsgVar()   F
last analyzed

Complexity

Conditions 25
Paths 216

Size

Total Lines 94

Duplication

Lines 21
Ratio 22.34 %

Importance

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