User Group-specific Controllers in CodeIgniter

I’ve been building out an application that has 2 types of users, corporate and operations. Each user type has access to a lot of the same methods, but operations has access to a little more than corporate. I’ve been struggling with how to break up the code for these two types of users since a lot of it is shared. At first, I thought I had a couple options:

1. Separate the logic into different directories

This would mean that I have completely separate controllers and I would be duplicating methods that are shared for each type of user. I started building this out as an HMVC application with a module for operations that provided the extra methods available to that type of user. This gave me regular URLs like products/edit/48 and operations users would get operations/products/edit/48.

Issues:

I found myself duplicating a lot of code for methods that were shared for each user type and I didn’t really want 2 URLs to access the same method.

2. Create user-specific methods in the same controller

I thought about prefixing methods with “operations_” or “corporate_” so I would have methods like “operations_edit”. Doing things this way I would have had to:

  1. See what type of user is trying to access the method
  2. See if a method with a prefix of their user type exists
  3. Route them accordingly

Issues:

I didn’t like this approach because my controllers would be huge and I’d have to manage 2 of the same method for each type of user, even though I’d try to share private methods with each user type to limit code duplication.

My Current Solution

I thought about it a little more and I decided the way I would like it to work is if I could have a single controller that both user types share, and then be able to provide a controller with user-specific methods if there is anything that I want to override or add only for one user type. I ended up with a file structure like this:

  • modules/products/controllers/products.php – The default
  • modules/products/controllers/operations.php – Operations only
  • modules/products/controllers/corporate.php – Corporate only

Then I use CodeIgniter’s _remap() method to route the user accordingly. This method will:

  1. Check if there is a controller that has methods specific to that user type (corporate.php or operations.php)
  2. Check to see if the requested method exists in that controller
  3. If the method exists, execute it, otherwise try to execute it from the default controller
public function _remap($method, $arguments)
{
    $controller = strtolower($this->router->class);

    if ($controller === $this->uri->segment(1))
    {
        if ($this->acl->is_admin())
        {
            $user_type = 'operations';
        }
        else
        {
            $user_type = 'corporate';
        }

        if ($module = $this->load->module($controller .'/'. $user_type))
        {
            if ( ! method_exists($module, $method))
            {
                $module = $this->load->module($controller);
            }
        }
    }
    else
    {
        $module = $this->load->module($this->uri->segment(1) .'/'. $controller);
    }

    call_user_func_array(array($module, $method), $arguments);
}

Break It Down Now Ya’ll

We start out with CodeIgniter’s _remap() method and setup the $controller variable to be the requested class name lowercased.

public function _remap($method, $arguments)
{
    $controller = strtolower($this->router->class);
}

Then we add a check to see if the requested controller is the default controller for the module. I do this because I don’t want to always override the requested controller, I still want to be able to have controllers for things like product images and categories in the same module. If it’s not the default controller for the module, I just load the requested controller.

if ($controller === $this->uri->segment(1))
{

}
else
{
    $module = $this->load->module($this->uri->segment(1) .'/'. $controller);
}

If they are requesting the default controller, I check to see what type of user they are and assign to to a variable. This could, and probably should, be setup dynamically in most cases.

if ($this->acl->is_admin())
{
    $user_type = 'operations';
}
else
{
    $user_type = 'corporate';
}

Next, I attempt to load the module and their respective controller. If the controller exists, I check to see if the requested method exists. If the method doesn’t exist, that means that I haven’t created a group-specific method for that type of user so I load the default controller.

if ($module = $this->load->module($controller .'/'. $user_type))
{
    if ( ! method_exists($module, $method))
    {
        $module = $this->load->module($controller);
    }
}

And finally, we call the actual method.

call_user_func_array(array($module, $method), $arguments);