6
6
use PHPStan \DependencyInjection \AutowiredService ;
7
7
use PHPStan \Php \PhpVersion ;
8
8
use PHPStan \Reflection \ReflectionProvider ;
9
- use PHPStan \ShouldNotHappenException ;
10
9
use PHPStan \TrinaryLogic ;
11
10
use PHPStan \Type \Accessory \AccessoryNonEmptyStringType ;
12
11
use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
32
31
use function is_int ;
33
32
use function octdec ;
34
33
use function preg_match ;
35
- use function sprintf ;
36
34
37
35
#[AutowiredService]
38
36
final class FilterFunctionReturnTypeHelper
@@ -58,7 +56,7 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
58
56
59
57
public function getOffsetValueType (Type $ inputType , Type $ offsetType , ?Type $ filterType , ?Type $ flagsType ): Type
60
58
{
61
- $ inexistentOffsetType = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_NULL_ON_FAILURE ' ) , $ flagsType )
59
+ $ inexistentOffsetType = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
62
60
? new ConstantBooleanType (false )
63
61
: new NullType ();
64
62
@@ -107,6 +105,9 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
107
105
108
106
if ($ filterType === null ) {
109
107
$ filterValue = $ this ->getConstant ('FILTER_DEFAULT ' );
108
+ if (null === $ filterValue ) {
109
+ return $ mixedType ;
110
+ }
110
111
} else {
111
112
if (!$ filterType instanceof ConstantIntegerType) {
112
113
return $ mixedType ;
@@ -121,17 +122,17 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
121
122
$ hasOptions = $ this ->hasOptions ($ flagsType );
122
123
$ options = $ hasOptions ->yes () ? $ this ->getOptions ($ flagsType , $ filterValue ) : [];
123
124
124
- $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ($ this -> getConstant ( 'FILTER_NULL_ON_FAILURE ' ) , $ flagsType )
125
+ $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
125
126
? new NullType ()
126
127
: new ConstantBooleanType (false ));
127
128
128
129
$ inputIsArray = $ inputType ->isArray ();
129
- $ hasRequireArrayFlag = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_REQUIRE_ARRAY ' ) , $ flagsType );
130
+ $ hasRequireArrayFlag = $ this ->hasFlag ('FILTER_REQUIRE_ARRAY ' , $ flagsType );
130
131
if ($ inputIsArray ->no () && $ hasRequireArrayFlag ) {
131
132
return $ defaultType ;
132
133
}
133
134
134
- $ hasForceArrayFlag = $ this ->hasFlag ($ this -> getConstant ( 'FILTER_FORCE_ARRAY ' ) , $ flagsType );
135
+ $ hasForceArrayFlag = $ this ->hasFlag ('FILTER_FORCE_ARRAY ' , $ flagsType );
135
136
if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag || $ hasForceArrayFlag )) {
136
137
$ inputArrayKeyType = $ inputType ->getIterableKeyType ();
137
138
$ inputType = $ inputType ->getIterableValueType ();
@@ -187,32 +188,47 @@ private function getFilterTypeMap(): array
187
188
$ stringType = new StringType ();
188
189
$ nonFalsyStringType = TypeCombinator::intersect ($ stringType , new AccessoryNonFalsyStringType ());
189
190
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 ,
208
209
];
209
210
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
+
210
220
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
+ }
212
225
}
213
226
214
227
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
+ }
216
232
}
217
233
218
234
return $ this ->filterTypeMap ;
@@ -227,24 +243,33 @@ private function getFilterTypeOptions(): array
227
243
return $ this ->filterTypeOptions ;
228
244
}
229
245
230
- $ this -> filterTypeOptions = [
231
- $ this -> getConstant ( 'FILTER_VALIDATE_INT ' ) => ['min_range ' , 'max_range ' ],
246
+ $ map = [
247
+ 'FILTER_VALIDATE_INT ' => ['min_range ' , 'max_range ' ],
232
248
// PHPStan does not yet support FloatRangeType
233
- // $this->getConstant( 'FILTER_VALIDATE_FLOAT') => ['min_range', 'max_range'],
249
+ // 'FILTER_VALIDATE_FLOAT' => ['min_range', 'max_range'],
234
250
];
235
251
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
+
236
261
return $ this ->filterTypeOptions ;
237
262
}
238
263
239
264
/**
240
265
* @param non-empty-string $constantName
241
266
*/
242
- private function getConstant (string $ constantName ): int
267
+ private function getConstant (string $ constantName ): ? int
243
268
{
244
269
$ constant = $ this ->reflectionProvider ->getConstant (new Node \Name ($ constantName ), null );
245
270
$ valueType = $ constant ->getValueType ();
246
271
if (!$ valueType instanceof ConstantIntegerType) {
247
- throw new ShouldNotHappenException ( sprintf ( ' Constant %s does not have integer type. ' , $ constantName )) ;
272
+ return null ;
248
273
}
249
274
250
275
return $ valueType ->getValue ();
@@ -301,8 +326,8 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
301
326
302
327
if ($ in instanceof ConstantStringType) {
303
328
$ 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 );
306
331
307
332
if ($ allowOctal && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
308
333
$ octalValue = octdec ($ value );
@@ -411,8 +436,16 @@ private function getOptions(Type $flagsType, int $filterValue): array
411
436
return $ options ;
412
437
}
413
438
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
415
443
{
444
+ $ flag = $ this ->getConstant ($ flagName );
445
+ if (null === $ flag ) {
446
+ return false ;
447
+ }
448
+
416
449
if ($ flagsType === null ) {
417
450
return false ;
418
451
}
@@ -441,9 +474,9 @@ private function canStringBeSanitized(int $filterValue, ?Type $flagsType): bool
441
474
// FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
442
475
// FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
443
476
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 );
447
480
}
448
481
449
482
return true ;
0 commit comments