Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

Related to this answer, which states

After every instance of switch_to_blog() you need to call restore_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:

  1. Always pair switch_to_blog() with restore_current_blog()
  2. Use a chain of switch_to_blog(), with the last one switching back to the blog where you started from, and then unset( $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.

share|improve this question
add comment

1 Answer

up vote 1 down vote accepted

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.

share|improve this answer
    
As far as your second ¶ goes -- I have a similar situation where I use switch_to_blog() repeatedly to generate a network-wide breadcrumb trail, and I think one of the consequences is that wp_upload_dir() is returning the full /wp-content/blogs.dir/[xxx]/files system path, rather than the /files path I expect. So I guess I'll need to add some restore_current_blog()s to that code. –  Pat J Nov 22 '13 at 14:51
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.