[e]
[e]
[e]

Data Warehousing with MySql

Data Warehousing is the idea of setting up a separate database structure for purely reporting purposes, so you can stop compromising between reporting and transactional demands.
MySql is a database that is fast, easy to use, and (generally) free.

> Aggregation
> Benchmarks
> ClickstreamData
> DatesAndTimes
> Home
> Indexing
> KPIs
> Links
> MathML
> Money
> MysqlCompared
> PhpPlugins
> Recipe
> Regression
> Scripting
> Sudoku
> SummaryData
> TableTypes
> TuningMysql
New Page:

search:

>
edit css
show debugging
contact: tom (.) cunningham (at) gmail (dot) com
[e]
[e]

Plugins in PHP



Why Plugins?


For a large serious program, there are a lot of good reasons to support for plugins. Implementing such a plugin mechanism would be (I imagine) a complicated affair.
 
What I wanted to do was make a very simple lightweight plugin system, in PHP, which let you add a new PHP file which added new features by overriding certain functions.

How They Should Work



The way I want plugins to work is just this: any function in the base program can be replaced by a function from a plugin, eg. an XML plugin would replace save() with xml_save().

There is another way of doing plugins: where you add calls in certain places in the code, like "do_plugin('save')" -- so that a plugin can attach to any one of these events. Eg., every time a button is clicked you could call a "button_click" event. This second method is probably better for big programs, so that you can reorganise the base code functions without corrupting the plugins, but for a small program it seems like too much overhead.

