The path to an automagical multisite switch 01

What if switching to multisite installation in tests was a matter of one method call? Or “Where I dream and then write some code”.

What’s a multisite installation

In rough cut terms a WordPress multisite installation differs from a single site installation in three points:

  1. a bunch of additional tables in the database
  2. the definition of a set of constants (MULTISITE_INSTALLATION and others)
  3. a specific .htaccess file configuration

So anything that could take care of covering the above points and roll them back would make going from a single site installation to a multiste one possible.

Why?

As covered in length in other articles, see this as an example, I want to be able to set up complex fixtures using PHP methods in my tests in place of a SQL dump.
The term “complex” involves dealing with multisite and allowing me to use the same codebase and same local test server to test a plugin or theme against it.
In code I would like to be able to write this code in a Codeception CEST acceptance method

// active modules are Helper\Acceptance, WPDb and WPBrowser
// WPDb modules is cleaning up after each test

class OptionsCest
{
    public function _before(\AcceptanceTester $I)
    {
        // the plugin is active
        $I->haveOptionInDatabase('active_plugins', ['my-plugin/my-plugin.php']);
    }

    public function _after(\AcceptanceTester $I)
    {
    }

    public function dontSeeTheMultisiteOptionsWhenNotInMultisiteInstallation(\AcceptanceTester $I) 
    {    
        $I->loginAsAdmin();
        $I->amOnPage('/admin.php?page=my-plugin_options');
        $I->dontSeeElement('#multisite-options');
    }

    public function seeTheMultisiteOptionsWhenNotInMultisiteInstallation(\AcceptanceTester $I) 
    {    
        $I->haveMultisiteInDatabase(true, true); // sub-domain install and need subdomain htaccess file
        $I->loginAsAdmin(); // super-admin too
        $I->amOnPage('/admin.php?page=my-plugin_options'); // options page on main blog
        $I->SeeElement('#multisite-options');
    }
}

What I’m not doing in this imaginary code is having a second WordPress installation running as multisite to maintain and keep in sync.
How to get to that?

A bunch of additional tables

Since the haveMultisiteInDatabase is a WPDb module provided method creating and modifying tables is easy and the current version of the method already covers that:

public function haveMultisiteInDatabase( $subdomainInstall = true ) {
    $this->isSubdomainMultisiteInstall = $subdomainInstall;
    $dbh                               = $this->driver->getDbh();
    foreach ( $this->tables->multisiteTables() as $table ) {
        $operation         = 'create';
        $prefixedTableName = $this->grabPrefixedTableNameFor( $table );
        if ( $this->_seeTableInDatabase( $prefixedTableName ) ) {
            $query     = $this->tables->getAlterTableQuery( $table, $this->config['tablePrefix'] );
            $operation = 'alter';
        } else {
            $query = $this->tables->getCreateTableQuery( $table, $this->config['tablePrefix'] );
        }

        if ( !empty ( $query ) ) {
            $sth = $dbh->prepare( $query );
            $this->debugSection( 'Query', $sth->queryString );
            $out[$table] = [ 'operation' => $operation, 'exit' => $sth->execute( [ ] ) ];
        } else {
            $out[$table] = [ 'operation' => $operation, 'exit' => false ];
        }
    }

    $domain = $this->getSiteDomain();
    if ( !$this->countInDatabase( $this->grabSiteTableName(), [ 'domain' => $domain ] ) ) {
        $this->haveInDatabase( $this->grabSiteTableName(), [ 'domain' => $domain, 'path' => '/' ] );
    }
    if ( !$this->countInDatabase( $this->grabBlogsTableName(), [ 'blog_id' => 1 ] ) ) {
        $this->query( "ALTER TABLE {$this->grabBlogsTableName()} AUTO_INCREMENT=1" );
        $mainBlogData = [
            'site_id'      => 1,
            'domain'       => $domain,
            'path'         => '/',
            'registered'   => Date::now(),
            'last_updated' => Date::now(),
            'public'       => 1
        ];
        $this->haveInDatabase( $this->grabBlogsTableName(), $mainBlogData );
    }

    return $out;
}

Well, on to point 2 and 3.

A config file

Points 2 and 3 from the list above are trickier to cover as the full set of WordPress multisite constants is usually defined in the wp-config.php file.
As a database based module WPDb can only do so much and has to rely on some external help to fulfill the task.
Keeping in mind that the module will deal with test installations of WordPress I can think of a way of making this possible following along the same path that, as an example, the default YeoPress installation uses: the wp-config.php file will contain a line similar to the one below

if(file_exists(dirname(__FILE__) . '/local-config.php')){
    include dirname(__FILE__) . '/local-config.php';
}

that file usually contains settings local to the web developer local installation, the local-config.php file will not be pushed to the production server allowing for values, and constants definition, to be overridden only locally.
WordPress users, and power users especially, are used to instructions like “copy this code in the config file” and someone testing his/her code especially will be able to follow along.

Next

I will iterate over the concepts above to ship a first working version of the hopefully powerful method.