Today I'll show a way of splitting up a plugins logic into multiple files without losing all the magic that makes writing habari plugins so easy and more importantly without making the plugin overly complex.
I assume you are already somewhat familiar with the Habari plugin system and of course that you know PHP. I will not explain anything in detail so don't expect a cut and paste tutorial. I'm just explaining the concept, the implementation I leave for you to experiment with.
The basics
Writing a plugin for Habari couldn't be easier. For instance all you have to do in order to expose a new method to your theme is add a public function whose name starts with "theme_" to your plugin.
Below is the comments_number function made famous by WordPress, implemented in a Habari plugin.
public function theme_comments_number($theme, $zero, $one, $more)
{
$c = $theme->post->comments->moderated->count;
switch ($c)
{
case '0':
return $zero;
break;
case '1':
return str_replace( '%s', '1', $one );
break;
default :
return str_replace( '%s', $c, $more);
}
}
The call to "comments_number" from the theme would look like this:
<h2><?php $theme->comments_number('No Responses', 'One Response', '%s Responses' );?></h2>
While this is quite powerful and good enough for most cases, sometimes you need a little flexibility. In Habari we can solve this with the "alias" method.
Hooking the aliases
The Habari documentation says:
"By creating a method named alias() in the pluggable class that returns an associative array, a plugin or theme can specify functions to use for specifically named hooks."
Habari exposes a set of predefined hooks that can be used by a plugin to alter the behavior of themes or in some cases Habari internal functions. In our very first example we talked about the function "theme_comments_number". Although this is not a Habari built in function it is regarded as a hook nonetheless. The only difference is that the hook is defined by the theme and not Habari core.
// single hook
function alias()
{
return array(
'my_hook_function' => 'filter_spam_filter',
);
}
// multiple hooks
function alias()
{
return array(
'my_hook_function' => array( 'action_post_content_out', 'action_post_content_excerpt', 'action_post_content_summary' ),
);
}
A simple module system
Using what we've learned so far we could easily build a plugin module system that loads the plugin logic from other files. For simple plugins there would be little to gain from this but for complex plugins that expose multiple logically separated functionality this would if nothing else increase the readability and maintainable of the plugin. A very basic module system could be implemented with just a handful of lines of code, where the heavy lifting is done by the "alias" and "__call" methods.
First we would need a way of loading modules. This could be a simple function that included all php files in a certain directory. The modules would then have to implement a specific interface exposing a function for our plugin/module handler to gather information of what functions to expose. Directly after plugin instantiation the alias() function, if it exists, will be called. This makes it an ideal place to trigger the loading of our modules. As mentioned above Habari expects the alias method to return a list of hooks and functions for the plugin, so we make sure our modules report back what functionality they expose when they are first loaded.
Because multiple modules may hook the same functions we need to keep track of which module loaded what somehow. One way of doing it would be to have an internal registry in our plugin, but for this example I chose to let Habari keep track for me by extending the name of the module function with the module name. That way we will know what module a function belongs to when the call goes through "__call".
public function alias()
{
$this->loadModules(); // loads our modules in to $this->modules
$aliases = array();
foreach( $this->modules as $name => $module )
{
$prepArray = function($module_name, $array)
{
$result = array();
foreach($array as $key => $val)
{
if ( strpos($key, $module_name) !== 0 )
{
$key = $module_name .'_'. $key;
}
$result[$key] = $val;
}
return $result;
};
$aliases = array_merge_recursive( $aliases, $prepArray($name, $module->aliases()) );
}
return $aliases;
}
Since Habari expects all functions registered in alias() to be functions in the main plugin class we need to add someway to listen for them. The easiest way to do that is using the magic function "__call". All we need to do in __call is find out what module the call belongs to and then pass it along together with all the parameters, and since we added the module name to the function names in alias() this is quite simple.
public function __call($method, $params)
{
if ( strstr($method, '_') )
{
list($module_name, $method_name) = explode('_', $method, 2);
if ( isset($this->modules[$module_name]) && method_exists($this->modules[$module_name], $method_name))
{
return call_user_func_array( array($this->modules[$module_name], $method_name), $params );
}
}
return false;
}
We can now write a simple module that will automatically be loaded, resulting in the function "my_test_method" to be exposed to the blog theme.
class myplugin_mymodule
{
public function aliases()
{
return array(
'test' => 'theme_my_test_method',
);
}
public function test()
{
return 'fiskar';
}
}
Beautiful isn't it?