Thursday, March 22, 2012

Organizing your libraries like a boss

Recently I've been creating a lot of small PHP libs/modules that I would like to be able to reuses in other projects. Issues always come about when I need to include the files from other modules and how to keep them path independent and organized without wrapping everything into a parent namespace. Namespaces, autoloaders, and a bootloader has been the best solution I've come up with until now. So for example, I have a message queue module (group of classes) which I keep in a directory /hacklabs/modules/mq/. Everything in this directory is namespaced in 'mq', for example the class "queue" ( /hacklabs/modules/mq/queue.php ):
namespace mq;

class queue {
    # ...
}
/hacklabs/modules/mq/exceptions/empty_queue_exception.php:
namespace mq\exceptions;

class empty_queue_exception {
    # ...
}
In another project, I would like to use this mq module. Now comes the bootloader of the mq module ( /hacklabs/modules/mq/bootloader.php )

define('MQ_PATH', dirname(__FILE__));

# add the parent directory to the include path:
set_include_path( get_include_path() . PATH_SEPARATOR . realpath( dirname( __FILE__ ) . '/../' ) );

spl_autoload_register();
At this point, if I want to use the module 'mq' from a project in /hacklabs/projects/big_project/ all I need to do is include the bootloader from my modules and the default spl autoloader will take care of the rest:
namespace big_project;

require_once('/hacklabs/modules/mq/bootloader.php');
# also load a forums module bootloader:
require_once('/hacklabs/modules/forums/bootloader.php');

$queue = new \mq\queue();
$msg = new \mq\message();

try {
    $queue->send( $msg );
} catch ( \mq\exceptions\empty_queue_exception $e ) {
    # ...
} catch ( \exception $e ) {
    # ...
}

$forum = new \forums\forum();
# ... 
Using this technique, I can make module specific defines in the modules bootloader while the actual autoload include path is anything in /hacklabs/modules/*. Include just the modules bootloader, then the rest is handled via spl_autoload_register() and namespaced code.