Skip to content

Fix phpstan/phpstan#11339: filter_input with FILTER_FORCE_ARRAY has incorrect return type#5315

Closed
phpstan-bot wants to merge 5 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fvwtg37
Closed

Fix phpstan/phpstan#11339: filter_input with FILTER_FORCE_ARRAY has incorrect return type#5315
phpstan-bot wants to merge 5 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fvwtg37

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

filter_input and filter_var with FILTER_FORCE_ARRAY or FILTER_REQUIRE_ARRAY flags can return arbitrarily nested arrays (e.g. from query strings like ?a[a][b][]=val), but PHPStan was reporting the return type as array<string|false> without accounting for nested array values. This fix adds array to the value type when the input could contain arrays.

Changes

  • Modified src/Type/Php/FilterFunctionReturnTypeHelper.php: In the getType() method, when wrapping the result in an array for FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY, union ArrayType(MixedType, MixedType) into the value type when the input could be an array ($inputIsArray is not no)
  • Updated test expectations in tests/PHPStan/Analyser/nsrt/filterVar.php for all cases with mixed inputs
  • Updated test expectations in tests/PHPStan/Analyser/nsrt/filter-var.php for cases with mixed/array inputs
  • Updated test expectations in tests/PHPStan/Analyser/nsrt/filter-input.php for filter_input with FORCE_ARRAY
  • Updated test expectations in tests/PHPStan/Analyser/nsrt/discussion-9134.php

Root cause

The FilterFunctionReturnTypeHelper::getType() method wraps the filtered scalar type in an ArrayType when FILTER_FORCE_ARRAY or FILTER_REQUIRE_ARRAY is set, but did not account for the fact that input values (from superglobals or mixed-type variables) can themselves be arrays. PHP's filter functions recursively apply the filter to nested arrays, producing nested array output. The fix adds array to the value type union when the input type could be an array.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11339.php with assertions for:

  • filter_input with FILTER_FORCE_ARRAY and FILTER_REQUIRE_ARRAY using FILTER_DEFAULT
  • filter_input with FILTER_VALIDATE_INT and both array flags
  • filter_var with known scalar inputs (verifying no false positive array in value type)

Fixes phpstan/phpstan#11339

…ncorrect return type

- When FILTER_FORCE_ARRAY or FILTER_REQUIRE_ARRAY is used, input values can be
  arbitrarily nested arrays (e.g. from query strings like ?a[a][b][]=val), so
  the array value type must include array when input could be an array
- Updated FilterFunctionReturnTypeHelper::getType() to union ArrayType into
  the value type when inputIsArray is not no
- New regression test in tests/PHPStan/Analyser/nsrt/bug-11339.php
- Updated existing test expectations in filterVar.php, filter-var.php,
  filter-input.php, and discussion-9134.php to reflect the corrected types
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do a little better

For instance we should have something like

assertType('array<array<int|null>>|null', filter_var($arrayOfArrayOfInt, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));

assertType('array<int|null>|null', filter_var($arrayOfInt, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));

assertType('array<array<int|null>int|null>|null', filter_var($arrayOfIntOrArrayOfInt, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE]));

…rray inputs

When the input array has a known value type (e.g. array<int>, array<array<int>>),
use that type information instead of always adding generic array to the value type.

- When value type is definitely not array (e.g. array<int>): don't add array
- When value type is definitely array (e.g. array<array<int>>): recursively
  compute the precise nested filtered type
- When value type is a union of scalar and array (e.g. array<int|array<int>>):
  extract scalar/array parts and handle each precisely
- For mixed/unknown value types: fall back to array<mixed, mixed> as before

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

The make phpstan background task also confirmed zero errors. Everything has been verified and pushed.

return $type;
}

private function addNestedArrayType(Type $type, ?Type $inputValueType, TrinaryLogic $inputIsArray, ?Type $filterType, ?Type $flagsType, MixedType $mixedType): Type
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be simpler to implement with the TypeTraverser::map method.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both background tasks confirmed complete — tests (11,677 passed) and phpstan (0 errors). The commit addressing the review is already pushed.

Replace the manual addNestedArrayType method with TypeTraverser::map
to recursively filter each component of the array value type. Extract
filterScalarType method for cleaner separation of scalar filtering
logic from array wrapping logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar strategy should be used for place you added

TypeCombinator::union($type, new ArrayType($mixedType, $mixedType));

Replace conservative `array<mixed, mixed>` with precise nested array
type computation using TypeTraverser::map when input type carries array
structure information (e.g., `int|array<int>`).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Both background tasks from the earlier run also completed successfully. All clear.

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're using TypeTraverser::map in two different way in this PR while it could be refactor/simplified into a single method reusable

Replaces the inline TypeTraverser::map and the separate computeNestedArrayType
method with a single reusable filterTypeComponents method. This also simplifies
the REQUIRE_ARRAY and FORCE_ARRAY code paths by using the unified method
directly instead of filterScalarType + computeNestedArrayType + union.

As a side effect, union-typed inputs now produce more precise types since each
union member is filtered individually rather than the union being treated as a
non-scalar whole.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Both background tasks from the earlier run also completed successfully. All clear.

@VincentLanglet
Copy link
Copy Markdown
Contributor

Gonna try a fresh start another day

@VincentLanglet VincentLanglet deleted the create-pull-request/patch-fvwtg37 branch March 30, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants