Completed
Push — master ( a32632...c637b7 )
by Juliette
9s
created

NewExecutionDirectivesSniff::process()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 54
Code Lines 30

Duplication

Lines 3
Ratio 5.56 %

Importance

Changes 0
Metric Value
dl 3
loc 54
rs 7.4119
c 0
b 0
f 0
cc 8
eloc 30
nc 10
nop 2

How to fix   Long Method   

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_NewExecutionDirectivesSniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Juliette Reinders Folmer <[email protected]>
8
 */
9
10
/**
11
 * PHPCompatibility_Sniffs_PHP_NewExecutionDirectivesSniff.
12
 *
13
 * @category  PHP
14
 * @package   PHPCompatibility
15
 * @author    Juliette Reinders Folmer <[email protected]>
16
 */
17
class PHPCompatibility_Sniffs_PHP_NewExecutionDirectivesSniff extends PHPCompatibility_AbstractNewFeatureSniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
18
{
19
20
    /**
21
     * A list of new execution directives
22
     *
23
     * The array lists : version number with false (not present) or true (present).
24
     * If the execution order is conditional, add the condition as a string to the version nr.
25
     * If's sufficient to list the first version where the execution directive appears.
26
     *
27
     * @var array(string => array(string => int|string|null))
28
     */
29
    protected $newDirectives = array (
30
                                'ticks' => array(
31
                                    '3.1' => false,
32
                                    '4.0' => true,
33
                                    'valid_value_callback' => 'isNumeric',
34
                                ),
35
                                'encoding' => array(
36
                                    '5.2' => false,
37
                                    '5.3' => '--enable-zend-multibyte', // Directive ignored unless.
38
                                    '5.4' => true,
39
                                    'valid_value_callback' => 'validEncoding',
40
                                ),
41
                                'strict_types' => array(
42
                                    '5.6' => false,
43
                                    '7.0' => true,
44
                                    'valid_values' => array(1),
45
                                ),
46
                               );
47
48
49
    /**
50
     * Tokens to ignore when trying to find the value for the directive.
51
     *
52
     * @var array
53
     */
54
    protected $ignoreTokens = array();
55
56
57
    /**
58
     * Returns an array of tokens this test wants to listen for.
59
     *
60
     * @return array
61
     */
62
    public function register()
63
    {
64
        $this->ignoreTokens          = PHP_CodeSniffer_Tokens::$emptyTokens;
65
        $this->ignoreTokens[T_EQUAL] = T_EQUAL;
66
67
        return array(T_DECLARE);
68
    }//end register()
69
70
71
    /**
72
     * Processes this test, when one of its tokens is encountered.
73
     *
74
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
75
     * @param int                  $stackPtr  The position of the current token in
76
     *                                        the stack passed in $tokens.
77
     *
78
     * @return void
79
     */
80
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
81
    {
82
        $tokens = $phpcsFile->getTokens();
83
84
        if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === true) {
85
	        $openParenthesis  = $tokens[$stackPtr]['parenthesis_opener'];
86
	        $closeParenthesis = $tokens[$stackPtr]['parenthesis_closer'];
87
        }
88
        else {
89
	        if (version_compare(PHP_CodeSniffer::VERSION, '2.0', '>=')) {
90
				return;
91
			}
92
93
			// Deal with PHPCS 1.x which does not set the parenthesis properly for declare statements.
94
			$openParenthesis = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($stackPtr + 1), null, false, null, true);
95 View Code Duplication
			if ($openParenthesis === false || isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
1 ignored issue
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...
96
				return;
97
			}
98
			$closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
99
		}
100
101
        $directivePtr = $phpcsFile->findNext(T_STRING, ($openParenthesis + 1), $closeParenthesis, false);
102
        if ($directivePtr === false) {
103
            return;
104
        }
105
106
        $directiveContent = $tokens[$directivePtr]['content'];
107
108
        if (isset($this->newDirectives[$directiveContent]) === false) {
109
            $error = 'Declare can only be used with the directives %s. Found: %s';
110
            $data  = array(
111
                implode(', ', array_keys($this->newDirectives)),
112
                $directiveContent,
113
            );
114
115
            $phpcsFile->addError($error, $stackPtr, 'InvalidDirectiveFound', $data);
116
        }
117
        else {
118
            // Check for valid directive for version.
119
            $itemInfo = array(
120
                'name'   => $directiveContent,
121
            );
122
            $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
123
124
            // Check for valid directive value.
125
            $valuePtr = $phpcsFile->findNext($this->ignoreTokens, $directivePtr + 1, $closeParenthesis, true);
126
            if ($valuePtr === false) {
127
                return;
128
            }
129
130
            $this->addWarningOnInvalidValue($phpcsFile, $valuePtr, $directiveContent);
131
        }
132
133
    }//end process()
134
135
136
    /**
137
     * Determine whether an error/warning should be thrown for an item based on collected information.
138
     *
139
     * @param array $errorInfo Detail information about an item.
140
     *
141
     * @return bool
142
     */
143
    protected function shouldThrowError(array $errorInfo)
144
    {
145
        return ($errorInfo['not_in_version'] !== '' || $errorInfo['conditional_version'] !== '');
146
    }
147
148
149
    /**
150
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
151
     *
152
     * @param array $itemInfo Base information about the item.
153
     *
154
     * @return array Version and other information about the item.
155
     */
156
    public function getItemArray(array $itemInfo)
157
    {
158
        return $this->newDirectives[$itemInfo['name']];
159
    }
160
161
162
    /**
163
     * Get an array of the non-PHP-version array keys used in a sub-array.
164
     *
165
     * @return array
166
     */
167
    protected function getNonVersionArrayKeys()
168
    {
169
        return array(
170
            'valid_value_callback',
171
            'valid_values',
172
        );
173
    }
174
175
176
    /**
177
     * Retrieve the relevant detail (version) information for use in an error message.
178
     *
179
     * @param array $itemArray Version and other information about the item.
180
     * @param array $itemInfo  Base information about the item.
181
     *
182
     * @return array
183
     */
184
    public function getErrorInfo(array $itemArray, array $itemInfo)
185
    {
186
        $errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
187
        $errorInfo['conditional_version'] = '';
188
        $errorInfo['condition']           = '';
189
190
        $versionArray = $this->getVersionArray($itemArray);
191
192
        if (empty($versionArray) === false) {
193 View Code Duplication
            foreach ($versionArray as $version => $present) {
1 ignored issue
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...
194
                if (is_string($present) === true && $this->supportsBelow($version) === true) {
195
                    // We cannot test for compilation option (ok, except by scraping the output of phpinfo...).
196
                    $errorInfo['conditional_version'] = $version;
197
                    $errorInfo['condition']           = $present;
198
                }
199
            }
200
        }
201
202
        return $errorInfo;
203
    }
204
205
206
    /**
207
     * Get the error message template for this sniff.
208
     *
209
     * @return string
210
     */
211
    protected function getErrorMsgTemplate()
212
    {
213
        return 'Directive '.parent::getErrorMsgTemplate();
214
    }
215
216
217
    /**
218
     * Generates the error or warning for this item.
219
     *
220
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
221
     * @param int                  $stackPtr  The position of the relevant token in
222
     *                                        the stack.
223
     * @param array                $itemInfo  Base information about the item.
224
     * @param array                $errorInfo Array with detail (version) information
225
     *                                        relevant to the item.
226
     *
227
     * @return void
228
     */
229
    public function addError(PHP_CodeSniffer_File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
230
    {
231
        if ($errorInfo['not_in_version'] !== '') {
232
            parent::addError($phpcsFile, $stackPtr, $itemInfo, $errorInfo);
233
        } else if ($errorInfo['conditional_version'] !== '') {
234
            $error     = 'Directive %s is present in PHP version %s but will be disregarded unless PHP is compiled with %s';
235
            $errorCode = $this->stringToErrorCode($itemInfo['name']).'WithConditionFound';
236
            $data      = array(
237
                $itemInfo['name'],
238
                $errorInfo['conditional_version'],
239
                $errorInfo['condition'],
240
            );
241
242
            $phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
243
        }
244
245
    }//end addError()
246
247
248
    /**
249
     * Generates a error or warning for this sniff.
250
     *
251
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
252
     * @param int                  $stackPtr  The position of the execution directive value
253
     *                                        in the token array.
254
     * @param string               $directive The directive.
255
     *
256
     * @return void
257
     */
258
    protected function addWarningOnInvalidValue($phpcsFile, $stackPtr, $directive)
259
    {
260
        $tokens = $phpcsFile->getTokens();
261
262
        $value = $tokens[$stackPtr]['content'];
263
        if ($tokens[$stackPtr]['code'] === T_CONSTANT_ENCAPSED_STRING) {
264
            $value = $this->stripQuotes($value);
265
        }
266
267
        $isError = false;
268
        if (isset($this->newDirectives[$directive]['valid_values'])) {
269
            if (in_array($value, $this->newDirectives[$directive]['valid_values']) === false) {
270
                $isError = true;
271
            }
272
        }
273
        else if (isset($this->newDirectives[$directive]['valid_value_callback'])) {
274
            $valid = call_user_func(array($this, $this->newDirectives[$directive]['valid_value_callback']), $value);
275
            if ($valid === false) {
276
                $isError = true;
277
            }
278
        }
279
280
        if ($isError === true) {
281
            $error     = 'The execution directive %s does not seem to have a valid value. Please review. Found: %s';
282
            $errorCode = $this->stringToErrorCode($directive).'InvalidValueFound';
283
            $data      = array(
284
                $directive,
285
                $value,
286
            );
287
288
            $phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
289
        }
290
    }// addErrorOnInvalidValue()
291
292
293
    /**
294
     * Check whether a value is numeric.
295
     *
296
     * Callback function to test whether the value for an execution directive is valid.
297
     *
298
     * @param mixed $value The value to test.
299
     *
300
     * @return bool
301
     */
302
    protected function isNumeric($value)
303
    {
304
        return is_numeric($value);
305
    }
306
307
308
    /**
309
     * Check whether a value is valid encoding.
310
     *
311
     * Callback function to test whether the value for an execution directive is valid.
312
     *
313
     * @param mixed $value The value to test.
314
     *
315
     * @return bool
316
     */
317
    protected function validEncoding($value)
318
    {
319
        $encodings = array();
320
        if (function_exists('mb_list_encodings')) {
321
            $encodings = mb_list_encodings();
322
        }
323
324
        if (empty($encodings)) {
325
            // If we can't test the encoding, let it pass through.
326
            return true;
327
        }
328
329
        if (in_array($value, $encodings, true)) {
330
            return true;
331
        }
332
        else {
333
            return false;
334
        }
335
    }
336
337
338
}//end class
339