Skip to content

Commit 193d801

Browse files
canvuralondrejmirtes
authored andcommitted
add support for FILTER_THROW_ON_FAILURE for filter_var
1 parent 391711c commit 193d801

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

src/Type/Php/FilterFunctionReturnTypeHelper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Type\Constant\ConstantIntegerType;
1717
use PHPStan\Type\Constant\ConstantStringType;
1818
use PHPStan\Type\ConstantScalarType;
19+
use PHPStan\Type\ErrorType;
1920
use PHPStan\Type\FloatType;
2021
use PHPStan\Type\IntegerRangeType;
2122
use PHPStan\Type\IntegerType;
@@ -129,6 +130,10 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
129130
$inputIsArray = $inputType->isArray();
130131
$hasRequireArrayFlag = $this->hasFlag('FILTER_REQUIRE_ARRAY', $flagsType);
131132
if ($inputIsArray->no() && $hasRequireArrayFlag) {
133+
if ($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)) {
134+
return new ErrorType();
135+
}
136+
132137
return $defaultType;
133138
}
134139

@@ -174,6 +179,10 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
174179
return new ArrayType($inputArrayKeyType ?? $mixedType, $type);
175180
}
176181

182+
if ($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)) {
183+
$type = TypeCombinator::remove($type, $defaultType);
184+
}
185+
177186
return $type;
178187
}
179188

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PhpParser\Node\Name;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\DependencyInjection\AutowiredService;
9+
use PHPStan\Reflection\FunctionReflection;
10+
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Type\Constant\ConstantIntegerType;
12+
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\DynamicFunctionThrowTypeExtension;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
17+
#[AutowiredService]
18+
final class FilterVarThrowTypeExtension implements DynamicFunctionThrowTypeExtension
19+
{
20+
21+
public function __construct(
22+
private ReflectionProvider $reflectionProvider,
23+
)
24+
{
25+
}
26+
27+
public function isFunctionSupported(
28+
FunctionReflection $functionReflection,
29+
): bool
30+
{
31+
return $functionReflection->getName() === 'filter_var'
32+
&& $this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null);
33+
}
34+
35+
public function getThrowTypeFromFunctionCall(
36+
FunctionReflection $functionReflection,
37+
FuncCall $funcCall,
38+
Scope $scope,
39+
): ?Type
40+
{
41+
if (!isset($funcCall->getArgs()[3])) {
42+
return null;
43+
}
44+
45+
$flagsExpr = $funcCall->getArgs()[3]->value;
46+
$flagsType = $scope->getType($flagsExpr);
47+
48+
if ($flagsType->isConstantArray()->yes()) {
49+
$flagsType = $flagsType->getOffsetValueType(new ConstantStringType('flags'));
50+
}
51+
52+
$flag = $this->getConstant();
53+
54+
if ($flag !== null && $flagsType instanceof ConstantIntegerType && ($flagsType->getValue() & $flag) === $flag) {
55+
return new ObjectType('Filter\FilterFailedException');
56+
}
57+
58+
return null;
59+
}
60+
61+
private function getConstant(): ?int
62+
{
63+
$constant = $this->reflectionProvider->getConstant(new Name('FILTER_THROW_ON_FAILURE'), null);
64+
$valueType = $constant->getValueType();
65+
if (!$valueType instanceof ConstantIntegerType) {
66+
return null;
67+
}
68+
69+
return $valueType->getValue();
70+
}
71+
72+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint >= 8.5
2+
3+
declare(strict_types=1);
4+
5+
namespace FilterVarPHP85;
6+
7+
use PHPStan\TrinaryLogic;
8+
use function PHPStan\Testing\assertType;
9+
use function PHPStan\Testing\assertVariableCertainty;
10+
11+
class FilterVarPHP85
12+
{
13+
14+
public function doFoo($mixed): void
15+
{
16+
try {
17+
filter_var($mixed, FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE);
18+
$foo = 1;
19+
} catch (\Filter\FilterFailedException $e) {
20+
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
21+
}
22+
23+
assertType('int', filter_var($mixed, FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE));
24+
assertType('int', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_THROW_ON_FAILURE]));
25+
}
26+
}

0 commit comments

Comments
 (0)