Skip to content

Report str_replace()/str_ireplace()/preg_replace()/preg_filter() calls with fewer replacements than search values - #7007

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

phpstan-bot wants to merge 1 commit into
phpstan:2.3.xfrom
phpstan-bot:create-pull-request/patch-kniiekt

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

str_replace(['a', 'b', 'c'], ['x', 'y'], $s) is valid PHP: the search values without a matching replacement are replaced with an empty string. This is rarely intended and usually a typo. This PR adds a bleeding-edge rule that reports these calls.

Changes

  • src/Rules/Functions/ReplaceCountMismatchRule.php: new level-5 rule, gated by the replaceCountMismatch feature toggle (enabled in conf/bleedingEdge.neon, declared in conf/config.neon and conf/parametersSchema.neon).
  • Covers every function with the same search/replace array semantics: str_replace, str_ireplace, preg_replace and preg_filter. Identifiers are strReplace.replaceCount and pregReplace.replaceCount.
  • Not reported:
    • a string replacement (a single replacement used for all search values, as requested in the issue comments)
    • an explicitly empty replacement array (a deliberate way to remove everything)
    • sizes that only might differ (e.g. list<string>, or a union of shapes that overlaps)
  • Supports named arguments through ArgumentsNormalizer, and handles treatPhpDocTypesAsCertain, adding the tip when only PHPDoc types prove the mismatch.

Root cause

PHPStan had no check comparing the sizes of the search and replace arrays. The rule compares getArraySize() of both arguments with isSmallerThan(). This works for constant arrays, array shapes, unions of shapes and non-empty lists alike.

Probed and out of scope: str_replace('a', ['x'], $s) (string search with an array replace) throws a TypeError on PHP 8 but is not reported today. That comes from the function signature, not a count mismatch, so it is left for a separate change.

Test

tests/PHPStan/Rules/Functions/ReplaceCountMismatchRuleTest.php with data/bug-3889.php. It contains the issue's reproducer verbatim, the string-replacement case from the comments (no error), and variants for: str_ireplace, preg_replace, preg_filter, named arguments (separate PHP 8.0+ fixture data/bug-3889-named-args.php), PHPDoc-only shapes (with the tip), lists, non-empty lists, unions of shapes, and empty/equal/longer replacement arrays.

Fixes phpstan/phpstan#3889

🤖 Generated with Claude Code

…()` calls with fewer replacements than search values

- New bleeding-edge rule `ReplaceCountMismatchRule` (level 5, feature toggle
  `replaceCountMismatch`): when both the search/pattern and the
  replace/replacement arguments are arrays and the replacement array size is
  definitely smaller than the search array size, report it, because PHP
  silently replaces the remaining values with an empty string.
- Uses `Type::getArraySize()` + `isSmallerThan()` so constant arrays, unions
  of shapes and non-empty lists are handled; only definite mismatches are reported.
- A string replacement and an explicitly empty replacement array (a deliberate
  "remove all" idiom) are not reported.
- Applied to the whole family of replace functions with the same semantics:
  str_replace, str_ireplace, preg_replace, preg_filter.
- Respects treatPhpDocTypesAsCertain and adds the tip when only PHPDoc types
  prove the mismatch.
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.

str_replace() with more search then replace values

1 participant