Users: Separate WP_User_Query cache entries by result format - #13754
akash-aman wants to merge 2 commits into
Conversation
When `fields` is an array, `WP_User_Query` fetches rows as objects; any other value is fetched as a flat list of values. A single-field array such as `array( 'ID' )` generates exactly the same SQL as `'ID'` or `'all'`, and because the cache key was derived only from the SQL, whichever query ran first decided the format returned to every later query. With a persistent object cache this returned objects where strings were expected (and vice versa), `WP_User` objects with empty data from `get_users()`, and a fatal error for `'all_with_meta'` (for example in `wp user list` or the Users list table). This adds the result format to the cache key, so each format has its own entry. Every key changes, so entries cached before this change are not read afterwards. Follow-up to [55657]. See #62003. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
WP_User_Queryfetches results in two different formats depending onfields: an array of fields returns rows as objects (get_results()), anything else returns a flat list of values (get_col()). A single-field array such asarray( 'ID' )generates exactly the same SQL as'ID'or'all', andgenerate_cache_key()builds the key from the SQL only. So whichever query runs first decides the format that every later query receives from the cache.With a persistent object cache, this causes:
WP_Userobjects with an emptyuser_login,user_email, etc. fromget_users()afterget_users( array( 'fields' => array( 'ID' ) ) );'all_with_meta'(Cannot access offset of type stdClass on arrayinclass-wp-user-query.php), e.g. inwp user list, Users → All Users, and on multisite Network Admin → Users.Full reproduction details are in comment:3 on the ticket.
Changes
src/wp-includes/class-wp-user-query.php:generate_cache_key()now includes the result format in the key:get_users:objects:<hash>, and all other queries useget_users:values:<hash>.'ID','all'and'all_with_meta'still share an entry, because they cache the same list of IDs.tests/phpunit/tests/user/queryCache.php: addstest_query_cache_should_respect_fields_return_format()with a data provider of 6 cases:'ID'array( 'ID' )array( 'ID' )'ID'array( 'ID' )'all''all'array( 'ID' )array( 'ID' )'all_with_meta'array( 'user_email' )'user_email'Each case compares the cached result with the same query run with
'cache_results' => false. It checks the format of every item withassertSame(), then the values.Testing
Tested on trunk with Redis as the persistent object cache:
'all'/'all_with_meta'cases.phpunit --filter Tests_User_Query_Cache: 42 tests, all pass.phpunit --group user: 1350 tests, no failures.wp user list, the Users list table, and Network Admin → Users on a multisite network all work. Entries cached incorrectly before the change are not returned after it.Related
#13727 by @irozum takes the same approach, with a hash-based key and a single-case test. This PR differs in its readable
objects/valueskey and its broader test coverage. Happy to consolidate into one PR, or to have the data provider added to #13727, whichever reviewers prefer.Trac ticket: https://core.trac.wordpress.org/ticket/62003
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Investigating the root cause, reproduction scripts, implementation, tests and this description. I reproduced the issue myself on local sites, and I reviewed and revised the implementation and tests.
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