Use a plugin pattern in PHP instead of global variable

There is a common situation in which for a same component we want it behaves differently in different environment. This is not just about PHP, it exists for any software system.

In OOP, we call it polymorphism. Its tempting to abuse a global variable in PHP to implement it. Declare a global varialbe, set a default initial value, and then overwrite it when needed.

Its dirty and quick, but usually not a good choice for code quality.

Lets look at a real example. I have this function called image_max_width, which will return a number in pixels, it used by a image processor, it will check the width of an image, if the width exceed the max width, it zooms the image to make it fit to the max width and write it to width and height attribute of img tag. If the images width less than max width, do nothing. This make sure our image will display properly in the content.

This value depend on the site theme, a two columns theme usually has a 750px wide content area, a single page theme is wider, like 900px. If we use different theme, this function should return a different value.

Using a global variable will be a disaster, the abstractions will leaks everywhere. Here is a solution I used to solve the global variable issue.

Instead of overwrite global variable, we can use a plugin

A plugin is an implementation can be plugged into a predefined place. And its replaceable.

Here, a plugin is just a function name, we can use an array to store the registered plugins.

 
$register_pluginfunc = array();
 
function plugin_register($name, $value) {
    global $register_pluginfunc;
    $register_pluginfunc[$name] = $value;
}
 
function plugin_notfound(){
    l()->debug("something unusual happened!");
}
 
function plugin_get($name) {
    global $register_pluginfunc;
    if(!isset ($register_pluginfunc[$name])) {
        return "plugin_notfound";
    }
    return $register_pluginfunc[$name];
}
 

Here is how to call a plugin, PHP has this ingenious feature which allows you call a function by the function name

 
$plugin = plugin_get('pluginname');
$plugin();
 

Notice that even a plugin has no registered implementation, the program will not throw exception, and the default implementation plugin_notfound is used. This usually an unnormal behavior, but you can catch it in plugin_notfound.

Now, go back to our problem. Our function will be implemented this way

 
function image_max_width_default() {
    debug("calling plugin image_max_width_default");
    return 750; // default
}
 
function register_defaultplugin_all(){
    plugin_register('image_max_width_plugin', "image_max_width_default");
}
 
function image_max_width(){
    $plugin = plugin_get('image_max_width_plugin');
    return $plugin();
}
 

A plugin named 'image_max_width_plugin' is used to return the width value, and by default, its implemented by image_max_width_default.

Here is how to change the behavior of the plugin, the convention here is the plugin function name is the theme name followed by a suffix. For example theme1_image_max_width

 
        $themename = get_themename();
        $pluginfunction = $themename. "_image_max_width";
        if(function_exists($pluginfunction)) {
            plugin_register('image_max_width_plugin', $pluginfunction);
        }
 

To apply the width of a particular theme, just define the function with name follows the convention. And when the theme is applied, the plugin will be replaced to the right one.

 
function theme1_image_max_width() {
    debug("using theme1 max image width");
    return 900;
}
 

Whats the advantage of this solution? The dependencies are gone, plugins independent with each other, global variables are gone. Now when you need to change the behavior, just add a function, no logic code is changed, thus won't break anything. If you don't provide the plugin, system fall back to default behavior. Its safe to add code, dangerous to change code. You can write your code with confident. In software design pattern, this is called Open Closed Principle.