Filed under: Programming · Date: Sun Aug 24 00:56:00 2008
In one project I needed to customize our content management system so that it allowed some domain specific operations be performed in various places across the administration interface. Naturally I didn't want to modify the CMS code -- it would have required intrusive changes which would in turn have made it very costly to upgrade the modified CMS.
Behaviour customization falls nicely in the domain of plugins, and that was exactly what was implemented. This post details how I did the plugin system.
There are a couple of ways to do plugin architechture. All methods require that the code which allows plugins to alter it's behaviour, or data, calls the plugin infrastructure to notify that a plugin can perform its task.
The simplest way to craft plugin architechture is to load all plugins and let them set up the callbacks in place. This works well if limited to seldom used parts of the application, like administration, which has little traffic in comparison to the public portion of the web application. If we wanted the plugins to work outside of the administration interface, loading, parsing, and executing all plugins for each page request would be quite heave for the server. This applies only to scripting languages which need to parse and execute the code for each request.
The CMS I'm working on is written in PHP5 and needs to be able to perform well in various environments, including those without opcode caching. This requirement makes the approach described above unoptimal.
The approach I chose uses a database table to list all plugins which respond to each specific event. Each hook in the system which allows plugins to participate in the logic of the application searches the database for plugins which have announced their capacity to handle that type of event. This approach allows the application to scale to large amount of plugins without adding much strain to the request handling.
For administrative tasks one design can't be said to be superior to another design unless the other has serious performance problems. Like in this example, the strain is not on the administration side, which means that both methods I've described can be used for the base architechture of plugin system. Choosing one is purely a matter of taste. However, considering the applicability to non-administrative operations, and comparatively low overhead in PHP5 environment, the database architechture was deemed to suit better my current and future needs.
The database which holds the plugins and the hooks the plugins attach themselves to is simple. Due to the nature of our CMS, we use two tables.
CREATE TABLE module (
id SERIAL NOT NULL,
name VARCHAR NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE hook (
name VARCHAR NOT NULL,
module INTEGER NOT NULL,
PRIMARY KEY (name, module),
FOREIGN KEY (module) REFERENCES module (id)
)The first table contais module ID and the directory name of the module. For clarity I have omitted the information about the modules, which do not contribute to this post.
Each plugin, when installed, writes the name of the callback hook and module identifier to the hooks table. Each time a hook can be called, the database is scanned for all modules which say they handle the hook. The modules found must implement the hook callback interface for a uniform calling mechanism. Inheriting wont work, since we don't want to limit callback hooks to any specific class.
interface hookCallback {
public function call($hook, &$data);
}The call() method will receive the name of the hook that triggered it, and a data structure the callback can modify. The datastructure usually holds the data that the CMS is editing at the moment, and the hook callback is free to modify it.
Calling the hooks in the core CMS code is done via a static class.
function doSomething() {
$myData = array('foo' => 'bar');
HookHandler::invoke('hook_name', $myData);
DB::store('test_table', $myData);
}The HookHandler class is responsible for finding suitable plugins for the method invoked. Each plugin found will be called in installation order. If any plugin returns false, then the processing of plugins will be halted.
A plugin system is easy to build and device. When it is introduced to an existing project, adding all the callback hooks in place is often more demanding task than building the system in the first place.
Using plugins will allow for creative customization without sacrificing upgradeability of the base system code.