Skip to content

Commit 6f4489a

Browse files
phpstan-botVincentLangletclaude
authored
Fix phpstan/phpstan#14397: FILTER_FLAG_GLOBAL_RANGE constant not "known" on PHP 8.3, 8.4 (#5334)
Co-authored-by: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Langlet <vincentlanglet@hotmail.fr>
1 parent 8b36ae3 commit 6f4489a

File tree

8 files changed

+111
-18
lines changed

8 files changed

+111
-18
lines changed

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,4 +501,9 @@ public function supportsObjectsInArraySumProduct(): bool
501501
return $this->versionId >= 80300;
502502
}
503503

504+
public function hasFilterThrowOnFailureConstant(): bool
505+
{
506+
return $this->versionId >= 80500;
507+
}
508+
504509
}

src/Rules/Functions/FilterVarRule.php

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpParser\Node\Name;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\DependencyInjection\RegisteredRule;
10+
use PHPStan\Php\PhpVersion;
1011
use PHPStan\Reflection\ReflectionProvider;
1112
use PHPStan\Rules\Rule;
1213
use PHPStan\Rules\RuleErrorBuilder;
@@ -23,6 +24,7 @@ final class FilterVarRule implements Rule
2324
public function __construct(
2425
private ReflectionProvider $reflectionProvider,
2526
private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper,
27+
private PhpVersion $phpVersion,
2628
)
2729
{
2830
}
@@ -44,23 +46,28 @@ public function processNode(Node $node, Scope $scope): array
4446

4547
$args = $node->getArgs();
4648

47-
if ($this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)) {
48-
if (count($args) < 3) {
49-
return [];
50-
}
49+
if (count($args) < 3) {
50+
return [];
51+
}
52+
53+
if (
54+
!$this->phpVersion->hasFilterThrowOnFailureConstant()
55+
|| !$this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)
56+
) {
57+
return [];
58+
}
5159

52-
$flagsType = $scope->getType($args[2]->value);
60+
$flagsType = $scope->getType($args[2]->value);
5361

54-
if ($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_NULL_ON_FAILURE', $flagsType)
55-
->and($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType))
56-
->yes()
57-
) {
58-
return [
59-
RuleErrorBuilder::message('Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.')
60-
->identifier('filterVar.nullOnFailureAndThrowOnFailure')
61-
->build(),
62-
];
63-
}
62+
if ($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_NULL_ON_FAILURE', $flagsType)
63+
->and($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType))
64+
->yes()
65+
) {
66+
return [
67+
RuleErrorBuilder::message('Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.')
68+
->identifier('filterVar.nullOnFailureAndThrowOnFailure')
69+
->build(),
70+
];
6471
}
6572

6673
return [];

src/Type/Php/FilterFunctionReturnTypeHelper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
151151

152152
$inputIsArray = $inputType->isArray();
153153
$hasRequireArrayFlag = $this->hasFlag('FILTER_REQUIRE_ARRAY', $flagsType);
154-
$hasThrowOnFailureFlag = $this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType);
154+
$hasThrowOnFailureFlag = $this->phpVersion->hasFilterThrowOnFailureConstant()
155+
? $this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)
156+
: TrinaryLogic::createNo();
155157
if ($inputIsArray->no() && $hasRequireArrayFlag->yes()) {
156158
if ($hasThrowOnFailureFlag->yes()) {
157159
return new ErrorType();

src/Type/Php/FilterVarThrowTypeExtension.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Name;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\DependencyInjection\AutowiredService;
9+
use PHPStan\Php\PhpVersion;
910
use PHPStan\Reflection\FunctionReflection;
1011
use PHPStan\Reflection\ReflectionProvider;
1112
use PHPStan\Type\Constant\ConstantIntegerType;
@@ -20,6 +21,7 @@ final class FilterVarThrowTypeExtension implements DynamicFunctionThrowTypeExten
2021

2122
public function __construct(
2223
private ReflectionProvider $reflectionProvider,
24+
private PhpVersion $phpVersion,
2325
)
2426
{
2527
}
@@ -28,8 +30,7 @@ public function isFunctionSupported(
2830
FunctionReflection $functionReflection,
2931
): bool
3032
{
31-
return $functionReflection->getName() === 'filter_var'
32-
&& $this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null);
33+
return $functionReflection->getName() === 'filter_var';
3334
}
3435

3536
public function getThrowTypeFromFunctionCall(
@@ -42,6 +43,13 @@ public function getThrowTypeFromFunctionCall(
4243
return null;
4344
}
4445

46+
if (
47+
!$this->phpVersion->hasFilterThrowOnFailureConstant()
48+
|| !$this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)
49+
) {
50+
return null;
51+
}
52+
4553
$flagsExpr = $funcCall->getArgs()[3]->value;
4654
$flagsType = $scope->getType($flagsExpr);
4755

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php // lint >= 8.2
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14397;
6+
7+
use PHPStan\TrinaryLogic;
8+
9+
use function filter_var;
10+
use function PHPStan\Testing\assertType;
11+
use function PHPStan\Testing\assertVariableCertainty;
12+
13+
function test(string $ipAddress): void
14+
{
15+
assertType('non-falsy-string|false', filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_GLOBAL_RANGE));
16+
}
17+
18+
function test2(mixed $mixed): void
19+
{
20+
try {
21+
filter_var($mixed, FILTER_VALIDATE_INT, FILTER_FLAG_GLOBAL_RANGE);
22+
$foo = 1;
23+
} catch (\Filter\FilterFailedException $e) {
24+
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
25+
}
26+
27+
assertType('int|false', filter_var($mixed, FILTER_VALIDATE_INT, FILTER_FLAG_GLOBAL_RANGE));
28+
assertType('int|false', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FLAG_GLOBAL_RANGE]));
29+
}

tests/PHPStan/Rules/Functions/FilterVarRuleTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Functions;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78
use PHPStan\Type\Php\FilterFunctionReturnTypeHelper;
@@ -16,6 +17,7 @@ protected function getRule(): Rule
1617
return new FilterVarRule(
1718
self::createReflectionProvider(),
1819
self::getContainer()->getByType(FilterFunctionReturnTypeHelper::class),
20+
self::getContainer()->getByType(PhpVersion::class),
1921
);
2022
}
2123

@@ -29,4 +31,16 @@ public function testRule(): void
2931
]);
3032
}
3133

34+
#[RequiresPhp('>= 8.2')]
35+
public function testRuleWithGlobalRange(): void
36+
{
37+
$this->analyse([__DIR__ . '/data/filter_var_null_and_throw_global_range.php'], []);
38+
}
39+
40+
#[RequiresPhp('>= 8.5')]
41+
public function testRuleGlobalRangePhp85(): void
42+
{
43+
$this->analyse([__DIR__ . '/data/filter_var_null_and_global_range_php85.php'], []);
44+
}
45+
3246
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 8.5
2+
3+
namespace FilterVarNullAndGlobalRangePhp85;
4+
5+
filter_var('foo@bar.test', FILTER_VALIDATE_EMAIL, FILTER_FLAG_GLOBAL_RANGE|FILTER_NULL_ON_FAILURE);
6+
7+
$flag = FILTER_NULL_ON_FAILURE|FILTER_FLAG_GLOBAL_RANGE;
8+
filter_var(100, FILTER_VALIDATE_INT, $flag);
9+
10+
filter_var(
11+
'johndoe',
12+
FILTER_VALIDATE_REGEXP,
13+
['options' => ['regexp' => '/^[a-z]+$/'], 'flags' => FILTER_FLAG_GLOBAL_RANGE|FILTER_NULL_ON_FAILURE]
14+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 8.2
2+
3+
namespace FilterVarNullAndThrowGlobalRange;
4+
5+
filter_var('foo@bar.test', FILTER_VALIDATE_EMAIL, FILTER_FLAG_GLOBAL_RANGE|FILTER_NULL_ON_FAILURE);
6+
7+
$flag = FILTER_NULL_ON_FAILURE|FILTER_FLAG_GLOBAL_RANGE;
8+
filter_var(100, FILTER_VALIDATE_INT, $flag);
9+
10+
filter_var(
11+
'johndoe',
12+
FILTER_VALIDATE_REGEXP,
13+
['options' => ['regexp' => '/^[a-z]+$/'], 'flags' => FILTER_FLAG_GLOBAL_RANGE|FILTER_NULL_ON_FAILURE]
14+
);

0 commit comments

Comments
 (0)