Completed
Push — feature/fix-509-removed-extens... ( 924d84 )
by Juliette
01:47
created

NewExtensionsSniff::isWhiteListed()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 22
Code Lines 12

Duplication

Lines 22
Ratio 100 %

Importance

Changes 0
Metric Value
dl 22
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 12
nc 7
nop 1
1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\NewExtensionsSniff.
4
 *
5
 * @category PHP
6
 * @package  PHPCompatibility
7
 * @author   Juliette Reinders Folmer <[email protected]>
8
 */
9
10
namespace PHPCompatibility\Sniffs\PHP;
11
12
use PHPCompatibility\AbstractNewFeatureSniff;
13
14
/**
15
 * \PHPCompatibility\Sniffs\PHP\NewExtensionsSniff.
16
 *
17
 * Detect use of PHP extensions which were not available in older PHP versions.
18
 *
19
 * @category PHP
20
 * @package  PHPCompatibility
21
 * @author   Juliette Reinders Folmer <[email protected]>
22
 */
23
class NewExtensionsSniff extends AbstractNewFeatureSniff
24
{
25
    /**
26
     * A list of functions to whitelist, if any.
27
     *
28
     * This is intended for projects using functions which start with the same
29
     * prefix as one of the removed extensions.
30
     *
31
     * This property can be set from the ruleset, like so:
32
     * <rule ref="PHPCompatibility.PHP.RemovedExtensions">
33
     *   <properties>
34
     *     <property name="functionWhitelist" type="array" value="mysql_to_rfc3339,mysql_another_function" />
35
     *   </properties>
36
     * </rule>
37
     *
38
     * @var array
39
     */
40
    public $functionWhitelist;
41
42
    /**
43
     * A list of new PHP extensions.
44
     *
45
     * The array lists : version number with false (available in PECL) and true (shipped with PHP).
46
     * If's sufficient to list the first version where the extension was introduced.
47
     *
48
     * @var array(string|null)
49
     */
50
    protected $newExtensions = array(
51
        'csprng' => array(
52
            '7.0'      => true,
53
            'prefixes' => array(
54
                // Function prefix all functions: verified.
55
                'random_', // In practice: only random_bytes, random_int
56
                // NO ini settings
57
                // NO constants
58
            ),
59
        ),
60
        'fileinfo' => array(
61
            '5.3'      => true,
62
            'prefixes' => array(
63
                // Function prefix all functions: verified.
64
                'finfo_'
65
                // Also function: mime_content_type()
66
                // Class: finfo
67
                // NO ini settings
68
                // Constants prefix: FILEINFO_
69
            ),
70
        ),
71
        'hash' => array(
72
            '5.1.2'    => true,
73
            'prefixes' => array(
74
                // Function prefix: verified.
75
                'hash_'
76
                // Function `hash()` also exists!!!!
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...
77
                // NO ini settings
78
                // 1 constant: HASH_HMAC
79
            ),
80
        ),
81
        'opcache' => array(
82
            '5.2'      => false,
83
            '5.5'      => true,
84
            'prefixes' => array(
85
                // Function prefix all functions: verified.
86
                'opcache_'
87
                // ini prefix: `opcache.`
88
                // NO constants
89
            ),
90
        ),
91
        'password' => array(
92
            '5.5'      => true,
93
            'prefixes' => array(
94
                // Function prefix all functions: verified.
95
                'password_'
96
                // NO ini settings
97
                // 2 constants: PASSWORD_BCRYPT, PASSWORD_DEFAULT
98
            ),
99
        ),
100
        'phar' => array(
101
            '5.3'      => true,
102
            'prefixes' => array(
103
                // NO functions, only classes.
104
                // ini prefix: `phar.`
105
                // constants - all class constants: `Phar::`
106
                // Classes: Phar, PharData, PharFileInfo, PharException
107
            ),
108
        ),
109
110
/*
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% 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...
111
        'activescript' => array(
112
            '5.1' => true,
113
            'alternative' => 'pecl/activescript',
114
        ),
115
        'cpdf' => array(
116
            '5.1' => true,
117
            'alternative' => 'pecl/pdflib',
118
        ),
119
        'dbase' => array(
120
            '5.3' => true,
121
            'alternative' => null,
122
            'separator' => '_', // Verified: all functions use separator.
123
        ),
124
        'dbx' => array(
125
            '5.1' => true,
126
            'alternative' => 'pecl/dbx',
127
            'separator' => '_', // Verified: all functions use separator.
128
        ),
129
        'dio' => array(
130
            '5.1' => true,
131
            'alternative' => 'pecl/dio',
132
            'separator' => '_', // Verified: all functions use separator.
133
        ),
134
        'ereg' => array(
135
            '5.3' => false,
136
            '7.0' => true,
137
            'alternative' => 'pcre',
138
        ),
139
        'fam' => array(
140
            '5.1' => true,
141
            'alternative' => null,
142
            'separator' => '_', // Verified: all functions use separator.
143
        ),
144
        'fbsql' => array(
145
            '5.3' => true,
146
            'alternative' => null,
147
            'separator' => '_', // Verified: all functions use separator.
148
        ),
149
        'fdf' => array(
150
            '5.3' => true,
151
            'alternative' => 'pecl/fdf',
152
            'separator' => '_', // Verified: all functions use separator.
153
        ),
154
        'filepro' => array(
155
            '5.2' => true,
156
            'alternative' => null,
157
            'separator' => '_', // Verified: function 'filepro' exists - all other functions use separator.
158
        ),
159
        'hw_api' => array(
160
            '5.2' => true,
161
            'alternative' => null,
162
        ),
163
        'ingres' => array(
164
            '5.1' => true,
165
            'alternative' => 'pecl/ingres',
166
            'separator' => '_', // Verified: all functions use separator.
167
        ),
168
        'ircg' => array(
169
            '5.1' => true,
170
            'alternative' => null,
171
        ),
172
        'mcrypt' => array(
173
            '7.1' => false,
174
            'alternative' => 'openssl (preferred) or pecl/mcrypt once available',
175
            'separator' => '_', // Verified: all functions use separator, though there is also the mdecrypt_generic function.
176
        ),
177
        'mcve' => array(
178
            '5.1' => true,
179
            'alternative' => 'pecl/mvce',
180
        ),
181
        'ming' => array(
182
            '5.3' => true,
183
            'alternative' => 'pecl/ming',
184
        ),
185
        'mnogosearch' => array(
186
            '5.1' => true,
187
            'alternative' => null,
188
            'prefix' => 'udm',
189
            'separator' => '_', // Verified: all functions use separator.
190
        ),
191
        'msql' => array(
192
            '5.3' => true,
193
            'alternative' => null,
194
            'separator' => '_', // Verified: function 'msql' exists - all other functions use separator.
195
        ),
196
        'mssql' => array(
197
            '7.0' => true,
198
            'alternative' => null,
199
            'separator' => '_', // Verified: all functions use separator.
200
        ),
201
        'mysql_' => array(
202
            '5.5' => false,
203
            '7.0' => true,
204
            'alternative' => 'mysqli',
205
            'separator' => '_', // Verified: all functions use separator.
206
        ),
207
        'ncurses' => array(
208
            '5.3' => true,
209
            'alternative' => 'pecl/ncurses',
210
            'separator' => '_', // Verified: all functions use separator.
211
        ),
212
        'oracle' => array(
213
            '5.1' => true,
214
            'alternative' => 'oci8 or pdo_oci',
215
        ),
216
        'ovrimos' => array(
217
            '5.1' => true,
218
            'alternative' => null,
219
            'separator' => '_', // Verified: all functions use separator.
220
        ),
221
        'pfpro' => array(
222
            '5.3' => true,
223
            'alternative' => null,
224
        ),
225
        'sqlite' => array(
226
            '5.4' => true,
227
            'alternative' => null,
228
            'separator' => '_', // Verified: all functions use separator.
229
        ),
230
        // Has to be before `sybase` as otherwise it will never match.
231
        'sybase_ct' => array(
232
            '7.0' => true,
233
            'alternative' => null,
234
        ),
235
        'sybase' => array(
236
            '5.3' => true,
237
            'alternative' => 'sybase_ct',
238
            'separator' => '_', // Verified: all functions use separator.
239
        ),
240
        'w32api' => array(
241
            '5.1' => true,
242
            'alternative' => 'pecl/ffi',
243
            'separator' => '_', // Verified: all functions use separator.
244
        ),
245
        'yp' => array(
246
            '5.1' => true,
247
            'alternative' => null,
248
            'separator' => '_', // Verified: all functions use separator.
249
        ),
250
*/
251
    );
252
253
    /**
254
     * Returns an array of tokens this test wants to listen for.
255
     *
256
     * @return array
257
     */
258
    public function register()
259
    {
260
        // Handle case-insensitivity of function names.
261
        $this->newExtensions = $this->arrayKeysToLowercase($this->newExtensions);
262
263
        return array(T_STRING);
264
265
    }//end register()
266
267
    /**
268
     * Processes this test, when one of its tokens is encountered.
269
     *
270
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
271
     * @param int                   $stackPtr  The position of the current token in the
272
     *                                         stack passed in $tokens.
273
     *
274
     * @return void
275
     */
276 View Code Duplication
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
277
    {
278
        $tokens = $phpcsFile->getTokens();
279
280
        // Find the next non-empty token.
281
        $openBracket = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
282
283
        if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) {
284
            // Not a function call.
285
            return;
286
        }
287
288
        if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
289
            // Not a function call.
290
            return;
291
        }
