The question:
Related to this answer, which states
After every instance of
switch_to_blog()
you need to callrestore_current_blog()
otherwise WP will think it is in a “switched” mode and can potentially return incorrect data.
I’ve done some testing, and can confirm that this is an issue. (It also answers a question I’ve had at work regarding file upload URLs, so two big thumbs up for that.)
In my testing (detailed below) I found that there were two ways to proceed:
- Always pair
switch_to_blog()
withrestore_current_blog()
- Use a chain of
switch_to_blog()
, with the last one switching back to the blog where you started from, and thenunset( $GLOBALS['_wp_switched_stack'] );
at the end.
My question: Is method #2 in keeping with WordPress development best practices, or is it an ugly hack?
My Testing
I added the following code to functions.php
on one of my WordPress Multisite installations:
add_action( 'shutdown', 'pj_stb_test' );
function pj_stb_test() {
if( ! current_user_can( 'update_core' ) )
return;
$home = get_current_blog_id();
$checklist = array(
'constants' => array( 'UPLOADS', 'MULTISITE', 'BLOGUPLOADDIR' ),
'functions' => array( 'ms_is_switched', 'wp_upload_dir' ),
);
$site_ids = array( 1, 2, 3 );
echo( "switch_to_blog() chain:<br />" );
foreach( $site_ids as $id ) {
switch_to_blog( $id );
}
switch_to_blog( $home );
_pj_dump( $checklist );
echo( 'switch_to_blog() chain followed by
unset( $GLOBALS['_wp_switched_stack'] )<br />' );
foreach( $site_ids as $id ) {
switch_to_blog( $id );
}
switch_to_blog( $home );
unset( $GLOBALS['_wp_switched_stack'] );
_pj_dump( $checklist );
echo( 'switch_to_blog() / restore_current_blog() pairings<br />' );
foreach( $site_ids as $id ) {
switch_to_blog( $id );
restore_current_blog();
}
_pj_dump( $checklist );
function _pj_dump( $checklist ) {
$constants = $checklist['constants'];
$functions = $checklist['functions'];
echo( "<p>Constants:</p>" );
echo( "<pre>n" );
foreach( $constants as $c ) {
echo( $c . ': ' );
var_dump( constant( $c ) );
echo( "n" );
}
foreach( $functions as $f ) {
echo( $f . ': ' );
var_dump( call_user_func( $f ) );
echo( "n" );
}
}
The output that was returned on an arbitrary site in my network:
switch_to_blog() chain:
UPLOADS: string(30) "wp-content/blogs.dir/94/files/"
MULTISITE: bool(true)
BLOGUPLOADDIR: string(50) "/path/to/wp/wp-content/blogs.dir/94/files/"
ms_is_switched: bool(true)
wp_upload_dir: array(6) {
["path"]=>
string(57) "/path/to/wp/wp-content/blogs.dir/94/files/2013/11"
["url"]=>
string(74) "http://example.com/my-site/wp-content/blogs.dir/94/files/2013/11"
["subdir"]=>
string(8) "/2013/11"
["basedir"]=>
string(49) "/path/to/wp/wp-content/blogs.dir/94/files"
["baseurl"]=>
string(66) "http://example.com/my-site/wp-content/blogs.dir/94/files"
["error"]=>
bool(false)
}
switch_to_blog() chain followed by unset( $GLOBALS['_wp_switched_stack'] )
UPLOADS: string(30) "wp-content/blogs.dir/94/files/"
MULTISITE: bool(true)
BLOGUPLOADDIR: string(50) "/path/to/wp/wp-content/blogs.dir/94/files/"
ms_is_switched: bool(false)
wp_upload_dir: array(6) {
["path"]=>
string(57) "/path/to/wp/wp-content/blogs.dir/94/files/2013/11"
["url"]=>
string(50) "http://example.com/my-site/files/2013/11"
["subdir"]=>
string(8) "/2013/11"
["basedir"]=>
string(49) "/path/to/wp/wp-content/blogs.dir/94/files"
["baseurl"]=>
string(42) "http://example.com/my-site/files"
["error"]=>
bool(false)
}
switch_to_blog() / restore_current_blog() pairings
UPLOADS: string(30) "wp-content/blogs.dir/94/files/"
MULTISITE: bool(true)
BLOGUPLOADDIR: string(50) "/path/to/wp/wp-content/blogs.dir/94/files/"
ms_is_switched: bool(false)
wp_upload_dir: array(6) {
["path"]=>
string(57) "/path/to/wp/wp-content/blogs.dir/94/files/2013/11"
["url"]=>
string(50) "http://example.com/my-site/files/2013/11"
["subdir"]=>
string(8) "/2013/11"
["basedir"]=>
string(49) "/path/to/wp/wp-content/blogs.dir/94/files"
["baseurl"]=>
string(42) "http://example.com/my-site/files"
["error"]=>
bool(false)
}
Sorry for the verbosity, but I wanted to make sure I had everything in here.
The Solutions:
Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.
Method 1
Short answer is NO. That is an ugly hack =) More global variables are affect than the ones that you are var_dump’ing. Here are the globals that I found affected: $wpdb, $wp_roles, $wp_object_cache, $global_groups, $GLOBALS[‘_wp_switched_stack’], $GLOBALS[‘blog_id’], and $GLOBALS[‘table_prefix’]. There could be more. I found that $wpdb was another major variable that was altered by switch_to_blog(). Try var_dump’ing $wpdb in your test and you will see the effect. Any APIs relying on those globals could be affected if you do not “Always pair switch_to_blog() with restore_current_blog()”
Long answer is “depends on the situation.” I would say the statement “After every instance of switch_to_blog() you need to call restore_current_blog()” is the best practice and generally true. But there are situations where you do not need to return to the originial blog or do restore_current_blog(). For example, I created an admin plugin which altered a users role across all blogs. It iterated through all the blogs in the network using switch_to_blog, called other WP APIs (that did not rely on those globals), and ended immediately. Hence no need to restore_current_blog(). YMMV depending on where and how switch_to_blog() is used.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0