Skip to content

Commit f37cf89

Browse files
committed
Check - overwriting scope variables with foreach
1 parent f880613 commit f37cf89

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* Statically declared methods are called statically.
2020
* Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one.
2121
* Disallow variable variables (`$$foo`, `$this->$method()` etc.)
22+
* Disallow overwriting variables with foreach key and value variables
2223
* Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`/`checkAlwaysTrueCheckTypeFunctionCall`/`checkAlwaysTrueStrictComparison` to false.
2324
* Require parameter and return typehints for functions and methods (either native or phpDocs)
2425
* Correct case for referenced and called function names.

rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ rules:
1717
- PHPStan\Rules\Cast\UselessCastRule
1818
- PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule
1919
- PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule
20+
- PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule
2021
- PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule
2122
- PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule
2223
- PHPStan\Rules\Methods\MissingMethodParameterTypehintRule
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\ForeachLoop;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
10+
class OverwriteVariablesWithForeachRule implements Rule
11+
{
12+
13+
public function getNodeType(): string
14+
{
15+
return Node\Stmt\Foreach_::class;
16+
}
17+
18+
/**
19+
* @param \PhpParser\Node\Stmt\Foreach_ $node
20+
* @param \PHPStan\Analyser\Scope $scope
21+
* @return string[]
22+
*/
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
$errors = [];
26+
if (
27+
$node->keyVar instanceof Node\Expr\Variable
28+
&& is_string($node->keyVar->name)
29+
&& $scope->hasVariableType($node->keyVar->name)->yes()
30+
) {
31+
$errors[] = sprintf('Foreach overwrites $%s with its key variable.', $node->keyVar->name);
32+
}
33+
34+
foreach ($this->checkValueVar($scope, $node->valueVar) as $error) {
35+
$errors[] = $error;
36+
}
37+
38+
return $errors;
39+
}
40+
41+
/**
42+
* @param Scope $scope
43+
* @param Expr $expr
44+
* @return string[]
45+
*/
46+
private function checkValueVar(Scope $scope, Expr $expr): array
47+
{
48+
$errors = [];
49+
if (
50+
$expr instanceof Node\Expr\Variable
51+
&& is_string($expr->name)
52+
&& $scope->hasVariableType($expr->name)->yes()
53+
) {
54+
$errors[] = sprintf('Foreach overwrites $%s with its value variable.', $expr->name);
55+
}
56+
57+
if (
58+
$expr instanceof Node\Expr\List_
59+
|| $expr instanceof Node\Expr\Array_
60+
) {
61+
foreach ($expr->items as $item) {
62+
if ($item === null) {
63+
continue;
64+
}
65+
66+
foreach ($this->checkValueVar($scope, $item->value) as $error) {
67+
$errors[] = $error;
68+
}
69+
}
70+
}
71+
72+
return $errors;
73+
}
74+
75+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\ForeachLoop;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
class OverwriteVariablesWithForeachRuleTest extends RuleTestCase
9+
{
10+
11+
protected function getRule(): Rule
12+
{
13+
return new OverwriteVariablesWithForeachRule();
14+
}
15+
16+
public function testRule(): void
17+
{
18+
$this->analyse([__DIR__ . '/data/foreach.php'], [
19+
[
20+
'Foreach overwrites $str with its value variable.',
21+
14,
22+
],
23+
[
24+
'Foreach overwrites $b with its value variable.',
25+
26,
26+
],
27+
[
28+
'Foreach overwrites $d with its value variable.',
29+
26,
30+
],
31+
[
32+
'Foreach overwrites $b with its value variable.',
33+
32,
34+
],
35+
[
36+
'Foreach overwrites $d with its value variable.',
37+
32,
38+
],
39+
]);
40+
}
41+
42+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace OverwriteVariablesWithForeach;
4+
5+
class Foo
6+
{
7+
8+
public function doFoo(array $a, array $b, string $str)
9+
{
10+
foreach ($a as $val) {
11+
12+
}
13+
14+
foreach ($a as $str) {
15+
16+
}
17+
18+
foreach ($a as $val) {
19+
foreach ($b as $var) {
20+
21+
}
22+
}
23+
}
24+
25+
public function doBar(array $a, string $b, string $d) {
26+
foreach ($a as [$b, $c, [$d, $e]]) {
27+
28+
}
29+
}
30+
31+
public function doBaz(array $a, string $b, string $d) {
32+
foreach ($a as list($b, $c, list($d, $e))) {
33+
34+
}
35+
}
36+
37+
}

0 commit comments

Comments
 (0)