292
293
        // Find the previous non-empty token.
294
        $search   = \PHP_CodeSniffer_Tokens::$emptyTokens;
295
        $search[] = T_BITWISE_AND;
296
        $previous = $phpcsFile->findPrevious($search, ($stackPtr - 1), null, true);
297
        if ($tokens[$previous]['code'] === T_FUNCTION) {
298
            // It's a function definition, not a function call.
299
            return;
300
        }
301
302
        if ($tokens[$previous]['code'] === T_NEW) {
303
            // We are creating an object, not calling a function.
304
            return;
305
        }
306
307
        if ($tokens[$previous]['code'] === T_OBJECT_OPERATOR) {
308
            // We are calling a method of an object.
309
            return;
310
        }
311
312
        $function   = $tokens[$stackPtr]['content'];
313
        $functionLc = strtolower($function);
314
315
        if ($this->isWhiteListed($functionLc) === true) {
316
            // Function is whitelisted.
317
            return;
318
        }
319
320
        foreach ($this->removedExtensions as $extension => $versionList) {
321
            if (strpos($functionLc, $extension) === 0) {
322
                $itemInfo = array(
323
                    'name'   => $extension,
324
                );
325
                $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
326
                break;
327
            }
328
        }
329
330
    }//end process()
331
332
333
    /**
334
     * Is the current function being checked whitelisted ?
335
     *
336
     * Parsing the list late as it may be provided as a property, but also inline.
337
     *
338
     * @param string $content Content of the current token.
339
     *
340
     * @return bool
341
     */
