Extension:Shared codebase

The Shared CodeBase Initiative (let's call it SCBI or "skibby", that'll be nice) is a project to make it easy to run multiple instances of MediaWiki on a server, with all instances sharing a common code base. This will have several benefits:
 * Reduces space (a little) required on the server: instead of having the entire codebase present for each instance, we only need the codebase installed in one master location, and then a few configuration files for each instance.
 * Changes to the codebase can be rolled out more easily: just edit the files in the master codebase and all the instances on the server will automatically get it.

In addition, it is desirable that each instance can independently override any functionality locally, without the change propagating to the other instances (if the change is desired to be global, it can be made in the master codebase, instead).

This is actually fairly easy to do in general, but there's probably a lot of hidden things that you we won't realize are broken right away. The basic idea is to configure the include_path (in each instance) to look for included files in the local directories (for that instance) first, and if it can't find them, then it goes to the master codebase. This also involves an override of the  function, which is used to automatically include the correct file for a specified class.

The main problem is that a lot of the include/require statements in the existing code base use the $IP variable as a path prefix, which creates an absolute path, so our include_path is no good. Similarly, a lot of them use  and   as prefixes for the code path. Even though these look like relative paths, they're only relative to the current directory, not to the include path, so once again, our include path is of no use.

So the primary effort for this is to find all the places in the code that do this, and just remove the prefixes that make them absolute paths. The only real downside to this (besides the work), is that it will take just a bit longer to include/require a relative path then an absolute path, because PHP needs to decide where to get the file from.

After downloading a pristine copy of mediawiki (version 1.11.0), I made of the code base, and in one copy, made all the changes that I've (so far) found to be necessary for this to work. Then I diffed the original codebase and my mods, so anyone interested in doing the same can just follow the diff to make the changes. The complete log of the diff, with a few of my comments (prefixed with # on the beginning of the line), is at /diff_1.11.0

Overriding functionality
Sadly, neither PHP nor the MediaWiki codebase is set up real well to accomodate the sort of thing we're trying to do here (not this I really think this was a foreseeable demand), but it is possible, you just have to get your feet dirty a little.

What we're basically going to do to override functionality locally, is to rename the existing class (and the file it's defined in) to suffix it with _Base. This is where all the master functionality is implemented. To override it, we'll create a subclass defined in our local directories, and just override the functions we want to. However, so we don't have to create a local subclass if we don't want to, we'll create a default subclass in the master codebase, which just doesn't override anything.

So here's how it will work. Let's take the Article class as an example. Straight out of the "box", it's defined in the file. In the master code base, we're going to rename this file, and inside the file, we'll simply rename the class from   to.

Next, we'll create a new  file, which looks like this: 

So what happens when somewhere in the code, we try to access the Article class. Well, at this point, our autoloader won't find the  file in our local instance directories, only in the master codebase, so it will include that one, which we showed above. The  class defined in that file is what we created above, it's just an empty subclass of Article_Base. So if it tries to call a function of the Article class, it will just get the implementation of that function from the Article_Base class, which is the original class from the original codebase, so everything's fine.

Now we want to override some functionality locally, which was our whole purpose for doing this. So back in the local instance, we create a new file. To start with, we can just copy the contents of the same file from the master code base (not the original  file, which we renamed to , but the new one we created as the default subclass). So what happens now when some code trys to access the Article class? The autoloader will look for the  file locally, and this time it will find it. So this file will include the  file (which is only in the master codebase, so it will get that one), and then define an empty subclass of the   class, called. In otherwords, it will do the same thing as before we copied it locally.

Now, however, we have the  class defined locally, with default (superclass) implementation defined in the master codebase. So now in the local  file, we can simply override any method of the   class that we want. When this method get's called, it will get our local implementation. However, any other instances that we have on the server that share this master codebase, will not get the new implementation, because they don't have their own Article.php file, so they get the one from the master codebase which doesn't override anything.

Statics
One little complication with this is the static fields. In our case, the static fields are supposed to belong to the Article class, but they're now defined in the  class, which means we can't access them. So we'll just have to cut them out of the Article_Base class, and paste them into all Article.php files. That means in our master codebase, and when we copy it down to a local instance for overriding. That's another reason why it's best for overriding to just copy the default subclass from the master codebase directly, to make sure you get all the static fields. Finally, you need to make sure that the base class ( in this case) can access any static fields it needs to (probably all of them), which means they can't be private.

Install script
Bash shell script to setup a new instance, run from the top level directory of the instance:
 * 1) !/bin/sh

if [$# -lt 1 ] then echo Requires one argument, the codebase directory. exit 1 fi

CODEBASE_DIR="$1"

mkdir config cp -v $CODEBASE_DIR/config/index.php? config/ cp -rv $CODEBASE_DIR/serialized. ln -s $CODEBASE_DIR/index.php index.php ln -s $CODEBASE_DIR/mb_entryPoint.php mb_entryPoint.php

echo "Done."

Pass in the path to the master codebase. Windoze users will have to copy index.php and mb_entryPoint.php, instead of linking. That just means you won't get any updates made to those files without manually rolling them out.

mb_entryPoint.php
This should be included at the top of any files that are entry points to the wiki, like index.php and config/index.php initially. Make sure to set the  and   as appropriate. <?php /** * Define the location and url for the master codebase, and some * helper functions for getting the path to a file or URL to * a resource, trying the local instance first, then going to * the codebase if neccessary. Also updates the include_path * so the instance directories come first, then the master * codebase dir. */

$GMB_CODEBASE = "/var/www/content/wiki/master/current"; $GMB_CODEBASE_URL = "/content/wiki/master/current";

$IP = getcwd;

$path = array( "$IP/", "$IP/includes/", "$IP/languages/", "$GMB_CODEBASE/", "$GMB_CODEBASE/includes/", "$GMB_CODEBASE/languages/" ); set_include_path( implode( PATH_SEPARATOR, $path ) . PATH_SEPARATOR . get_include_path );
 * 1) We put the local instance directories first on the list, then the master code base directories

function mb_getResource($relPath, $instanceScriptPath) /** * Get a URL for a resource (like an icon or something). Tries to find it * in the local instance, if it can't, it use $gCodeBaseUrl as a prefix. */ {       global $GMB_CODEBASE_URL;

if(file_exists($relPath)) return $instanceScriptPath. "/" . $relPath; else return $GMB_CODEBASE_URL. "/" . $relPath; } //end function mb_getResource

function mb_getFile($relPath) {       global $GMB_CODEBASE;

if(file_exists($relPath)) return $relPath; else return $GMB_CODEBASE. "/" . $relPath; } //end function mb_getFile

?>