From 1aa11859d044fcb2d047a123c7d71f4457d074dc Mon Sep 17 00:00:00 2001 From: akash-aman Date: Fri, 25 Sep 2026 18:23:34 +0530 Subject: [PATCH] Users: Separate `WP_User_Query` cache entries by result format. 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) --- src/wp-includes/class-wp-user-query.php | 8 +++- tests/phpunit/tests/user/queryCache.php | 55 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-user-query.php b/src/wp-includes/class-wp-user-query.php index 3815023924489..d8e447814391a 100644 --- a/src/wp-includes/class-wp-user-query.php +++ b/src/wp-includes/class-wp-user-query.php @@ -1055,9 +1055,15 @@ protected function generate_cache_key( array $deprecated, $sql ) { // Replace wpdb placeholder in the SQL statement used by the cache key. $sql = $wpdb->remove_placeholder_escape( $sql ); + /* + * An array of fields is fetched as objects, anything else as a list of values. + * The SQL can be identical for both, so keep them in separate cache entries. + */ + $format = is_array( $this->query_vars['fields'] ) ? 'objects' : 'values'; + $key = md5( $sql ); - return "get_users:$key"; + return "get_users:$format:$key"; } /** diff --git a/tests/phpunit/tests/user/queryCache.php b/tests/phpunit/tests/user/queryCache.php index fb556bfa36b21..4738e704e3283 100644 --- a/tests/phpunit/tests/user/queryCache.php +++ b/tests/phpunit/tests/user/queryCache.php @@ -802,4 +802,59 @@ public function test_generate_cache_key_with_orderby_post_count_and_deprecated_w $this->assertNotEmpty( $query->get_results() ); } + + /** + * Tests that queries with the same SQL but a different `fields` format do not share a cache entry. + * + * @ticket 62003 + * + * @covers ::generate_cache_key + * + * @dataProvider data_query_cache_should_respect_fields_return_format + * + * @param string|string[] $first_fields The `fields` value used to prime the cache. + * @param string|string[] $second_fields The `fields` value read from the cache. + */ + public function test_query_cache_should_respect_fields_return_format( $first_fields, $second_fields ) { + new WP_User_Query( array( 'fields' => $first_fields ) ); + + $cached = new WP_User_Query( array( 'fields' => $second_fields ) ); + $uncached = new WP_User_Query( + array( + 'fields' => $second_fields, + 'cache_results' => false, + ) + ); + + $get_format = static function ( $result ) { + return is_object( $result ) ? get_class( $result ) : gettype( $result ); + }; + + $this->assertSame( + array_map( $get_format, $uncached->get_results() ), + array_map( $get_format, $cached->get_results() ), + 'Cached results should have the same format as uncached results.' + ); + $this->assertEquals( + $uncached->get_results(), + $cached->get_results(), + 'Cached results should match uncached results.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_query_cache_should_respect_fields_return_format() { + return array( + 'string then array' => array( 'ID', array( 'ID' ) ), + 'array then string' => array( array( 'ID' ), 'ID' ), + 'array then all' => array( array( 'ID' ), 'all' ), + 'all then array' => array( 'all', array( 'ID' ) ), + 'array then all_with_meta' => array( array( 'ID' ), 'all_with_meta' ), + 'array then string field' => array( array( 'user_email' ), 'user_email' ), + ); + } }