342 View Code Duplication
    protected function isWhiteListed($content)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
343
    {
344
        if (isset($this->functionWhitelist) === false) {
345
            return false;
346
        }
347
348
        if (is_string($this->functionWhitelist) === true) {
349
            if (strpos($this->functionWhitelist, ',') !== false) {
350
                $this->functionWhitelist = explode(',', $this->functionWhitelist);
351
            } else {
352
                $this->functionWhitelist = (array) $this->functionWhitelist;
353
            }
354
        }
355
356
        if (is_array($this->functionWhitelist) === true) {
357
            $this->functionWhitelist = array_map('strtolower', $this->functionWhitelist);
358
            return in_array($content, $this->functionWhitelist, true);
359
        }
360
361
        return false;
362
363
    }//end isWhiteListed()
364
365
366
    /**
367
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
368
     *
369
     * @param array $itemInfo Base information about the item.
370
     *
371
     * @return array Version and other information about the item.
372
     */
373
    public function getItemArray(array $itemInfo)
374
    {
375
        return $this->removedExtensions[$itemInfo['name']];
376
    }
377
378
379
    /**
380
     * Get the error message template for this sniff.
381
     *
382
     * @return string
383
     */
384
    protected function getErrorMsgTemplate()
385
    {
386
        return "Extension '%s' is ";
387
    }
388
389
390
}//end class
391