Skip to content

Commit d96f9e7

Browse files
Fix
1 parent 0e3a12d commit d96f9e7

File tree

1 file changed

+70
-37
lines changed

1 file changed

+70
-37
lines changed

src/Type/Php/FilterFunctionReturnTypeHelper.php

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\DependencyInjection\AutowiredService;
77
use PHPStan\Php\PhpVersion;
88
use PHPStan\Reflection\ReflectionProvider;
9-
use PHPStan\ShouldNotHappenException;
109
use PHPStan\TrinaryLogic;
1110
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
1211
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
@@ -32,7 +31,6 @@
3231
use function is_int;
3332
use function octdec;
3433
use function preg_match;
35-
use function sprintf;
3634

3735
#[AutowiredService]
3836
final 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 (null === $filterValue) {
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 (null === $flag) {
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

Comments
 (0)