22
33namespace PHPStan \Type \Php ;
44
5- use PhpParser \Node \ Expr \ ConstFetch ;
5+ use PhpParser \Node ;
66use PhpParser \Node \Expr \FuncCall ;
77use PHPStan \Analyser \Scope ;
88use PHPStan \Reflection \FunctionReflection ;
9+ use PHPStan \Type \ArrayType ;
910use PHPStan \Type \BooleanType ;
11+ use PHPStan \Type \Constant \ConstantArrayType ;
1012use PHPStan \Type \Constant \ConstantBooleanType ;
13+ use PHPStan \Type \Constant \ConstantIntegerType ;
14+ use PHPStan \Type \Constant \ConstantStringType ;
1115use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
1216use PHPStan \Type \FloatType ;
1317use PHPStan \Type \IntegerType ;
1418use PHPStan \Type \MixedType ;
19+ use PHPStan \Type \NullType ;
1520use PHPStan \Type \StringType ;
1621use PHPStan \Type \Type ;
1722use PHPStan \Type \UnionType ;
1823
1924class 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}
0 commit comments