Conditions | 68 |
Paths | > 20000 |
Total Lines | 319 |
Lines | 55 |
Ratio | 17.24 % |
Changes | 0 |
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:
If many parameters/temporary variables are present:
1 | <?php |
||
120 | public function process(File $phpcsFile, $stackPtr) |
||
121 | { |
||
122 | if ($this->supportsAbove('7.0') === false) { |
||
123 | return; |
||
124 | } |
||
125 | |||
126 | $tokens = $phpcsFile->getTokens(); |
||
127 | |||
128 | if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { |
||
129 | // Abstract function, interface function, live coding or parse error. |
||
130 | return; |
||
131 | } |
||
132 | |||
133 | $scopeOpener = $tokens[$stackPtr]['scope_opener']; |
||
134 | $scopeCloser = $tokens[$stackPtr]['scope_closer']; |
||
135 | |||
136 | // Does the function declaration have parameters ? |
||
137 | $params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr); |
||
138 | if (empty($params)) { |
||
139 | // No named arguments found, so no risk of them being changed. |
||
140 | return; |
||
141 | } |
||
142 | |||
143 | $paramNames = array(); |
||
144 | foreach ($params as $param) { |
||
145 | $paramNames[] = $param['name']; |
||
146 | } |
||
147 | |||
148 | for ($i = ($scopeOpener + 1); $i < $scopeCloser; $i++) { |
||
149 | View Code Duplication | if (isset($this->skipPastNested[$tokens[$i]['type']]) && isset($tokens[$i]['scope_closer'])) { |
|
|
|||
150 | // Skip past nested structures. |
||
151 | $i = $tokens[$i]['scope_closer']; |
||
152 | continue; |
||
153 | } |
||
154 | |||
155 | if ($tokens[$i]['code'] !== T_STRING) { |
||
156 | continue; |
||
157 | } |
||
158 | |||
159 | $foundFunctionName = strtolower($tokens[$i]['content']); |
||
160 | |||
161 | if (isset($this->changedFunctions[$foundFunctionName]) === false) { |
||
162 | // Not one of the target functions. |
||
163 | continue; |
||
164 | } |
||
165 | |||
166 | /* |
||
167 | * Ok, so is this really a function call to one of the PHP native functions ? |
||
168 | */ |
||
169 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true); |
||
170 | if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { |
||
171 | // Live coding, parse error or not a function call. |
||
172 | continue; |
||
173 | } |
||
174 | |||
175 | $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true); |
||
176 | if ($prev !== false) { |
||
177 | if (isset($this->noneFunctionCallIndicators[$tokens[$prev]['code']])) { |
||
178 | continue; |
||
179 | } |
||
180 | |||
181 | // Check for namespaced functions, ie: \foo\bar() not \bar(). |
||
182 | View Code Duplication | if ($tokens[ $prev ]['code'] === T_NS_SEPARATOR) { |
|
183 | $pprev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); |
||
184 | if ($pprev !== false && $tokens[ $pprev ]['code'] === T_STRING) { |
||
185 | continue; |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | |||
190 | /* |
||
191 | * Address some special cases. |
||
192 | */ |
||
193 | if ($foundFunctionName !== 'func_get_args') { |
||
194 | $paramOne = $this->getFunctionCallParameter($phpcsFile, $i, 1); |
||
195 | if ($paramOne !== false) { |
||
196 | switch ($foundFunctionName) { |
||
197 | /* |
||
198 | * Check if `debug_(print_)backtrace()` is called with the |
||
199 | * `DEBUG_BACKTRACE_IGNORE_ARGS` option. |
||
200 | */ |
||
201 | case 'debug_backtrace': |
||
202 | case 'debug_print_backtrace': |
||
203 | $hasIgnoreArgs = $phpcsFile->findNext( |
||
204 | T_STRING, |
||
205 | $paramOne['start'], |
||
206 | ($paramOne['end'] + 1), |
||
207 | false, |
||
208 | 'DEBUG_BACKTRACE_IGNORE_ARGS' |
||
209 | ); |
||
210 | |||
211 | if ($hasIgnoreArgs !== false) { |
||
212 | // Debug_backtrace() called with ignore args option. |
||
213 | continue 2; |
||
214 | } |
||
215 | break; |
||
216 | |||
217 | /* |
||
218 | * Collect the necessary information to only throw a notice if the argument |
||
219 | * touched/changed is in line with the passed $arg_num. |
||
220 | * |
||
221 | * Also, we can ignore `func_get_arg()` if the argument offset passed is |
||
222 | * higher than the number of named parameters. |
||
223 | * |
||
224 | * {@internal Note: This does not take calculations into account! |
||
225 | * Should be exceptionally rare and can - if needs be - be addressed at a later stage.}} |
||
226 | */ |
||
227 | case 'func_get_arg': |
||
228 | $number = $phpcsFile->findNext(T_LNUMBER, $paramOne['start'], ($paramOne['end'] + 1)); |
||
229 | View Code Duplication | if ($number !== false) { |
|
230 | $argNumber = $tokens[$number]['content']; |
||
231 | |||
232 | if (isset($paramNames[$argNumber]) === false) { |
||
233 | // Requesting a non-named additional parameter. Ignore. |
||
234 | continue 2; |
||
235 | } |
||
236 | } |
||
237 | break; |
||
238 | } |
||
239 | } |
||
240 | } else { |
||
241 | /* |
||
242 | * Check if the call to func_get_args() happens to be in an array_slice() or |
||
243 | * array_splice() with an $offset higher than the number of named parameters. |
||
244 | * In that case, we can ignore it. |
||
245 | * |
||
246 | * {@internal Note: This does not take offset calculations into account! |
||
247 | * Should be exceptionally rare and can - if needs be - be addressed at a later stage.}} |
||
248 | */ |
||
249 | if ($prev !== false && $tokens[$prev]['code'] === T_OPEN_PARENTHESIS) { |
||
250 | |||
251 | $maybeFunctionCall = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); |
||
252 | if ($maybeFunctionCall !== false |
||
253 | && $tokens[$maybeFunctionCall]['code'] === T_STRING |
||
254 | && ($tokens[$maybeFunctionCall]['content'] === 'array_slice' |
||
255 | || $tokens[$maybeFunctionCall]['content'] === 'array_splice') |
||
256 | ) { |
||
257 | $parentFuncParamTwo = $this->getFunctionCallParameter($phpcsFile, $maybeFunctionCall, 2); |
||
258 | $number = $phpcsFile->findNext( |
||
259 | T_LNUMBER, |
||
260 | $parentFuncParamTwo['start'], |
||
261 | ($parentFuncParamTwo['end'] + 1) |
||
262 | ); |
||
263 | |||
264 | View Code Duplication | if ($number !== false && isset($paramNames[$tokens[$number]['content']]) === false) { |
|
265 | // Requesting non-named additional parameters. Ignore. |
||
266 | continue ; |
||
267 | } |
||
268 | |||
269 | // Slice starts at a named argument, but we know which params are being accessed. |
||
270 | $paramNamesSubset = array_slice($paramNames, $tokens[$number]['content']); |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | |||
275 | /* |
||
276 | * For debug_backtrace(), check if the result is being dereferenced and if so, |
||
277 | * whether the `args` index is used. |
||
278 | * I.e. whether `$index` in `debug_backtrace()[$stackFrame][$index]` is a string |
||
279 | * with the content `args`. |
||
280 | * |
||
281 | * Note: We already know that $next is the open parenthesis of the function call. |
||
282 | */ |
||
283 | if ($foundFunctionName === 'debug_backtrace' && isset($tokens[$next]['parenthesis_closer'])) { |
||
284 | $afterParenthesis = $phpcsFile->findNext( |
||
285 | Tokens::$emptyTokens, |
||
286 | ($tokens[$next]['parenthesis_closer'] + 1), |
||
287 | null, |
||
288 | true |
||
289 | ); |
||
290 | |||
291 | if ($tokens[$afterParenthesis]['code'] === T_OPEN_SQUARE_BRACKET |
||
292 | && isset($tokens[$afterParenthesis]['bracket_closer']) |
||
293 | ) { |
||
294 | $afterStackFrame = $phpcsFile->findNext( |
||
295 | Tokens::$emptyTokens, |
||
296 | ($tokens[$afterParenthesis]['bracket_closer'] + 1), |
||
297 | null, |
||
298 | true |
||
299 | ); |
||
300 | |||
301 | if ($tokens[$afterStackFrame]['code'] === T_OPEN_SQUARE_BRACKET |
||
302 | && isset($tokens[$afterStackFrame]['bracket_closer']) |
||
303 | ) { |
||
304 | $arrayIndex = $phpcsFile->findNext( |
||
305 | T_CONSTANT_ENCAPSED_STRING, |
||
306 | ($afterStackFrame + 1), |
||
307 | $tokens[$afterStackFrame]['bracket_closer'] |
||
308 | ); |
||
309 | |||
310 | if ($arrayIndex !== false && $this->stripQuotes($tokens[$arrayIndex]['content']) !== 'args') { |
||
311 | continue; |
||
312 | } |
||
313 | } |
||
314 | } |
||
315 | } |
||
316 | |||
317 | /* |
||
318 | * Only check for variables before the start of the statement to |
||
319 | * prevent false positives on the return value of the function call |
||
320 | * being assigned to one of the parameters, i.e.: |
||
321 | * `$param = func_get_args();`. |
||
322 | */ |
||
323 | $startOfStatement = PHPCSHelper::findStartOfStatement($phpcsFile, $i, $this->ignoreForStartOfStatement); |
||
324 | |||
325 | /* |
||
326 | * Ok, so we've found one of the target functions in the right scope. |
||
327 | * Now, let's check if any of the passed parameters were touched. |
||
328 | */ |
||
329 | $scanResult = 'clean'; |
||
330 | for ($j = ($scopeOpener + 1); $j < $startOfStatement; $j++) { |
||
331 | View Code Duplication | if (isset($this->skipPastNested[$tokens[$j]['type']]) |
|
332 | && isset($tokens[$j]['scope_closer']) |
||
333 | ) { |
||
334 | // Skip past nested structures. |
||
335 | $j = $tokens[$j]['scope_closer']; |
||
336 | continue; |
||
337 | } |
||
338 | |||
339 | if ($tokens[$j]['code'] !== T_VARIABLE) { |
||
340 | continue; |
||
341 | } |
||
342 | |||
343 | if ($foundFunctionName === 'func_get_arg' && isset($argNumber)) { |
||
344 | if (isset($paramNames[$argNumber]) |
||
345 | && $tokens[$j]['content'] !== $paramNames[$argNumber] |
||
346 | ) { |
||
347 | // Different param than the one requested by func_get_arg(). |
||
348 | continue; |
||
349 | } |
||
350 | } elseif ($foundFunctionName === 'func_get_args' && isset($paramNamesSubset)) { |
||
351 | if (in_array($tokens[$j]['content'], $paramNamesSubset, true) === false) { |
||
352 | // Different param than the ones requested by func_get_args(). |
||
353 | continue; |
||
354 | } |
||
355 | } elseif (in_array($tokens[$j]['content'], $paramNames, true) === false) { |
||
356 | // Variable is not one of the function parameters. |
||
357 | continue; |
||
358 | } |
||
359 | |||
360 | /* |
||
361 | * Ok, so we've found a variable which was passed as one of the parameters. |
||
362 | * Now, is this variable being changed, i.e. incremented, decremented or |
||
363 | * assigned something ? |
||
364 | */ |
||
365 | $scanResult = 'warning'; |
||
366 | if (isset($variableToken) === false) { |
||
367 | $variableToken = $j; |
||
368 | } |
||
369 | |||
370 | $beforeVar = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($j - 1), null, true); |
||
371 | View Code Duplication | if ($beforeVar !== false && isset($this->plusPlusMinusMinus[$tokens[$beforeVar]['code']])) { |
|
372 | // Variable is being (pre-)incremented/decremented. |
||
373 | $scanResult = 'error'; |
||
374 | $variableToken = $j; |
||
375 | break; |
||
376 | } |
||
377 | |||
378 | $afterVar = $phpcsFile->findNext(Tokens::$emptyTokens, ($j + 1), null, true); |
||
379 | if ($afterVar === false) { |
||
380 | // Shouldn't be possible, but just in case. |
||
381 | continue; |
||
382 | } |
||
383 | |||
384 | View Code Duplication | if (isset($this->plusPlusMinusMinus[$tokens[$afterVar]['code']])) { |
|
385 | // Variable is being (post-)incremented/decremented. |
||
386 | $scanResult = 'error'; |
||
387 | $variableToken = $j; |
||
388 | break; |
||
389 | } |
||
390 | |||
391 | if ($tokens[$afterVar]['code'] === T_OPEN_SQUARE_BRACKET |
||
392 | && isset($tokens[$afterVar]['bracket_closer']) |
||
393 | ) { |
||
394 | // Skip past array access on the variable. |
||
395 | while (($afterVar = $phpcsFile->findNext(Tokens::$emptyTokens, ($tokens[$afterVar]['bracket_closer'] + 1), null, true)) !== false) { |
||
396 | View Code Duplication | if ($tokens[$afterVar]['code'] !== T_OPEN_SQUARE_BRACKET |
|
397 | || isset($tokens[$afterVar]['bracket_closer']) === false |
||
398 | ) { |
||
399 | break; |
||
400 | } |
||
401 | } |
||
402 | } |
||
403 | |||
404 | View Code Duplication | if ($afterVar !== false |
|
405 | && isset(Tokens::$assignmentTokens[$tokens[$afterVar]['code']]) |
||
406 | ) { |
||
407 | // Variable is being assigned something. |
||
408 | $scanResult = 'error'; |
||
409 | $variableToken = $j; |
||
410 | break; |
||
411 | } |
||
412 | } |
||
413 | |||
414 | unset($argNumber, $paramNamesSubset); |
||
415 | |||
416 | if ($scanResult === 'clean') { |
||
417 | continue; |
||
418 | } |
||
419 | |||
420 | $error = 'Since PHP 7.0, functions inspecting arguments, like %1$s(), no longer report the original value as passed to a parameter, but will instead provide the current value. The parameter "%2$s" was %4$s on line %3$s.'; |
||
421 | $data = array( |
||
422 | $foundFunctionName, |
||
423 | $tokens[$variableToken]['content'], |
||
424 | $tokens[$variableToken]['line'], |
||
425 | ); |
||
426 | |||
427 | if ($scanResult === 'error') { |
||
428 | $data[] = 'changed'; |
||
429 | $phpcsFile->addError($error, $i, 'Changed', $data); |
||
430 | |||
431 | } elseif ($scanResult === 'warning') { |
||
432 | $data[] = 'used, and possibly changed (by reference),'; |
||
433 | $phpcsFile->addWarning($error, $i, 'NeedsInspection', $data); |
||
434 | } |
||
435 | |||
436 | unset($variableToken); |
||
437 | } |
||
438 | } |
||
439 | } |
||
440 |
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.