Revised: PHP.ActiveRecord + CodeIgniter 2.0 Reactor 2.0+

Note: This post is essentially an update to my previous post a few months back, but I revamped it for a few good reasons.  The main reason is that CodeIgniter Reactor is a bit different from the old 1.7.x Core branch, and a secondary reason being that I hacked the last one together in a very hasty manner for a project I needed to complete.  Since then I’ve refined the process a bit and it should not only be more reliable, but also be a bit faster as it aleves some overhead.

Alright, let’s get started!

  1. Grab the nightly build of PHP.ActiveRecord (the stable release is severely out-of-date and lacks several features and bugfixes mentioned in the documentation.
  2. Grab a copy of CodeIgniter Reactor 2.0+ from CodeIgniter.com or their BitBucket page.
  3. Extract your CodeIgniter installation and move it into your web server’s directory
  4. Extract PHP.ActiveRecord into your ./application/third_party directory.

This is where we astray from the previous method and make this implementation a bit more CodeIgniter-like.

In ./application/config/config.php, change:

$config['enable_hooks'] = FALSE; 

to

$config['enable_hooks'] = TRUE;

then close it out.  This will allow hooks to be called in your application, and our next step requires this.  Open ./application/config/hooks.php and add the following:

$hook['pre_controller'][] = array(
    'class'    => '',
    'function' => 'initialize_php_activerecord',
    'filename' => 'ActiveRecord.php',
    'filepath' => 'third_party/php-activerecord'                                
);

This adds a hook to the CodeIgniter initialization that will call the “initialize_php_activerecord” function just before the controller constructor is called to ensure our models are available if needed.  We’re going to create that function next and add some autoloading magic that will keep true to the “load only what you need” mentality of CodeIgniter.

Open up ./application/third_party/php-activerecord/ActiveRecord.php and swap out the existing code with the following:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

function initialize_php_activerecord() {
    if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
        die('PHP ActiveRecord requires PHP 5.3 or higher');

    define('PHP_ACTIVERECORD_VERSION_ID','1.0');

    // This constant allows you to prepend your file to the autoload stack rather than append it.
    if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND')) {
        define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true);
    }

    // This line simply states that if we haven't opted to disable the autoloader, add it to the autoload stack
    if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE')) {
        // Because we're prepending - we need to load the library after the models
        spl_autoload_register('activerecord_autoload', false, PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
        spl_autoload_register('activerecord_lib_autoload', false, PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
    }

    // The Utils.php file has some namespaced procedural functions, so we must require it manually.
    require 'lib/Utils'.EXT;

    // Include the CodeIgniter database config so we can access the variables declared within
    include(APPPATH.'config/database'.EXT);

    $dsn = array();
    if ($db) {
        foreach ($db as $name => $db_values) {
            // Convert to dsn format
            $dsn[$name] = $db[$name]['dbdriver'] .
                '://'   . $db[$name]['username'] .
                ':'     . $db[$name]['password'] .
                '@'     . $db[$name]['hostname'] .
                '/'     . $db[$name]['database'];
        }
    } 

    // Initialize ActiveRecord
    ActiveRecord\Config::initialize(function($cfg) use ($dsn, $active_group){
        $cfg->set_model_directory(APPPATH.'models');
        $cfg->set_connections($dsn);
        $cfg->set_default_connection($active_group);
    });
}


function activerecord_lib_autoload($class_name)
{
    $lib_path = APPPATH.'third_party/php-activerecord/lib/';

    if (strpos($class_name, 'ActiveRecord') !== FALSE) 
    {
        $class = substr($class_name, strpos($class_name, '\\')+1);

        if (file_exists($lib_path.$class.EXT))
            require $lib_path.$class.EXT;
    }
}

function activerecord_autoload($class_name)
{
    $path = ActiveRecord\Config::instance()->get_model_directory();
    $root = realpath(isset($path) ? $path : '.');

    if (($namespaces = ActiveRecord\get_namespaces($class_name)))
    {
        $class_name = array_pop($namespaces);
        $directories = array();

        foreach ($namespaces as $directory)
            $directories[] = $directory;

        $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);
    }

    $file = "$root/$class_name".EXT;

    if (file_exists($file))
        require $file;
}

At this point, PHP.ActiveRecord is ready for use with CodeIgniter 2.0.  I will certainly admit this is not the simplest way ever to add in PHP.ActiveRecord, but there are many people who reject doing a simple include() (for whatever reason) in CodeIgniter, so I kept up with traditional CodeIgniter idioms as much as possible while still getting what I want from the whole thing.  I personally am rather satisfied with this solution, but if you have your own opinions on it, feel free to let me know.

Next post I’m going to go over keeping your controllers DRY with PHP.AR and CI, including some info on model-based validations over CI validations.

Edit: I’ve also released a Spark for this functionality to be quickly added to a CodeIgniter install.  It matches the first implementation much more closely and is not as optimized as the solution I’ve just explained, but you can’t beat it’s setup speed (php tools/spark install php-activerecord.  DONE!).  Check it out here: php-activerecord on getsparks.org