Creating a Simple Extendable Module System

Many web pages these days contain a sidebar filled with all sorts of goodies:  Calendars, Searches, Links, RSS News feeds, etc. For those DIYers this can get sort of ugly if you are sticking all of these into a single php document.  What we are going to show you in this article is how to create a simple way to import these types of features as self contained modules. First we'll start real simple, then cover a few ways to expand this.

What you'll need

Essentially you need a basic PHP enabled website (doh!), additionally for this lesson you'll need the File_Find class available from PEAR. File_Find is going to help us read in our modules. There are other ways to do this that aren't too difficult, but there is no reason to reinvent the wheel either.

Getting File_Find with PEAR

Provided you have pear properly installed, getting File_Find is rather trivial. To give it a check try typing pear at the command line. Just typing pear should result in the a print out some help options for using PEAR. For example:
stitch:~ scott$ pear

Usage: pear [options] command [command-options] 

Type "pear help options" to list all options.

Type "pear help " to get the help for the 
specified command.

Commands:

build        Build an Extension From C Source

bundle       Unpacks a Pecl Package

clear-cache  Clear XML-RPC Cache

config-get   Show One Setting

config-help  Show Information About Setting

config-set   Change Setting

config-show  Show All Settings

cvsdiff      Run a "cvs diff" for all files in a 
package

cvstag       Set CVS Release Tag

download     Download Package

download-all Downloads each available package from 
master_server

info         Display information about a package

install      Install Package

list         List Installed Packages

list-all     List All Packages

list-upgrades List Available Upgrades

login        Connects and authenticates to remote 
server

logout       Logs out from the remote server

makerpm      Builds an RPM spec file from a PEAR 
package

package      Build Package

package-dependencies Show package dependencies

package-validate Validate Package Consistency

remote-info  Information About Remote Packages

remote-list  List Remote Packages

run-tests    Run Regression Tests

search       Search remote package database

shell-test   Shell Script Test

sign         Sign a package distribution file

uninstall    Un-install Package

upgrade      Upgrade Package

upgrade-all  Upgrade All Packages


If you get that, or something similar, we can assume that PEAR is installed. Now to install File_Find, simply type:
stitch:~ scott$ sudo pear install File_Find  

Password:

downloading File_Find-0.2.0.tgz ...

Starting to download File_Find-0.2.0.tgz 
(3,245 bytes)

....done: 3,245 bytes

install ok: File_Find 0.2.0


(Note the sudo, you'll likely need administrative rights to install classes with PEAR. If you don't, you can still download the PEAR classes individually, and install them in a local php include directory.)

Finally to make sure everything worked like it should we can list our installed PEAR classes and make sure File_Find is there:
stitch:~ scott$ pear list

Installed packages:

===================

Package        Version State

Archive_Tar    1.2     stable

Console_Getopt 1.2     stable

DB             1.6.8   stable

File_Find      0.2.0   stable

HTTP           1.3.3   stable

Mail           1.1.4   stable

Net_SMTP       1.2.6   stable

Net_Socket     1.0.2   stable

PEAR           1.3.3.1 stable

SQLite         1.0     stable

XML_Parser     1.2.1   stable

XML_RPC        1.1.0   stable


Alas, everything looks right.

Creating a Simple Module

For our example here, the first thing we will do is create a simple module. Our first module will be called hello.mod. So initially we will create a couple of directories. First, we will create a module directory (we'll call it modules), under that we will create a directory called hello.mod to keep out module pieces. Finally, within our hello.mod directory we will create our main module file named module.php.

Our module.php file is going to simply contain the following:

     echo "Hello, I'm a module</br>";

A few things to note: since simplicity is key here, there need to be a few rules for consistency.  First of all, all module packages (i.e. folders containing the module code) need to end with ".mod" (you could use any other extension if you wanted, but you'll have to change some code below, and whatever you choose, you need to stick with it). Second, all module packages need to be kept in the same modules directory. Finally, each module must have a primary file (in this case module.php) and each module must name this file identically (this file acts just like an index.html file in a way).  Now each one of these rules can be broken, but doing so will require you to add new code to this example and add complexity to the project as a whole.

A quick look at the directory structure of our example is as follows:
./

|_ modules/

      |_  hello.mod/

               |_ module.php


Any additional modules can be dropped in. For example let's add helloagian.mod and in that moudule.php file we'll type:

    echo "Hello again... I'm a module too!</br>";

Now, our directory structure will look like:

./

|_ modules/

      |_  hello.mod/

      |       |_ module.php

      |

      |_  helloagain.mod/

              |_ module.php


Creating the Module Sidebar

The code for this is very easy, though for use in a real web page you'll want to add styles and perhaps some other things (we'll get to a few later). What we are going to start with is simply:

     $mod_path = "./modules/";      $mod_index_file = "module.php";      include_once('File/Find.php');      $modpkg = File_Find::glob(".mod", $mod_path);      foreach ($modpkg as $module) { include("$mod_path/$module/$mod_index_file");      }

There should only need to be a few points of explanation needed here. First of all a quick look at some of the initial variables.

  • $mod_path is the directory where you will put all of your module "packages"
  • $mod_index_file is the primary include file within each module package.


    Once we define those variables we include out File_Find class from which we will use the glob() function. The glob() function will search for any module package (by searching for the .mod extension) in our $mod_path . Each package it finds it will add to the $modpkg array.

    Finally, we will loop through our $modpkg array, and for each $module we will include the $mod_index_file and thus each module will get added to our module bar.

    Solving a Few Immediately Noticeable Issues

    Ok, so the code works, and now we have a simple way including modules. There are two immediate issues which we will want to solve: style and load order.

    The style issue is easily solved by changing our module code as follows:

        $mod_path = "./modules/";      $mod_index_file = "module.php";      include_once('File/Find.php');      $modpkg = File_Find::glob(".mod", $mod_path);      foreach ($modpkg as $module) {      print "
    " include("$mod_path/$module/$mod_index_file"); print "
    "      }

    This will allow you to attach a style for .module for all modules. Certain modules may require special unique styling too. This, however, should likely be done within the module code.

    The load order of our modules is a slightly bigger problem. Right now there is no guarantee that the modules will load in the order you want. Keeping with our simplicity theme, we can easily sort our modules with PHP's sort() function. To do this we once again add to our module code which will end up looking like:

         $mod_path = "./modules/";      $mod_index_file = "module.php";      include_once('File/Find.php');      $modpkg = File_Find::glob(".mod", $mod_path);            sort($modpkg);       // sort our modules // to control load order      foreach ($modpkg as $module) {      print "
    " include("$mod_path/$module/$mod_index_file"); print "
    "      }

    Now we need to alter our module names so that they sort in the proper order. An easy way to do this is to barrow number our modules so that hello.mod becomes 010_hello.mod and helloagain.mod becomes 020_helloagian.mod. Those of you who have learned good old fashioned Basic (QBasic, AppleBasic...etc.) will note the familiar numbering. By leaving gaps in the numbering scheme you can easily slide a new module in between two existing ones without needing to change the existing modules. So, if I wanted to later add a module to load between the "hello" and "helloagain" modules I can easily name it 015_inbetween.mod.

    What's next?

    Well, clearly the next thing is to create some real, useful modules, but that's another article. There are also a number of ways to take the basic ideas here and alter or improve upon them. PHP is incredibly flexible in that way. But until next time... enjoy.