Today I found a major shortcoming of CodeIgniter. Basically, in a project I’m working on, there were a bunch of different things that a User could add to their account that would need to be approved by an AdminUser before they would appear. For fairness and ease of use, I wanted to have a page on the admin panel which listed all the items awaiting approval, with the oldest submission first, with buttons to approve them.

However, the things that might be awaiting approval are all totally different types of objects. They each need treating rather differently when they are approved.

Of course I could start writing huge staircases of if statements, but this sort of thing encourages tight coupling.

What’s Coupling?

Coupling is the relationship between classes.

In Object Oriented Programming, we may describe a “tightly coupled” system, in which classes heavily rely on “knowing” details about many other classes in the system. This is undesirable.

Programmers should aim to have a system which is “loosely coupled”, so that we can update parts of the system without disastrous knock-on effect to other parts.

I decided the best solution was to create an interface which described the methods I needed these “approvable” objects to implement – things like getting the date they were submitted for approval for that oldest-first list on the admin, approving them, un-approving them, and checking if they are approved. This would mean the classes carrying out the approving logic wouldn’t need to know details about how these items were implemented.

But working within CodeIgniter, it quickly became clear that this was going to be more than a little bit of a pain. There was no native way to load interfaces. I tried autoloading it as a library and as a model, but of course CodeIgniter wants to initialize an instance of the class, which can’t be done with an interface, so it fails.

I considered hacking the CodeIgniter core, but that’s not ideal because it makes it difficult to update the framework. So, I extended the core instead. This is done by simply adding a file called MY_Loader.php under /application/core with contents like this:


<?php
class MY_Loader extends CI_Loader
{
/**
* List of paths to load interfaces from
*
* @var array
* @access protected
*/
protected $_ci_interface_paths = array();
function __construct()
{
parent::__construct();
//we do all the standard Loader construction, then also set up the acceptable places to look for interfaces
$this->_ci_interface_paths = array(APPPATH, BASEPATH);
}

public function initialize()
{
parent::initialize();
// After the parent is initialized, we load the autoload config
if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
}
else
{
include(APPPATH.'config/autoload.php');
}
// and if $autoload['interface'] is in the config, load each one
if (isset($autoload['interface']))
{
foreach($autoload['interface'] as $interface)
$this->iface($interface);
}
}

/**
* Load an interface from /application/interfaces
*
* @param $interface string The interface name
* @return CI_Loader for chaining */
public function iface($interface = '')
{
$class = str_replace('.php', '', trim($interface, '/'));

// Was the path included with the interface name?
// We look for a slash to determine this
$subdir = '';
if (($last_slash = strrpos($class, '/')) !== FALSE)
{
// Extract the path
$subdir = substr($class, 0, $last_slash + 1);

// Get the filename from the path
$class = substr($class, $last_slash + 1);
}
// Look for the interface path and include it
$is_duplicate = FALSE;
foreach ($this->_ci_interface_paths as $path)
{
$filepath = $path.'interfaces/'.$subdir.$class.'.php';

// Does the file exist? No? Try the remaining paths
if ( ! file_exists($filepath))
{
continue;
}

include_once($filepath);
$this->_ci_loaded_files[] = $filepath;
return $this;
}
}
}
/* End of file MY_Loader.php */
/* Location: ./application/core/MY_Loader.php */

See the comments for details on how that works. I put my interface Approvable in a file called approvable.php under /application/interfaces

Then I added the following to my /application/config/autoload.php file:

$autoload['interface'] = array('approvable');

And presto, CodeIgniter was loading my custom interfaces the same way it loaded models and libraries.