RemovedImplodeFlexibleParamOrderSniff   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 295
Duplicated Lines 6.78 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 1
dl 20
loc 295
rs 8.5599
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A bowOutEarly() 0 4 1
F processParameters() 20 168 46
A throwNotice() 0 21 1

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

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\ParameterValues;
12
13
use PHPCompatibility\AbstractFunctionCallParameterSniff;
14
use PHP_CodeSniffer_File as File;
15
use PHP_CodeSniffer_Tokens as Tokens;
16
17
/**
18
 * Passing the `$glue` and `$pieces` parameters to `implode()` in reverse order has
19
 * been deprecated in PHP 7.4.
20
 *
21
 * PHP version 7.4
22
 *
23
 * @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.implode-reverse-parameters
24
 * @link https://wiki.php.net/rfc/deprecations_php_7_4#implode_parameter_order_mix
25
 * @link https://php.net/manual/en/function.implode.php
26
 *
27
 * @since 9.3.0
28
 */
29
class RemovedImplodeFlexibleParamOrderSniff extends AbstractFunctionCallParameterSniff
30
{
31
32
    /**
33
     * Functions to check for.
34
     *
35
     * @since 9.3.0
36
     *
37
     * @var array
38
     */
39
    protected $targetFunctions = array(
40
        'implode' => true,
41
        'join'    => true,
42
    );
43
44
    /**
45
     * List of PHP native constants which should be recognized as text strings.
46
     *
47
     * @since 9.3.0
48
     *
49
     * @var array
50
     */
51
    private $constantStrings = array(
52
        'DIRECTORY_SEPARATOR' => true,
53
        'PHP_EOL'             => true,
54
    );
55
56
    /**
57
     * List of PHP native functions which should be recognized as returning an array.
58
     *
59
     * Note: The array_*() functions will always be taken into account.
60
     *
61
     * @since 9.3.0
62
     *
63
     * @var array
64
     */
65
    private $arrayFunctions = array(
66
        'compact' => true,
67
        'explode' => true,
68
        'range'   => true,
69
    );
70
71
    /**
72
     * List of PHP native array functions which should *not* be recognized as returning an array.
73
     *
74
     * @since 9.3.0
75
     *
76
     * @var array
77
     */
78
    private $arrayFunctionExceptions = array(
79
        'array_key_exists'     => true,
80
        'array_key_first'      => true,
81
        'array_key_last'       => true,
82
        'array_multisort'      => true,
83
        'array_pop'            => true,
84
        'array_product'        => true,
85
        'array_push'           => true,
86
        'array_search'         => true,
87
        'array_shift'          => true,
88
        'array_sum'            => true,
89
        'array_unshift'        => true,
90
        'array_walk_recursive' => true,
91
        'array_walk'           => true,
92
    );
93
94
95
    /**
96
     * Do a version check to determine if this sniff needs to run at all.
97
     *
98
     * @since 9.3.0
99
     *
100
     * @return bool
101
     */
102
    protected function bowOutEarly()
103
    {
104
        return ($this->supportsAbove('7.4') === false);
105
    }
106
107
108
    /**
109
     * Process the parameters of a matched function.
110
     *
111
     * @since 9.3.0
112
     *
113
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
114
     * @param int                   $stackPtr     The position of the current token in the stack.
115
     * @param string                $functionName The token content (function name) which was matched.
116
     * @param array                 $parameters   Array with information about the parameters.
117
     *
118
     * @return int|void Integer stack pointer to skip forward or void to continue
119
     *                  normal file processing.
120
     */
121
    public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
122
    {
123
        if (isset($parameters[2]) === false) {
124
            // Only one parameter, this must be $pieces. Bow out.
125
            return;
126
        }
127
128
        $tokens = $phpcsFile->getTokens();
129
130
        /*
131
         * Examine the first parameter.
132
         * If there is any indication that this is an array declaration, we have an error.
133
         */
134
135
        $targetParam = $parameters[1];
136
        $start       = $targetParam['start'];
137
        $end         = ($targetParam['end'] + 1);
138
        $isOnlyText  = true;
139
140
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
141
        if ($firstNonEmpty === false) {
142
            // Parse error. Shouldn't be possible.
143
            return;
144
        }
145
146 View Code Duplication
        if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_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...
147
            $start = ($firstNonEmpty + 1);
148
            $end   = $tokens[$firstNonEmpty]['parenthesis_closer'];
149
        }
150
151
        $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
152 View Code Duplication
        if ($hasTernary !== 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...
153
            && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
154
            && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
155
        ) {
156
            $start = ($hasTernary + 1);
157
        }
158
159
        for ($i = $start; $i < $end; $i++) {
160
            $tokenCode = $tokens[$i]['code'];
161
162
            if (isset(Tokens::$emptyTokens[$tokenCode])) {
163
                continue;
164
            }
165
166
            if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
167
                continue;
168
            }
169
170
            if ($hasTernary !== false && $tokenCode === \T_INLINE_ELSE) {
171
                continue;
172
            }
173
174
            if (isset(Tokens::$stringTokens[$tokenCode]) === false) {
175
                $isOnlyText = false;
176
            }
177
178
            if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
179
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
180
                return;
181
            }
182
183
            if ($tokenCode === \T_STRING) {
184
                /*
185
                 * Check for specific functions which return an array (i.e. $pieces).
186
                 */
187
                $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), $end, true);
188
                if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
189
                    continue;
190
                }
191
192
                $nameLc = strtolower($tokens[$i]['content']);
193
                if (isset($this->arrayFunctions[$nameLc]) === false
194
                    && (strpos($nameLc, 'array_') !== 0
195
                    || isset($this->arrayFunctionExceptions[$nameLc]) === true)
196
                ) {
197
                    continue;
198
                }
199
200
                // Now make sure it's the PHP native function being called.
201
                $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), $start, true);
202
                if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_COLON
203
                    || $tokens[$prevNonEmpty]['code'] === \T_OBJECT_OPERATOR
204
                ) {
205
                    // Method call, not a call to the PHP native function.
206
                    continue;
207
                }
208
209
                if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR
210
                    && $tokens[$prevNonEmpty - 1]['code'] === \T_STRING
211
                ) {
212
                    // Namespaced function.
213
                    continue;
214
                }
215
216
                // Ok, so we know that there is an array function in the first param.
217
                // 99.9% chance that this is $pieces, not $glue.
218
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
219
                return;
220
            }
221
        }
222
223
        if ($isOnlyText === true) {
224
            // First parameter only contained text string tokens, i.e. glue.
225
            return;
226
        }
227
228
        /*
229
         * Examine the second parameter.
230
         */
231
232
        $targetParam = $parameters[2];
233
        $start       = $targetParam['start'];
234
        $end         = ($targetParam['end'] + 1);
235
236
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
237
        if ($firstNonEmpty === false) {
238
            // Parse error. Shouldn't be possible.
239
            return;
240
        }
241
242 View Code Duplication
        if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_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...
243
            $start = ($firstNonEmpty + 1);
244
            $end   = $tokens[$firstNonEmpty]['parenthesis_closer'];
245
        }
246
247
        $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
248 View Code Duplication
        if ($hasTernary !== 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...
249
            && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
250
            && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
251
        ) {
252
            $start = ($hasTernary + 1);
253
        }
254
255
        for ($i = $start; $i < $end; $i++) {
256
            $tokenCode = $tokens[$i]['code'];
257
258
            if (isset(Tokens::$emptyTokens[$tokenCode])) {
259
                continue;
260
            }
261
262
            if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
263
                // Found an array, $pieces is second.
264
                return;
265
            }
266
267
            if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
268
                // One of the special cased, PHP native string constants found.
269
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
270
                return;
271
            }
272
273
            if ($tokenCode === \T_STRING || $tokenCode === \T_VARIABLE) {
274
                // Function call, constant or variable encountered.
275
                // No matter what this is combined with, we won't be able to reliably determine the value.
276
                return;
277
            }
278
279
            if ($tokenCode === \T_CONSTANT_ENCAPSED_STRING
280
                || $tokenCode === \T_DOUBLE_QUOTED_STRING
281
                || $tokenCode === \T_HEREDOC
282
                || $tokenCode === \T_NOWDOC
283
            ) {
284
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
285
                return;
286
            }
287
        }
288
    }
289
290
291
    /**
292
     * Throw the error/warning.
293
     *
294
     * @since 9.3.0
295
     *
296
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
297
     * @param int                   $stackPtr     The position of the current token in the stack.
298
     * @param string                $functionName The token content (function name) which was matched.
299
     *
300
     * @return void
301
     */
302
    protected function throwNotice(File $phpcsFile, $stackPtr, $functionName)
303
    {
304
        $message   = 'Passing the $glue and $pieces parameters in reverse order to %s has been deprecated since PHP 7.4';
305
        $isError   = false;
306
        $errorCode = 'Deprecated';
307
        $data      = array($functionName);
308
309
        /*
310
        Support for the deprecated behaviour is expected to be removed in PHP 8.0.
311
        Once this has been implemented, this section should be uncommented.
312
        if ($this->supportsAbove('8.0') === true) {
313
            $message  .= ' and is removed since PHP 8.0';
314
            $isError   = true;
315
            $errorCode = 'Removed';
316
        }
317
        */
318
319
        $message .= '; $glue should be the first parameter and $pieces the second';
320
321
        $this->addMessage($phpcsFile, $message, $stackPtr, $isError, $errorCode, $data);
322
    }
323
}
324