Skip to content

Formatting: Document sanitize_key() as accepting mixed - #14759

Closed
westonruter wants to merge 3 commits into
WordPress:trunkfrom
westonruter:fix/sanitize-key-mixed-type
Closed

westonruter wants to merge 3 commits into
WordPress:trunkfrom
westonruter:fix/sanitize-key-mixed-type

Conversation

@westonruter

@westonruter westonruter commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

✅ Committed in r64682 (eaa3b6b).


This widens the documented type of sanitize_key()'s $key parameter from string to mixed, which resolves 50 PHPStan errors at rule level 10 without introducing any.

Background

The sanitize_key() function was documented as taking a string, but it has accepted any value since r52292 (Core-54160) added a type guard to fix a PHP warning when an array was passed. Changeset r52370 then switched that guard from is_string() to is_scalar() so that non-string scalars keep being coerced to a string by strtolower(), as they always had been. The existing tests lock this behavior in:

  • 0 → '0', true → '1', false → '', 0.123 → '0123'
  • an array, null, or an object → ''

So the function is total over mixed, and that matches how it is used. Of its ~75 call sites in core, most pass raw request data ($_POST, $_GET, $_REQUEST, often through wp_unslash()), and it is also used as a REST sanitize_callback, as an array_map() callback, and as a pre_post_* filter callback. In all of these the value can be of any type, such as an array from ?post_type[]=x.

PHPStan errors resolved

At level 10 (the local phpstan.neon), trunk reports:

  • 47 × argument.type: Parameter #1 $key of function sanitize_key expects string, mixed given. across 19 files.
  • 2 × argument.type in WP_Query::parse_query(): Parameter #1 $callback of function array_map expects (callable(mixed): mixed)|null, 'sanitize_key' given.
  • 1 × return.type in sanitize_key() itself: Function sanitize_key() should return lowercase-string but returns string. The apply_filters() return type extension reads the type of the filter's first @param, which was string.

A full run goes from 27,467 to 27,417 errors, with no new errors. No baselines change, since none of these errors are baselined at level 5, and phpstan.neon.dist still reports no errors.

Alternative considered: scalar

Widening to scalar (documented as string|int|float|bool plus @phpstan-param scalar) was tried first. It fixes only the return.type error: every call site still passes mixed, which scalar does not accept either, and the strtolower() call gains a new error for bool|float|int|string. Making the call sites pass scalar would mean adding an is_scalar() check before each one, duplicating the check the function already does.

Changes

  • $key is documented as mixed in the function and in the sanitize_key filter, since the filter receives the original, unsanitized value.
  • Scalars are cast to string explicitly before strtolower(). This preserves the prior implicit coercion.
  • The filter gets @phpstan-param lowercase-string $sanitized_key, so that apply_filters() agrees with the function's existing @phpstan-return lowercase-string.
  • The nonstring_scalar data provider and its test are renamed to non_string, since the provider now covers non-scalar types too.
  • Test cases are added for a negative integer, a float in scientific notation (1.0E+25 → '10e25', which exercises lowercasing after the cast), INF (→ 'inf'), an array, an object, null, a stringable object, and a closure. A stringable object still yields '', because the guard is is_scalar().
  • A test is added for the arguments passed to the sanitize_key filter, using the same non-string cases.

Trac ticket: https://core.trac.wordpress.org/ticket/65817

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Analyzing the call sites and PHPStan errors, prototyping the scalar and mixed alternatives, writing the change and tests, and drafting this description. Reviewed by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

🤖 Generated with Claude Code

westonruter and others added 3 commits October 22, 2026 13:53
Since `sanitize_key()` guards with `is_scalar()`, it accepts any value: non-string scalars are coerced to a string, and any other type yields an empty string, as covered by its tests. Most callers pass raw request data or REST parameters, which PHPStan sees as `mixed`, so the `string` param type was reported at every such call site.

* Widen `$key` to `mixed` in the function and `sanitize_key` filter docs.
* Cast scalars to `string` explicitly before `strtolower()`, preserving the previous implicit coercion.
* Add `@phpstan-param lowercase-string` for the filtered `$sanitized_key` so the `apply_filters()` return type matches the function's `lowercase-string` return type.
* Add tests for more non-string scalars and for the arguments passed to the filter.

This resolves 50 PHPStan errors at rule level 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… provider.

This also covers the arguments passed to the `sanitize_key` filter for those types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e types.

The data provider now includes non-scalar types, so it is renamed from `nonstring_scalar` to `non_string`, along with the tests that use it.

Cases are added for infinity, `null`, a stringable object, and a closure. A stringable object still yields an empty string, since `sanitize_key()` checks `is_scalar()`. An `Exception` is used as the stringable object because an anonymous class declared in a data provider causes PHPUnit to load the test class twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter
westonruter marked this pull request as ready for review October 22, 2026 22:24
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

wporg-sync pushed a commit that referenced this pull request Oct 22, 2026
Since a non-scalar key was made to yield an empty string in r52292 and non-string scalars were again coerced to a string in r52370, `sanitize_key()` handles values of any type, and its tests cover this. Most of its callers pass raw request data or REST parameters, which are untyped, so documenting `$key` as a `string` caused PHPStan to report an error at nearly every call site.

The `sanitize_key` filter is updated to match, as it receives the original key, and its sanitized key is now typed as a lowercase string so that the result of `apply_filters()` agrees with the function's return type. The cast to `string` before `strtolower()` makes the existing coercion explicit without changing behavior. Tests are added for additional non-string types and for the arguments passed to the filter.

This resolves 50 PHPStan errors at rule level 10.

Developed in #14759.
Follow-up to r13718, r52292, r52370.

See #54160, #65817.


git-svn-id: https://develop.svn.wordpress.org/trunk@64682 602fd350-edb4-49c9-b593-d223f7449a82
wporg-sync pushed a commit to WordPress/WordPress that referenced this pull request Oct 22, 2026
Since a non-scalar key was made to yield an empty string in r52292 and non-string scalars were again coerced to a string in r52370, `sanitize_key()` handles values of any type, and its tests cover this. Most of its callers pass raw request data or REST parameters, which are untyped, so documenting `$key` as a `string` caused PHPStan to report an error at nearly every call site.

The `sanitize_key` filter is updated to match, as it receives the original key, and its sanitized key is now typed as a lowercase string so that the result of `apply_filters()` agrees with the function's return type. The cast to `string` before `strtolower()` makes the existing coercion explicit without changing behavior. Tests are added for additional non-string types and for the arguments passed to the filter.

This resolves 50 PHPStan errors at rule level 10.

Developed in WordPress/wordpress-develop#14759.
Follow-up to r13718, r52292, r52370.

See #54160, #65817.

Built from https://develop.svn.wordpress.org/trunk@64682


git-svn-id: http://core.svn.wordpress.org/trunk@63832 1a063a9b-81f0-0310-95a4-ce76da25c4cd
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.

1 participant