Plugin Architecture
In this post I’m going to talk about how the RedFeather plugin system is implemented. I’m also give a rough overview on how the rest of RedFeather works and provide a rationale for the decisions I made regarding the design.
Given that the primary motivation behind RedFeather is the desire for simplicity I hat to make sure that the plugin system was just as simple. The first thing to consider was installation. This is a particular problem with EPrints, where a plugin is usually composed of many files which need to be individually copied in the correct directories in the source in order to work. While, the Bazaar package manager goes a long way towards making it easier for the user, it increases the complexity of both the plugin itself and the EPrints platform. With RedFeather each plugin is composed of a single php file which is can be installed by simply copying it into the webspace alongside the main repository. Depending on the complexity of the plugin, a small amount of additional configuration may be required but ultimately that’s all it takes. This simplicity also translates to the loading process; plugins are activated by appending their content to the main RedFeather file using a PHP include – this means that there is no need to parse or process the plugins in any way. It also maintains consistency within the system since there is absolutely no difference between a plugin and the RedFeather core itself. In effect, the main RedFeather source is really just a collection of the most useful plugins.
So, how does RedFeather work? Function pointers.
Literally everything in RedFeather is achieved via the clever use of functions: page loading, toolbar rendering, and workflow management. Furthermore, ever single function can be overridden in a plugin, allowing you to change the behaviour of almost any part of RedFeather. If you don’t like the layout of the browse page you can simply create a plugin that replaces that part of the system. The challenge then becomes one of code factorisation – how do I best break down the code into functions in order to facilitate code reuse? In the example where we’re editing the browse page there existing code is already broken down into various chunks; there is code to render individual fields, individual resources, the complete list of resources and the entire browse page itself. If you wanted to adjust the browse page to include a description at the top, you would only have to edit the “page_browse” function (which only consists of a few lines). Likewise, if you wanted to add an icon to each resource you might edit “generate_resource”.
This leads us onto a problem: too many functions reduces the readability of the code – made worse due to the fact that, as of the time of writing, the core RedFeather source code alone contains over 70 function. There is where good code structure and consistant naming conventions become critical. For example, functions prefixed with “render_” always write to the output buffer whereas functions beginning with “generate_” return html. However, functions names also serve a far more useful purpose in controlling program flow. The most obvious example is the prefix “page_”, which is used to denote the entry point for any URL access (for example, the url “index.html?page=resource_manager” will automatically call the function ‘page_resource_manager”). This makes it very easy to write plugins to define new pages, since all one must do is define a single function. I’ll talk more about function pointers in a future post.
In addition to the cunning function pointers there are also a number of global variables which are used to pass information around the program. The $BODY and $TITLE variables are used as output buffers for the html body and title when rendering a page using a inbuilt template system (which I will talk about in a future post). The $DATA variable is pre-loaded on every page with the metadata of all the resources in the repository and can be written back to persistance using the “save_data” function. The $CONF variable is the most important of all and contains all the configuration information – from the repository name itself, to the width of the resource preview widget. While it is generally considered “bad practise” to use global variables I felt they actually improved code readability in this case because they provide a consistent way to access frequently used values. Also, passing them around would just add unnecessary overhead to every single function call.
I think that’s enough background on the RedFeather implementation for now – I’ll go into more detail in future posts.
Leave a Reply