Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e15cfcd

Browse files
BackEndTeaondrejmirtes
authored andcommitted
Add support for FILTER_NULL_ON_FAILURE flag in filter_var
1 parent 8e71e56 commit e15cfcd

File tree

4 files changed

+239
-27
lines changed

4 files changed

+239
-27
lines changed

‎build/composer-require-checker.json‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
"static", "self", "parent",
55
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object",
66
"PHPUnit\\Framework\\TestCase", "PHPUnit\\Framework\\AssertionFailedError",
7-
"JSON_THROW_ON_ERROR", "SimpleXMLElement", "PHPStan\\ExtensionInstaller\\GeneratedConfig", "Nette\\DI\\InvalidConfigurationException"
7+
"JSON_THROW_ON_ERROR", "SimpleXMLElement", "PHPStan\\ExtensionInstaller\\GeneratedConfig", "Nette\\DI\\InvalidConfigurationException",
8+
"FILTER_SANITIZE_EMAIL", "FILTER_SANITIZE_EMAIL", "FILTER_SANITIZE_ENCODED", "FILTER_SANITIZE_MAGIC_QUOTES", "FILTER_SANITIZE_NUMBER_FLOAT",
9+
"FILTER_SANITIZE_NUMBER_INT", "FILTER_SANITIZE_SPECIAL_CHARS", "FILTER_SANITIZE_STRING", "FILTER_SANITIZE_URL", "FILTER_VALIDATE_BOOLEAN",
10+
"FILTER_VALIDATE_EMAIL", "FILTER_VALIDATE_FLOAT", "FILTER_VALIDATE_INT", "FILTER_VALIDATE_IP", "FILTER_VALIDATE_MAC", "FILTER_VALIDATE_REGEXP",
11+
"FILTER_VALIDATE_URL", "FILTER_NULL_ON_FAILURE", "FILTER_FORCE_ARRAY"
812
],
913
"php-core-extensions" : [
1014
"Core",

‎src/Type/Php/FilterVarDynamicReturnTypeExtension.php‎

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,95 @@
22

33
namespace PHPStan\Type\Php;
44

5-
use PhpParser\Node\Expr\ConstFetch;
5+
use PhpParser\Node;
66
use PhpParser\Node\Expr\FuncCall;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\ArrayType;
910
use PHPStan\Type\BooleanType;
11+
use PHPStan\Type\Constant\ConstantArrayType;
1012
use PHPStan\Type\Constant\ConstantBooleanType;
13+
use PHPStan\Type\Constant\ConstantIntegerType;
14+
use PHPStan\Type\Constant\ConstantStringType;
1115
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1216
use PHPStan\Type\FloatType;
1317
use PHPStan\Type\IntegerType;
1418
use PHPStan\Type\MixedType;
19+
use PHPStan\Type\NullType;
1520
use PHPStan\Type\StringType;
1621
use PHPStan\Type\Type;
1722
use PHPStan\Type\UnionType;
1823

1924
class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2025
{
2126

22-
/** @var array<string, Type> */
27+
/** @var array<int, Type> */
2328
private $filterTypesHashMaps;
2429

30+
/** @var array<int, Type> */
31+
private $nullableTypes;
32+
33+
/** @var ConstantStringType */
34+
private $flagsString;
35+
2536
public function __construct()
2637
{
38+
if (!defined('FILTER_SANITIZE_EMAIL')) {
39+
return;
40+
}
41+
2742
$booleanType = new BooleanType();
28-
$floatOrFalseType = new UnionType([new FloatType(), new ConstantBooleanType(false)]);
29-
$intOrFalseType = new UnionType([new IntegerType(), new ConstantBooleanType(false)]);
30-
$stringOrFalseType = new UnionType([new StringType(), new ConstantBooleanType(false)]);
43+
$floatType = new FloatType();
44+
$intType = new IntegerType();
45+
$stringType = new StringType();
46+
47+
$nullType = new NullType();
48+
$falseType = new ConstantBooleanType(false);
49+
50+
$nullableBooleanType = new UnionType([$booleanType, $nullType]);
51+
$floatOrFalseType = new UnionType([$floatType, $falseType]);
52+
$nullableFloatType = new UnionType([$floatType, $nullType]);
53+
$intOrFalseType = new UnionType([$intType, $falseType]);
54+
$nullableIntType = new UnionType([$intType, $nullType]);
55+
$stringOrFalseType = new UnionType([$stringType, $falseType]);
56+
$nullableStringType = new UnionType([$stringType, $nullType]);
3157

3258
$this->filterTypesHashMaps = [
33-
'FILTER_SANITIZE_EMAIL' => $stringOrFalseType,
34-
'FILTER_SANITIZE_ENCODED' => $stringOrFalseType,
35-
'FILTER_SANITIZE_MAGIC_QUOTES' => $stringOrFalseType,
36-
'FILTER_SANITIZE_NUMBER_FLOAT' => $stringOrFalseType,
37-
'FILTER_SANITIZE_NUMBER_INT' => $stringOrFalseType,
38-
'FILTER_SANITIZE_SPECIAL_CHARS' => $stringOrFalseType,
39-
'FILTER_SANITIZE_STRING' => $stringOrFalseType,
40-
'FILTER_SANITIZE_URL' => $stringOrFalseType,
41-
'FILTER_VALIDATE_BOOLEAN' => $booleanType,
42-
'FILTER_VALIDATE_EMAIL' => $stringOrFalseType,
43-
'FILTER_VALIDATE_FLOAT' => $floatOrFalseType,
44-
'FILTER_VALIDATE_INT' => $intOrFalseType,
45-
'FILTER_VALIDATE_IP' => $stringOrFalseType,
46-
'FILTER_VALIDATE_MAC' => $stringOrFalseType,
47-
'FILTER_VALIDATE_REGEXP' => $stringOrFalseType,
48-
'FILTER_VALIDATE_URL' => $stringOrFalseType,
59+
FILTER_SANITIZE_EMAIL => $stringOrFalseType,
60+
FILTER_SANITIZE_ENCODED => $stringOrFalseType,
61+
FILTER_SANITIZE_MAGIC_QUOTES => $stringOrFalseType,
62+
FILTER_SANITIZE_NUMBER_FLOAT => $stringOrFalseType,
63+
FILTER_SANITIZE_NUMBER_INT => $stringOrFalseType,
64+
FILTER_SANITIZE_SPECIAL_CHARS => $stringOrFalseType,
65+
FILTER_SANITIZE_STRING => $stringOrFalseType,
66+
FILTER_SANITIZE_URL => $stringOrFalseType,
67+
FILTER_VALIDATE_BOOLEAN => $booleanType,
68+
FILTER_VALIDATE_EMAIL => $stringOrFalseType,
69+
FILTER_VALIDATE_FLOAT => $floatOrFalseType,
70+
FILTER_VALIDATE_INT => $intOrFalseType,
71+
FILTER_VALIDATE_IP => $stringOrFalseType,
72+
FILTER_VALIDATE_MAC => $stringOrFalseType,
73+
FILTER_VALIDATE_REGEXP => $stringOrFalseType,
74+
FILTER_VALIDATE_URL => $stringOrFalseType,
75+
];
76+
77+
$this->nullableTypes = [
78+
FILTER_VALIDATE_BOOLEAN => $nullableBooleanType,
79+
FILTER_VALIDATE_EMAIL => $nullableStringType,
80+
FILTER_VALIDATE_FLOAT => $nullableFloatType,
81+
FILTER_VALIDATE_INT => $nullableIntType,
82+
FILTER_VALIDATE_IP => $nullableStringType,
83+
FILTER_VALIDATE_MAC => $nullableStringType,
84+
FILTER_VALIDATE_REGEXP => $nullableStringType,
85+
FILTER_VALIDATE_URL => $nullableStringType,
4986
];
87+
88+
$this->flagsString = new ConstantStringType('flags');
5089
}
5190

5291
public function isFunctionSupported(FunctionReflection $functionReflection): bool
5392
{
54-
return strtolower($functionReflection->getName()) === 'filter_var';
93+
return defined('FILTER_SANITIZE_EMAIL') && strtolower($functionReflection->getName()) === 'filter_var';
5594
}
5695

5796
public function getTypeFromFunctionCall(
@@ -67,14 +106,59 @@ public function getTypeFromFunctionCall(
67106
return $mixedType;
68107
}
69108

70-
$filterExpr = $filterArg->value;
71-
if (!$filterExpr instanceof ConstFetch) {
109+
$filterType = $scope->getType($filterArg->value);
110+
if (!$filterType instanceof ConstantIntegerType) {
72111
return $mixedType;
73112
}
74113

75-
$filterName = (string) $filterExpr->name;
114+
$filterValue = $filterType->getValue();
115+
116+
$flagsArg = $functionCall->args[2] ?? null;
117+
if ($this->isNullableType($filterValue, $flagsArg, $scope)) {
118+
$type = $this->nullableTypes[$filterValue];
119+
} else {
120+
$type = $this->filterTypesHashMaps[$filterValue] ?? $mixedType;
121+
}
122+
123+
if ($this->isForcedArrayType($flagsArg, $scope)) {
124+
return new ArrayType(new MixedType(), $type);
125+
}
126+
127+
return $type;
128+
}
129+
130+
private function isNullableType(int $filterValue, ?Node\Arg $flagsArg, Scope $scope): bool
131+
{
132+
if ($flagsArg === null || !array_key_exists($filterValue, $this->nullableTypes)) {
133+
return false;
134+
}
135+
136+
return $this->hasFlag(FILTER_NULL_ON_FAILURE, $flagsArg, $scope);
137+
}
138+
139+
private function isForcedArrayType(?Node\Arg $flagsArg, Scope $scope): bool
140+
{
141+
if ($flagsArg === null) {
142+
return false;
143+
}
144+
145+
return $this->hasFlag(FILTER_FORCE_ARRAY, $flagsArg, $scope);
146+
}
147+
148+
private function hasFlag(int $flag, Node\Arg $expression, Scope $scope): bool
149+
{
150+
$type = $this->getFlagsValue($scope->getType($expression->value));
151+
152+
return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag;
153+
}
154+
155+
private function getFlagsValue(Type $exprType): Type
156+
{
157+
if (!$exprType instanceof ConstantArrayType) {
158+
return $exprType;
159+
}
76160

77-
return $this->filterTypesHashMaps[$filterName] ?? $mixedType;
161+
return $exprType->getOffsetValueType($this->flagsString);
78162
}
79163

80164
}

‎tests/PHPStan/Analyser/NodeScopeResolverTest.php‎

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8006,6 +8006,10 @@ public function dataFilterVar(): array
80068006
'string|false',
80078007
'filter_var($mixed, FILTER_SANITIZE_EMAIL)',
80088008
],
8009+
[
8010+
'array<string|false>',
8011+
'filter_var($mixed, FILTER_SANITIZE_EMAIL, FILTER_FORCE_ARRAY)',
8012+
],
80098013
[
80108014
'string|false',
80118015
'filter_var($mixed, FILTER_SANITIZE_ENCODED)',
@@ -8038,34 +8042,151 @@ public function dataFilterVar(): array
80388042
'bool',
80398043
'filter_var($mixed, FILTER_VALIDATE_BOOLEAN)',
80408044
],
8045+
[
8046+
'bool|null',
8047+
'filter_var($mixed, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)',
8048+
],
8049+
[
8050+
'bool|null',
8051+
'filter_var($mixed, FILTER_VALIDATE_BOOLEAN ,["flags" => FILTER_NULL_ON_FAILURE])',
8052+
],
80418053
[
80428054
'string|false',
80438055
'filter_var($mixed, FILTER_VALIDATE_EMAIL)',
80448056
],
8057+
[
8058+
'string|null',
8059+
'filter_var($mixed, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)',
8060+
],
8061+
[
8062+
'string|null',
8063+
'filter_var($mixed, FILTER_VALIDATE_EMAIL ,["flags" => FILTER_NULL_ON_FAILURE])',
8064+
],
80458065
[
80468066
'float|false',
80478067
'filter_var($mixed, FILTER_VALIDATE_FLOAT)',
80488068
],
8069+
[
8070+
'float|null',
8071+
'filter_var($mixed, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE)',
8072+
],
8073+
8074+
[
8075+
'array<float|false>',
8076+
'filter_var($mixed, FILTER_VALIDATE_FLOAT, FILTER_FORCE_ARRAY)',
8077+
],
8078+
[
8079+
'array<float|null>',
8080+
'filter_var($mixed, FILTER_VALIDATE_FLOAT, FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE)',
8081+
],
8082+
[
8083+
'array<float|null>',
8084+
'filter_var($mixed, FILTER_VALIDATE_FLOAT, $forceArrayFilter | $nullFilter)',
8085+
],
8086+
[
8087+
'float|null',
8088+
'filter_var($mixed, FILTER_VALIDATE_FLOAT ,["flags" => FILTER_NULL_ON_FAILURE])',
8089+
],
80498090
[
80508091
'int|false',
80518092
'filter_var($mixed, FILTER_VALIDATE_INT)',
80528093
],
8094+
[
8095+
'int|null',
8096+
'filter_var($mixed, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE)',
8097+
],
8098+
[
8099+
'int|null',
8100+
'filter_var($mixed, FILTER_VALIDATE_INT ,["flags" => FILTER_NULL_ON_FAILURE])',
8101+
],
80538102
[
80548103
'string|false',
80558104
'filter_var($mixed, FILTER_VALIDATE_IP)',
80568105
],
8106+
[
8107+
'string|false',
8108+
'filter_var($mixed, $filterIp)',
8109+
],
8110+
[
8111+
'string|false',
8112+
'filter_var($mixed, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)',
8113+
],
8114+
[
8115+
'string|null',
8116+
'filter_var($mixed, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE)',
8117+
],
8118+
[
8119+
'string|null',
8120+
'filter_var($mixed, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)',
8121+
],
8122+
[
8123+
'array<string|null>',
8124+
'filter_var($mixed, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4 | FILTER_FORCE_ARRAY)',
8125+
],
8126+
[
8127+
'string|null',
8128+
'filter_var($mixed, FILTER_VALIDATE_IP ,["flags" => FILTER_NULL_ON_FAILURE])',
8129+
],
80578130
[
80588131
'string|false',
80598132
'filter_var($mixed, FILTER_VALIDATE_MAC)',
80608133
],
8134+
[
8135+
'string|null',
8136+
'filter_var($mixed, FILTER_VALIDATE_MAC, FILTER_NULL_ON_FAILURE)',
8137+
],
8138+
[
8139+
'string|null',
8140+
'filter_var($mixed, FILTER_VALIDATE_MAC ,["flags" => FILTER_NULL_ON_FAILURE])',
8141+
],
80618142
[
80628143
'string|false',
80638144
'filter_var($mixed, FILTER_VALIDATE_REGEXP)',
80648145
],
8146+
[
8147+
'string|false',
8148+
'filter_var($mixed, FILTER_VALIDATE_REGEXP, ["options" => ["regexp" => "/match/"]])',
8149+
],
8150+
[
8151+
'string|null',
8152+
'filter_var($mixed, FILTER_VALIDATE_REGEXP, FILTER_NULL_ON_FAILURE)',
8153+
],
8154+
[
8155+
'string|null',
8156+
'filter_var($mixed, FILTER_VALIDATE_REGEXP ,["flags" => FILTER_NULL_ON_FAILURE])',
8157+
],
8158+
[
8159+
'string|null',
8160+
'filter_var($mixed, FILTER_VALIDATE_REGEXP ,["flags" => FILTER_NULL_ON_FAILURE, "options" => ["regexp" => "/match/"]])',
8161+
],
80658162
[
80668163
'string|false',
80678164
'filter_var($mixed, FILTER_VALIDATE_URL)',
80688165
],
8166+
[
8167+
'string|false',
8168+
'filter_var($mixed, FILTER_VALIDATE_URL, $mixed)',
8169+
],
8170+
[
8171+
'string|false',
8172+
'filter_var($mixed, FILTER_VALIDATE_URL ,["flags" => $mixed])',
8173+
],
8174+
[
8175+
'string|null',
8176+
'filter_var($mixed, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)',
8177+
],
8178+
[
8179+
'string|null',
8180+
'filter_var($mixed, FILTER_VALIDATE_URL, $nullFilter)',
8181+
],
8182+
[
8183+
'string|null',
8184+
'filter_var($mixed, FILTER_VALIDATE_URL ,["flags" => FILTER_NULL_ON_FAILURE])',
8185+
],
8186+
[
8187+
'string|null',
8188+
'filter_var($mixed, FILTER_VALIDATE_URL ,["flags" => $nullFilter])',
8189+
],
80698190
];
80708191
}
80718192

‎tests/PHPStan/Analyser/data/filterVar.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
function () {
66
/** @var mixed $mixed */
77
$mixed = null;
8+
$nullFilter = \FILTER_NULL_ON_FAILURE;
9+
$forceArrayFilter = \FILTER_FORCE_ARRAY;
10+
$filterIp = FILTER_VALIDATE_IP;
811

912
die;
1013
};

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /