Skip to content

Commit 1eecbdb

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix phpstan/phpstan#11339: filter_input with FILTER_FORCE_ARRAY has incorrect return type
- When FILTER_FORCE_ARRAY or FILTER_REQUIRE_ARRAY is used, input values can be arbitrarily nested arrays (e.g. from query strings like ?a[a][b][]=val), so the array value type must include array when input could be an array - Updated FilterFunctionReturnTypeHelper::getType() to union ArrayType into the value type when inputIsArray is not no - New regression test in tests/PHPStan/Analyser/nsrt/bug-11339.php - Updated existing test expectations in filterVar.php, filter-var.php, filter-input.php, and discussion-9134.php to reflect the corrected types
1 parent d8f5be7 commit 1eecbdb

6 files changed

Lines changed: 198 additions & 166 deletions

File tree

src/Type/Php/FilterFunctionReturnTypeHelper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,19 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
198198
}
199199

200200
if ($hasRequireArrayFlag->yes()) {
201+
if (!$inputIsArray->no()) {
202+
$type = TypeCombinator::union($type, new ArrayType($mixedType, $mixedType));
203+
}
201204
$type = new ArrayType($inputArrayKeyType ?? $mixedType, $type);
202205
if (!$inputIsArray->yes()) {
203206
$type = TypeCombinator::union($type, $defaultType);
204207
}
205208
}
206209

207210
if ($hasRequireArrayFlag->no() && $hasForceArrayFlag->yes()) {
211+
if (!$inputIsArray->no()) {
212+
$type = TypeCombinator::union($type, new ArrayType($mixedType, $mixedType));
213+
}
208214
return new ArrayType($inputArrayKeyType ?? $mixedType, $type);
209215
}
210216

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11339;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
// assume query string to be ?a[a][b][]=a&a[a][b][]=b
8+
9+
$f = filter_input(INPUT_GET, 'a', FILTER_DEFAULT, FILTER_FORCE_ARRAY);
10+
assertType('array<array|string|false>|null', $f);
11+
12+
$g = filter_input(INPUT_GET, 'a', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
13+
assertType('array<array|string|false>|false|null', $g);
14+
15+
$h = filter_input(INPUT_GET, 'a', FILTER_VALIDATE_INT, FILTER_FORCE_ARRAY);
16+
assertType('array<array|int|false>|null', $h);
17+
18+
$i = filter_input(INPUT_GET, 'a', FILTER_VALIDATE_INT, FILTER_REQUIRE_ARRAY);
19+
assertType('array<array|int|false>|false|null', $i);
20+
21+
// filter_var with known scalar should not include array in value type
22+
$j = filter_var('foo', FILTER_DEFAULT, FILTER_FORCE_ARRAY);
23+
assertType("array<'foo'>", $j);
24+
25+
$k = filter_var('foo', FILTER_VALIDATE_INT, FILTER_FORCE_ARRAY);
26+
assertType('array<false>', $k);

tests/PHPStan/Analyser/nsrt/discussion-9134.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
$var = $_GET["data"];
88
$res = filter_var($var, FILTER_VALIDATE_INT, FILTER_REQUIRE_ARRAY);
9-
assertType('array<int|false>|false', $res);
9+
assertType('array<array|int|false>|false', $res);
1010
if (is_array($res) === false) {
1111
throw new \RuntimeException();
1212
}

tests/PHPStan/Analyser/nsrt/filter-input.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public function doFoo(string $foo): void
3434
assertType('int|false|null', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT));
3535
assertType('int|false|null', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['flags' => FILTER_NULL_ON_FAILURE]));
3636
assertType("'invalid'|int|null", filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['options' => ['default' => 'invalid']]));
37-
assertType('array<int|false>|null', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
38-
assertType('array<int|null>|false', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
37+
assertType('array<array|int|false>|null', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
38+
assertType('array<array|int|null>|false', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
3939
assertType('0|int<17, 19>|null', filter_input(INPUT_GET, 'foo', FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 17, 'max_range' => 19]]));
4040
}
4141

tests/PHPStan/Analyser/nsrt/filter-var.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,28 @@ public function doFoo($mixed, array $stringMixedMap): void
2323
assertType('null', filter_var(17, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
2424
assertType('false', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY]));
2525
assertType('null', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
26-
assertType('array<int|false>|false', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY]));
27-
assertType('array<int|null>|null', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
28-
assertType('array<string, int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY]));
29-
assertType('array<string, int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
26+
assertType('array<array|int|false>|false', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY]));
27+
assertType('array<array|int|null>|null', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
28+
assertType('array<string, array|int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY]));
29+
assertType('array<string, array|int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));
3030

3131
assertType('array<17>', filter_var(17, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
3232
assertType('array<17>', filter_var(17, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
3333
assertType('array<false>', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
3434
assertType('array<null>', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
35-
assertType('array<int|false>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
36-
assertType('array<int|null>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
37-
assertType('array<string, int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
38-
assertType('array<string, int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
35+
assertType('array<array|int|false>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
36+
assertType('array<array|int|null>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
37+
assertType('array<string, array|int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
38+
assertType('array<string, array|int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
3939

4040
assertType('false', filter_var(17, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
4141
assertType('null', filter_var(17, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
4242
assertType('false', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
4343
assertType('null', filter_var('foo', FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
44-
assertType('array<int|false>|false', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
45-
assertType('array<int|null>|null', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
46-
assertType('array<string, int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
47-
assertType('array<string, int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
44+
assertType('array<array|int|false>|false', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
45+
assertType('array<array|int|null>|null', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
46+
assertType('array<string, array|int|false>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY]));
47+
assertType('array<string, array|int|null>', filter_var($stringMixedMap, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
4848

4949
assertType('0|int<17, 19>', filter_var($mixed, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 17, 'max_range' => 19]]));
5050

0 commit comments

Comments
 (0)