66use PHPStan \DependencyInjection \AutowiredService ;
77use PHPStan \Php \PhpVersion ;
88use PHPStan \Reflection \ReflectionProvider ;
9- use PHPStan \ShouldNotHappenException ;
109use PHPStan \TrinaryLogic ;
1110use PHPStan \Type \Accessory \AccessoryNonEmptyStringType ;
1211use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
3231use function is_int ;
3332use function octdec ;
3433use function preg_match ;
35- use function sprintf ;
3634
3735#[AutowiredService]
3836final class FilterFunctionReturnTypeHelper
@@ -58,7 +56,7 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
5856
5957 public function getOffsetValueType (Type $ inputType , Type $ offsetType , ?Type $ filterType , ?Type $ flagsType ): Type
6058 {
61- $ inexistentOffsetType = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_NULL_ON_FAILURE ' ) , $ flagsType )
59+ $ inexistentOffsetType = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
6260 ? new ConstantBooleanType (false )
6361 : new NullType ();
6462
@@ -107,6 +105,9 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
107105
108106 if ($ filterType === null ) {
109107 $ filterValue = $ this ->getConstant ('FILTER_DEFAULT ' );
108+ if ($ filterValue === null ) {
109+ return $ mixedType ;
110+ }
110111 } else {
111112 if (!$ filterType instanceof ConstantIntegerType) {
112113 return $ mixedType ;
@@ -121,17 +122,17 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
121122 $ hasOptions = $ this ->hasOptions ($ flagsType );
122123 $ options = $ hasOptions ->yes () ? $ this ->getOptions ($ flagsType , $ filterValue ) : [];
123124
124- $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ($ this -> getConstant ( 'FILTER_NULL_ON_FAILURE ' ) , $ flagsType )
125+ $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
125126 ? new NullType ()
126127 : new ConstantBooleanType (false ));
127128
128129 $ inputIsArray = $ inputType ->isArray ();
129- $ hasRequireArrayFlag = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_REQUIRE_ARRAY ' ) , $ flagsType );
130+ $ hasRequireArrayFlag = $ this ->hasFlag ('FILTER_REQUIRE_ARRAY ' , $ flagsType );
130131 if ($ inputIsArray ->no () && $ hasRequireArrayFlag ) {
131132 return $ defaultType ;
132133 }
133134
134- $ hasForceArrayFlag = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FORCE_ARRAY ' ) , $ flagsType );
135+ $ hasForceArrayFlag = $ this ->hasFlag ('FILTER_FORCE_ARRAY ' , $ flagsType );
135136 if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag || $ hasForceArrayFlag )) {
136137 $ inputArrayKeyType = $ inputType ->getIterableKeyType ();
137138 $ inputType = $ inputType ->getIterableValueType ();
@@ -187,32 +188,47 @@ private function getFilterTypeMap(): array
187188 $ stringType = new StringType ();
188189 $ nonFalsyStringType = TypeCombinator::intersect ($ stringType , new AccessoryNonFalsyStringType ());
189190
190- $ this -> filterTypeMap = [
191- $ this -> getConstant ( 'FILTER_UNSAFE_RAW ' ) => $ stringType ,
192- $ this -> getConstant ( 'FILTER_SANITIZE_EMAIL ' ) => $ stringType ,
193- $ this -> getConstant ( 'FILTER_SANITIZE_ENCODED ' ) => $ stringType ,
194- $ this -> getConstant ( 'FILTER_SANITIZE_NUMBER_FLOAT ' ) => $ stringType ,
195- $ this -> getConstant ( 'FILTER_SANITIZE_NUMBER_INT ' ) => $ stringType ,
196- $ this -> getConstant ( 'FILTER_SANITIZE_SPECIAL_CHARS ' ) => $ stringType ,
197- $ this -> getConstant ( 'FILTER_SANITIZE_STRING ' ) => $ stringType ,
198- $ this -> getConstant ( 'FILTER_SANITIZE_URL ' ) => $ stringType ,
199- $ this -> getConstant ( 'FILTER_VALIDATE_BOOLEAN ' ) => $ booleanType ,
200- $ this -> getConstant ( 'FILTER_VALIDATE_DOMAIN ' ) => $ stringType ,
201- $ this -> getConstant ( 'FILTER_VALIDATE_EMAIL ' ) => $ nonFalsyStringType ,
202- $ this -> getConstant ( 'FILTER_VALIDATE_FLOAT ' ) => $ floatType ,
203- $ this -> getConstant ( 'FILTER_VALIDATE_INT ' ) => $ intType ,
204- $ this -> getConstant ( 'FILTER_VALIDATE_IP ' ) => $ nonFalsyStringType ,
205- $ this -> getConstant ( 'FILTER_VALIDATE_MAC ' ) => $ nonFalsyStringType ,
206- $ this -> getConstant ( 'FILTER_VALIDATE_REGEXP ' ) => $ stringType ,
207- $ this -> getConstant ( 'FILTER_VALIDATE_URL ' ) => $ nonFalsyStringType ,
191+ $ map = [
192+ 'FILTER_UNSAFE_RAW ' => $ stringType ,
193+ 'FILTER_SANITIZE_EMAIL ' => $ stringType ,
194+ 'FILTER_SANITIZE_ENCODED ' => $ stringType ,
195+ 'FILTER_SANITIZE_NUMBER_FLOAT ' => $ stringType ,
196+ 'FILTER_SANITIZE_NUMBER_INT ' => $ stringType ,
197+ 'FILTER_SANITIZE_SPECIAL_CHARS ' => $ stringType ,
198+ 'FILTER_SANITIZE_STRING ' => $ stringType ,
199+ 'FILTER_SANITIZE_URL ' => $ stringType ,
200+ 'FILTER_VALIDATE_BOOLEAN ' => $ booleanType ,
201+ 'FILTER_VALIDATE_DOMAIN ' => $ stringType ,
202+ 'FILTER_VALIDATE_EMAIL ' => $ nonFalsyStringType ,
203+ 'FILTER_VALIDATE_FLOAT ' => $ floatType ,
204+ 'FILTER_VALIDATE_INT ' => $ intType ,
205+ 'FILTER_VALIDATE_IP ' => $ nonFalsyStringType ,
206+ 'FILTER_VALIDATE_MAC ' => $ nonFalsyStringType ,
207+ 'FILTER_VALIDATE_REGEXP ' => $ stringType ,
208+ 'FILTER_VALIDATE_URL ' => $ nonFalsyStringType ,
208209 ];
209210
211+ $ this ->filterTypeMap = [];
212+ foreach ($ map as $ filter => $ type ) {
213+ $ constant = $ this ->getConstant ($ filter );
214+ if ($ constant === null ) {
215+ continue ;
216+ }
217+ $ this ->filterTypeMap [$ constant ] = $ type ;
218+ }
219+
210220 if ($ this ->reflectionProvider ->hasConstant (new Node \Name ('FILTER_SANITIZE_MAGIC_QUOTES ' ), null )) {
211- $ this ->filterTypeMap [$ this ->getConstant ('FILTER_SANITIZE_MAGIC_QUOTES ' )] = $ stringType ;
221+ $ sanitizeMagicQuote = $ this ->getConstant ('FILTER_SANITIZE_MAGIC_QUOTES ' );
222+ if ($ sanitizeMagicQuote !== null ) {
223+ $ this ->filterTypeMap [$ sanitizeMagicQuote ] = $ stringType ;
224+ }
212225 }
213226
214227 if ($ this ->reflectionProvider ->hasConstant (new Node \Name ('FILTER_SANITIZE_ADD_SLASHES ' ), null )) {
215- $ this ->filterTypeMap [$ this ->getConstant ('FILTER_SANITIZE_ADD_SLASHES ' )] = $ stringType ;
228+ $ sanitizeAddSlashes = $ this ->getConstant ('FILTER_SANITIZE_ADD_SLASHES ' );
229+ if ($ sanitizeAddSlashes !== null ) {
230+ $ this ->filterTypeMap [$ sanitizeAddSlashes ] = $ stringType ;
231+ }
216232 }
217233
218234 return $ this ->filterTypeMap ;
@@ -227,24 +243,33 @@ private function getFilterTypeOptions(): array
227243 return $ this ->filterTypeOptions ;
228244 }
229245
230- $ this -> filterTypeOptions = [
231- $ this -> getConstant ( 'FILTER_VALIDATE_INT ' ) => ['min_range ' , 'max_range ' ],
246+ $ map = [
247+ 'FILTER_VALIDATE_INT ' => ['min_range ' , 'max_range ' ],
232248 // PHPStan does not yet support FloatRangeType
233- // $this->getConstant( 'FILTER_VALIDATE_FLOAT') => ['min_range', 'max_range'],
249+ // 'FILTER_VALIDATE_FLOAT' => ['min_range', 'max_range'],
234250 ];
235251
252+ $ this ->filterTypeOptions = [];
253+ foreach ($ map as $ filter => $ type ) {
254+ $ constant = $ this ->getConstant ($ filter );
255+ if ($ constant === null ) {
256+ continue ;
257+ }
258+ $ this ->filterTypeOptions [$ constant ] = $ type ;
259+ }
260+
236261 return $ this ->filterTypeOptions ;
237262 }
238263
239264 /**
240265 * @param non-empty-string $constantName
241266 */
242- private function getConstant (string $ constantName ): int
267+ private function getConstant (string $ constantName ): ? int
243268 {
244269 $ constant = $ this ->reflectionProvider ->getConstant (new Node \Name ($ constantName ), null );
245270 $ valueType = $ constant ->getValueType ();
246271 if (!$ valueType instanceof ConstantIntegerType) {
247- throw new ShouldNotHappenException ( sprintf ( ' Constant %s does not have integer type. ' , $ constantName )) ;
272+ return null ;
248273 }
249274
250275 return $ valueType ->getValue ();
@@ -301,8 +326,8 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
301326
302327 if ($ in instanceof ConstantStringType) {
303328 $ value = $ in ->getValue ();
304- $ allowOctal = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FLAG_ALLOW_OCTAL ' ) , $ flagsType );
305- $ allowHex = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FLAG_ALLOW_HEX ' ) , $ flagsType );
329+ $ allowOctal = $ this ->hasFlag ('FILTER_FLAG_ALLOW_OCTAL ' , $ flagsType );
330+ $ allowHex = $ this ->hasFlag ('FILTER_FLAG_ALLOW_HEX ' , $ flagsType );
306331
307332 if ($ allowOctal && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
308333 $ octalValue = octdec ($ value );
@@ -411,8 +436,16 @@ private function getOptions(Type $flagsType, int $filterValue): array
411436 return $ options ;
412437 }
413438
414- private function hasFlag (int $ flag , ?Type $ flagsType ): bool
439+ /**
440+ * @param non-empty-string $flagName
441+ */
442+ private function hasFlag (string $ flagName , ?Type $ flagsType ): bool
415443 {
444+ $ flag = $ this ->getConstant ($ flagName );
445+ if ($ flag === null ) {
446+ return false ;
447+ }
448+
416449 if ($ flagsType === null ) {
417450 return false ;
418451 }
@@ -441,9 +474,9 @@ private function canStringBeSanitized(int $filterValue, ?Type $flagsType): bool
441474 // FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
442475 // FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
443476 if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
444- return $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FLAG_STRIP_LOW ' ) , $ flagsType )
445- || $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FLAG_STRIP_HIGH ' ) , $ flagsType )
446- || $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FLAG_STRIP_BACKTICK ' ) , $ flagsType );
477+ return $ this ->hasFlag ('FILTER_FLAG_STRIP_LOW ' , $ flagsType )
478+ || $ this ->hasFlag ('FILTER_FLAG_STRIP_HIGH ' , $ flagsType )
479+ || $ this ->hasFlag ('FILTER_FLAG_STRIP_BACKTICK ' , $ flagsType );
447480 }
448481
449482 return true ;
0 commit comments