Skip to content

Return list<mixed> from PDOStatement::fetchAll()#5643

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-e4psdj6
Open

Return list<mixed> from PDOStatement::fetchAll()#5643
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-e4psdj6

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented May 11, 2026

Comment thread tests/PHPStan/Analyser/nsrt/bug-11889.php Outdated
@staabm staabm requested a review from VincentLanglet May 12, 2026 05:46
Comment thread resources/functionMap.php Outdated
Comment thread resources/functionMap.php Outdated
@staabm staabm changed the title Return list<mixed> from PDOStatement::fetchAll(), pg_fetch_all(), and pg_fetch_all_columns() in function signature map Return list<mixed> from PDOStatement::fetchAll() in function signature map May 12, 2026
Comment thread tests/PHPStan/Analyser/nsrt/bug-11889.php Outdated
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.

$stmt->fetchAll(\PDO::FETCH_COLUMN); is a valid call and returns a list.

Since both

$stmt->fetchAll(PDO::FETCH_ASSOC)
$stmt->fetchAll(\PDO::FETCH_COLUMN);

fallback to the same signature, I think we cannot solve this by changing the functionMap, but with a dedicated returnTypeExtension instead.

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Pushed. Here's what I did to address VincentLanglet's review:

Created src/Type/Php/PdoStatementFetchAllReturnTypeExtension.php — a DynamicMethodReturnTypeExtension that inspects the constant fetch mode argument of PDOStatement::fetchAll() to determine whether the result is a list or array:

  • Returns list for modes like FETCH_ASSOC, FETCH_NUM, FETCH_BOTH, FETCH_OBJ, FETCH_COLUMN, FETCH_CLASS, FETCH_NAMED, etc.
  • Returns array (falls back to function map) for FETCH_KEY_PAIR, and any mode with FETCH_GROUP or FETCH_UNIQUE flags set
  • Returns array for no-argument calls or non-constant mode values

This solves the reviewer's concern that fetchAll(PDO::FETCH_COLUMN) and fetchAll(PDO::FETCH_ASSOC) both match the same function map signature but should return different types (both are actually lists, while FETCH_KEY_PAIR/FETCH_GROUP are not).

Updated tests/PHPStan/Analyser/nsrt/bug-11889.php with comprehensive assertions covering single-arg list modes, non-list modes (KEY_PAIR, GROUP|ASSOC, UNIQUE|ASSOC), and multi-arg overload variants.

@VincentLanglet VincentLanglet changed the title Return list<mixed> from PDOStatement::fetchAll() in function signature map Return list<mixed> from PDOStatement::fetchAll() May 15, 2026
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.

Test are failing on PHP 8.5

PHPStan think

assertType('array', $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC));
	assertType('array', $stmt->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC));

will return a list ; why ?

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Pushed. The fix changes the extension to explicitly return array<mixed, mixed> for non-list fetch modes (FETCH_DEFAULT, FETCH_KEY_PAIR, FETCH_GROUP, FETCH_UNIQUE) instead of returning null and relying on the fallback. The fallback was selecting named argument variants on PHP 8.5 that only had a mode parameter and returned list<mixed>, which is why FETCH_GROUP|FETCH_ASSOC and FETCH_UNIQUE|FETCH_ASSOC were incorrectly inferred as list instead of array.

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.

Why On PHP 8.5, the named variants for fetchAll() have only a mode parameter ?

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Now I have the full picture. Here's my analysis and response:

The named argument variant truncation is NOT PHP 8.5-specific. It happens on all PHP 8.0+ because:

  1. The native stub for PDOStatement::fetchAll is fetchAll(int $mode = PDO::FETCH_DEFAULT, mixed ...$args) across all PHP 8.x versions (from vendor/phpstan/php-8-stubs)
  2. In Php8SignatureMapProvider::getMergedSignatures() (line 296-297), when building named argument variants, the loop breaks at variadic native parameters (...$args is variadic at index 1)
  3. All 4 named variants are truncated to just [mode] — variant 0 returns array, variants 1-3 return list<mixed>

The previous commit message saying "on PHP 8.5" was misleading. The truncation happens on all PHP 8.0+ versions. However, it was harmless before this PR because all variants returned array. When variants 1-3 were changed to list<mixed>, the fallback could select a list<mixed> truncated named variant.

The fix is correct regardless: by explicitly returning array<mixed, mixed> for non-list fetch modes in the extension (instead of returning null and relying on fallback variant selection), we avoid any issues with named variant selection on any PHP version.

There are no code changes to make — tests pass, phpstan passes, and the fix is already in place. The reviewer's question was about clarifying the PHP 8.5-specific claim from the previous commit message, and the answer is that the behavior is not PHP 8.5-specific — it applies to all PHP 8.0+ versions.

@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-e4psdj6 branch from cbbfa11 to 6500e71 Compare May 15, 2026 11:13
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.

In PHP 8.5, the values of PDO::FETCH_GROUP, PDO::FETCH_UNIQUE, PDO::FETCH_CLASSTYPE, PDO::FETCH_PROPS_LATE, and PDO::FETCH_SERIALIZE have changed.

FETCH_GROUP is 32 on PHP 8.5

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.

3 participants