diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index e0d7f63cdf..2f351a3fa3 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -16,6 +16,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; +use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; @@ -129,6 +130,10 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T $inputIsArray = $inputType->isArray(); $hasRequireArrayFlag = $this->hasFlag('FILTER_REQUIRE_ARRAY', $flagsType); if ($inputIsArray->no() && $hasRequireArrayFlag) { + if ($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)) { + return new ErrorType(); + } + return $defaultType; } @@ -174,6 +179,10 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T return new ArrayType($inputArrayKeyType ?? $mixedType, $type); } + if ($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)) { + $type = TypeCombinator::remove($type, $defaultType); + } + return $type; } diff --git a/src/Type/Php/FilterVarThrowTypeExtension.php b/src/Type/Php/FilterVarThrowTypeExtension.php new file mode 100644 index 0000000000..677a90a2b1 --- /dev/null +++ b/src/Type/Php/FilterVarThrowTypeExtension.php @@ -0,0 +1,72 @@ +getName() === 'filter_var' + && $this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null); + } + + public function getThrowTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $funcCall, + Scope $scope, + ): ?Type + { + if (!isset($funcCall->getArgs()[3])) { + return null; + } + + $flagsExpr = $funcCall->getArgs()[3]->value; + $flagsType = $scope->getType($flagsExpr); + + if ($flagsType->isConstantArray()->yes()) { + $flagsType = $flagsType->getOffsetValueType(new ConstantStringType('flags')); + } + + $flag = $this->getConstant(); + + if ($flag !== null && $flagsType instanceof ConstantIntegerType && ($flagsType->getValue() & $flag) === $flag) { + return new ObjectType('Filter\FilterFailedException'); + } + + return null; + } + + private function getConstant(): ?int + { + $constant = $this->reflectionProvider->getConstant(new Name('FILTER_THROW_ON_FAILURE'), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + return null; + } + + return $valueType->getValue(); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/filter-var-php85.php b/tests/PHPStan/Analyser/nsrt/filter-var-php85.php new file mode 100644 index 0000000000..e9b2a6fb49 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/filter-var-php85.php @@ -0,0 +1,26 @@ += 8.5 + +declare(strict_types=1); + +namespace FilterVarPHP85; + +use PHPStan\TrinaryLogic; +use function PHPStan\Testing\assertType; +use function PHPStan\Testing\assertVariableCertainty; + +class FilterVarPHP85 +{ + + public function doFoo($mixed): void + { + try { + filter_var($mixed, FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE); + $foo = 1; + } catch (\Filter\FilterFailedException $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } + + assertType('int', filter_var($mixed, FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE)); + assertType('int', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_THROW_ON_FAILURE])); + } +}