These are the things which I wanted a plugin framework to do (I didn't know this was what I wanted until I wrote 4 different plugin schemes and found them failing one or other of these criteria).

(1) You should be able to add plugins without modifying the base code. This was pretty easy to do, with:

 foreach( glob( "plugin_*.php" ) as $plugin_fn ) {
      include( $plugin_fn );
  }


(2) The plugin's function should have access to the base function itself. Then you can do things like this:

  function logging_save() {
     log( "saving now" );
     save();
  }


Here the plugin doesn't replace the original code -- it just adds something extra to it.

(3) Plugins should cascade, in a definable order. Then two different plugins can both be called on the same function, and in a sensible order. Eg., if you had a logging plugin which overrides the save() function then passes on control, and an XML plugin which overrides the save() function but doesn't pass control; ideally the logging function would be called first, and the XML function second.

I'm not sure what the best rules are for determining order of plugin cascade, but in any case the plugin system should be able to support some such rules.

One problem that should be mentioned here: the cascade-order could be definied either at the level of an entire plugin, or at the level of an individual function within a plugin. Conceivably, two plugins could exist, such that a function of the first should *precede* a function of the second, but another function of the first should *succeed* another function of the second's.

This is a genuine problem, but insisting that it be solved destroys the simplicity of many of the schemes I am considering. Also, there does exist a workaround: simply define two different plugins, one for each function, with different precedence rules.


(4) The plugin mechanism should not force the code to do simple things in roundabout ways. But more importantly, if it does make you write complicated code, it should prevent you from accidentally doing it the simple way.
For example, if the mechanism requires that all method calls should be written as:

$this->plugin_top->save();


Then it should fail outright when you do it the straight-forward way:

$this->save();


Otherwise there are bound to be cases when you accidentally call a function the wrong way, and cause hard-to-find bugs.

(5) It would be a bonus if there were not too many calls to eval().

(6) The system, if class-based, should support abstract methods.

(7) PHP4 compatible. I wanted my system to be PHP4 compatible, just so it could run on a standard hosted account on a web-server.

(8) The mechanism, if based on classes, should work on multiple classes. Some of the examples below are written to allow plugins to override methods on just one class, but I think that they are all easily extendable to support multiple classes, principally by creating a class "PluginBase" which contains all the functionality, then inheriting the other classes from that base.

(A) Inheritance.


Some of these criteria sound familiar: they're like inheritance. In fact (2), (3), (4), and (5) are all solved very cleanly by inheritance.

The first problem with implementing plugins with inheritance is that PHP doesn't support multiple-inheritance. (There is a function aggregate() in PHP5 which may work partially, but it has restrictions).

This problem can be avoided by making a chain of inheritance, so that each plugin inherits off the next plugin, all the way up to the base class. The problem with this is that you don't know which plugins you have until run-time, but inheritance cannot be set at run-time.

So another workaround: use eval() to arrange the inheritance at run-time. Load the code for each of the plugins into a string, then do a search and replace on the piece of code which specifies the class inheritance, to make each plugin inherits off the next in the chain. Then evaluate all the code.

This scheme seems to work OK, with the following code:

class Base {
    function get_html() {
        return "here I am";
    }
}

$last_class= "Base";
$plugins= glob( "pluginexp_*.php" ) or Array();
foreach( $plugins as $plugin_fn ) {
    $code= file_get_contents( $plugin_fn );
    preg_match( "/class ([\w]*) extends PLUGIN/", $code, $matches );
    $code= str_replace( "PLUGIN", $last_class, $code );
    $last_class= $matches[1];
    eval( $code );
}

$obj= new $last_class();
echo $obj->get_html();


The only drawback of this code is that you are realying heavily on eval(). First, the code may be slow because of this; second, evaluating strings as code is an intrinsically unpleasant thing to do: it is difficult to debug.

(B) Using PHP5 overloading


 In PHP5 you can define the functions "__call()", "__get()", and "__set()" which will be called whenever you call a method, get a property, or set a property.
 Typically you define this kind of function with a switch statement, like:

  function __get( $property_name ) {
      switch( $property_name ) {

         case "credit_card_number":
             return "you wish";

         default:
             return $this->$property_name;
      }
  }


  Here is a very simple implementation of a plugin handler as two class methods:

function register_plugin_function( $base_method, $plugin_method ) {
   @$GLOBALS['plugin_functions'][$base_method][]= $plugin_method;
}
function __call( $method, $arguments ) {
   if( @$plugin_function= current( $GLOBALS['plugin_functions'][$method] ) ) {
       call_user_func_array( $plugin_function, Array( $this, $arguments ) );
   } else {
       return call_user_func_array(array($this, "_". $method ), $arguments );
   }
}


  This system has a couple of problems: first, the "__call" method only gets invoked if you call a method which isn't already defined, so all the base class's methods need to be renamed (eg., "save()" becomes "_save()"), to allow the plugin-handler to take control.

  Second, the "__call" method doesn't trap calls to *abstract* methods, which indeed is a pain. There is a workaround for this too: replace all your abstract methods with methods on another class, instantiate that class, and let the instatiation be a global variable (or something similar).

  The biggest problem is that it won't run on PHP4, so it won't run on most public web hosting providers.

(C) using PHP4 overload()


  As of 4.3 PHP4 has a function overlad() which does the same thing as the PHP5 extension. However, this functionality is labelled experimental in PHP4, and has another peculiarity: __call() does not get invoked when you call a method from another method in the same class. This makes it pretty much useless.

(D) using containers


  I got another idea from the book "Design Patterns". They have a pattern they call the "Container".

  The idea is this: one object can be contained by another object in the same way as one class can inherit from another class: each method passes responsibility up the chain, until it terminates somewhere.
 
 

  class Base {
        function save() { ... }
        function print() { ... }
  }
 
  class Container {
        var $next_object= NULL; // this points to the next object in the chain,
                                // and would be initialised in a constructor.
        function save() { return $this->next_object->save(); }
        function print() { return $this->next_object->print(); }
  }
 
  class LogPlugin extends Container {
       function save() {
           log( "Saving." );
           parent::save();
       }
  }
 
  $object= new LogPlugin();
  $object->save();
 


  This system has a couple of small but annoying drawbacks:
   (1) In the base class, you must not call a method with $this->save(), because then it won't go through the plugin containers. You have to do something like: $this->top_plugin->save() so the execution goes to the right place.
   (2) In the plugin methods, you cannot refer to object properties as $this->property, instead you have to have some construction like $this->base_object->property.

  It doesn't take much effort to follow these practices, but if you're hoping to encourage other people to write plugins for your system, quirks like this will put them off very quickly.
 

(E) using functions


 One final scheme which I haven't tried yet is to go back to just using functions instead of objects. You can convert methods to functions by making a data structure which contains all of the object's properties, and pass that as the first argument to each function.
 Then each function like "save" is just a stub which passes control to a plugin-dispatcher, which in turn calls other routines: first all the plugged-in functions and then the base function (which might be called "base_save()").

Conclusion


 

Appendix



You can test PHP4 compatibility by setting the following in PHP.INI:

zend.ze1_compatibility_mode = 1


Total Time: 0.57s
REQUEST:Array
(
    [link_body/body] => {incl:PhpPlugins}
    [PHPSESSID] => b9a289e2959005ab5c102380940272d1
)
SESSION:Array
(
    [body/body] => {incl:PhpPlugins}
)
MESSAGES:PLUGINS: