Eliminate switch_to_blog() from multisite option/post functions#11257
Eliminate switch_to_blog() from multisite option/post functions#11257soderlind wants to merge 10 commits intoWordPress:trunkfrom
switch_to_blog() from multisite option/post functions#11257Conversation
- Introduced _get_option_from_blog() to retrieve options for a specific site without switching context, improving performance and reducing potential side effects. - Updated get_blog_option() to utilize the new function, maintaining functionality while avoiding unnecessary context switches. - Modified get_blog_post() to fetch posts directly from the database without switching blogs, ensuring consistency and efficiency. - Added tests to verify that get_blog_option() and get_blog_post() do not alter the global switched state, ensuring expected behavior in multisite environments. See https://core.trac.wordpress.org/ticket/64863
|
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. |
Updated the assertions in the Tests_Option_Multisite class to improve code consistency by removing unnecessary spaces around array keys in the checks for the $GLOBALS['switched'] variable. This change enhances readability and maintains coding standards across the test cases.
Replace switch_to_blog()/restore_current_blog() pairs in get_blog_option(), add_blog_option(), delete_blog_option(), update_blog_option(), WP_Site::get_details(), and get_blog_post() with direct DB queries using $wpdb->get_blog_prefix($blog_id). Add _get_option_from_blog() internal helper for non-switching option reads with object-cache support (blog-alloptions/blog-notoptions groups). Add regression tests verifying $GLOBALS['switched'] is not set after cross-site option and post operations.
… blog ID in update_blog_option()
Motivation
switch_to_blog()mutates six globals and, on the fallback object-cache implementation, wipes the entire object cache. Several core functions use it internally even though they only need to read or write a single row in a per-site table. This patch replaces those internal switches with direct queries using$wpdb->get_blog_prefix( $blog_id ), which is a pure function — no side-effects, no globals written.Files changed
1. ms-blogs.php
New function:
_get_option_from_blog()(added beforeget_blog_option())wp_N_optionstable without switching blog context.get_option()when$blog_id === get_current_blog_id().wp_cache_get( $blog_id, 'blog-alloptions' )andblog-notoptionsbefore hitting the DB.SELECTagainst$wpdb->get_blog_prefix( $blog_id ) . 'options'.blog-notoptions.blog_option_{$option}filter.Refactored:
get_blog_option()Refactored:
add_blog_option()Refactored:
delete_blog_option()Refactored:
update_blog_option()2. class-wp-site.php
Refactored:
WP_Site::get_details()(private)if ( false === $details ) { - switch_to_blog( $this->blog_id ); + $id = (int) $this->blog_id; $details = new stdClass(); foreach ( get_object_vars( $this ) as $key => $value ) { $details->$key = $value; } - $details->blogname = get_option( 'blogname' ); - $details->siteurl = get_option( 'siteurl' ); - $details->post_count = get_option( 'post_count' ); - $details->home = get_option( 'home' ); - restore_current_blog(); + $details->blogname = _get_option_from_blog( $id, 'blogname' ); + $details->siteurl = _get_option_from_blog( $id, 'siteurl' ); + $details->post_count = _get_option_from_blog( $id, 'post_count', 0 ); + $details->home = _get_option_from_blog( $id, 'home' );3. ms-functions.php
Refactored:
get_blog_post()4. multisite.php
3 new test methods added:
test_get_blog_option_does_not_switch_context()get_blog_option()on another site must not set$GLOBALS['switched']totruetest_update_blog_option_does_not_switch_context()update_blog_option()on another site must not set$GLOBALS['switched']totruetest_wp_site_get_blogname_without_switching()WP_Site::__get('blogname')returns correct value and doesn't set$GLOBALS['switched']5. site.php
1 new test method added:
test_get_blog_post_does_not_switch_context()get_blog_post()returns correctWP_Postwhen fetching from another siteDesign decisions
_get_option_from_blog()— prefixed with_per WordPress convention for internal/private APIs.get_option()/update_option()/add_option()/delete_option()/get_post()for full filter and cache compatibility.blog-alloptionsandblog-notoptionsmatch the existing WordPress core cache strategy.add_,update_,delete_) invalidate both cache groups.update_blog_option()— compares serialized old/new values and returnsfalsewhen unchanged, matchingupdate_option()behavior.get_blog_post()— returnsnullon failure (matches documentedWP_Post|nullreturn type).Out of scope
get_blog_details()(deprecated function at line 249 still usesswitch_to_blog)wp_initialize_site()/wp_uninitialize_site()— deeper coupling to switched contextTrac ticket: https://core.trac.wordpress.org/ticket/64863
Use of AI Tools
GitHub Copilot and Opus 4.6 have been used to review the changes.
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.