I recently I needed to organize the controllers of a CodeIgniter instance into sub-sub folders. By default, CodeIgniter only allows routing to sub folders in the controller directory:

application
-- controllers
---- api
------ users.php
------ items.php
---- welcome.php

With the above structure, you'd access your welcome controller at the following URI:

/welcome

And, you'd access the users API controller at this URI:

/api/users

The problem comes when you want to have a deeper controllers directory structure:

application
-- controllers
---- admin
------ api
-------- users.php
-------- items.php
------ home.php
------ shop.php
---- shop
------ api
-------- cart.php
------ catalog.php
---- blog
------ api
-------- article.php
-------- comments.php
------ home.php

By default, CodeIgniter will not route requests for the following URIs correctly:

/admin/api/item
/shop/api/cart
/blog/api/comments

How to Make it Work

First, if you have not already, you'll need to add mod_rewrite rules to your site's webroot to allow the routing of requests to your CodeIgniter's index.php file:

<IfModule mod_rewrite.c>

  Options +FollowSymLinks
  RewriteEngine On
  RewriteBase /

  # If your default controller is something other than 'welcome' you should probably change this.
  RewriteRule ^(welcome(/index)?|index(\.php)?)/?$ / [L,R=301]
  RewriteRule ^(.*)/index/?$ $1 [L,R=301]

  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ /index.php/$1 [L]

  SetEnvIfNoCase X-Forwarded-For .+ proxy=yes
  SetEnvIfNoCase X-moz prefetch no_access=yes
   
  # Block pre-fetch requests with X-moz headers.
  RewriteCond %{ENV:no_access} yes
  RewriteRule .* - [F,L]

  # Fix for infinite redirect loops.
  RewriteCond %{ENV:REDIRECT_STATUS} 200
  RewriteRule .* - [L]

</IfModule>

The above rules tell Apache to internally route all requests through the index.php file, but only for requests that do not match an existing file or directory. Once you've got rewrite rules in place and working, you can move on to the sub-sub-folder magic.

The Magic

You will need to extend the core Router class of CodeIgniter. All you have to do is add one file, application/core/MY_Router.php:

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

/*
    Extended the core Router class to allow for sub-sub-folders in the controllers directory.
*/
class MY_Router extends CI_Router {

    function __construct()
    {
        parent::__construct();
    }

    function _validate_request($segments)
    {
        if (count($segments) == 0)
        {
            return $segments;
        }

        // Does the requested controller exist in the root folder?
        if (file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
        {
            return $segments;
        }

        // Is the controller in a sub-folder?
        if (is_dir(APPPATH.'controllers/'.$segments[0]))
        {
            // Set the directory and remove it from the segment array
            $this->set_directory($segments[0]);
            $segments = array_slice($segments, 1);

            while (count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
            {
                // Set the directory and remove it from the segment array
                $this->set_directory($this->directory . $segments[0]);
                $segments = array_slice($segments, 1);
            }

            if (count($segments) > 0)
            {
                // Does the requested controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
                {
                    if ( ! empty($this->routes['404_override']))
                    {
                        $x = explode('/', $this->routes['404_override']);

                        $this->set_directory('');
                        $this->set_class($x[0]);
                        $this->set_method(isset($x[1]) ? $x[1] : 'index');

                        return $x;
                    }
                    else
                    {
                        show_404($this->fetch_directory().$segments[0]);
                    }
                }
            }
            else
            {
                // Is the method being specified in the route?
                if (strpos($this->default_controller, '/') !== FALSE)
                {
                    $x = explode('/', $this->default_controller);

                    $this->set_class($x[0]);
                    $this->set_method($x[1]);
                }
                else
                {
                    $this->set_class($this->default_controller);
                    $this->set_method('index');
                }

                // Does the default controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
                {
                    $this->directory = '';
                    return array();
                }

            }

            return $segments;
        }


        // If we've gotten this far it means that the URI does not correlate to a valid
        // controller class.  We will now see if there is an override
        if ( ! empty($this->routes['404_override']))
        {
            $x = explode('/', $this->routes['404_override']);

            $this->set_class($x[0]);
            $this->set_method(isset($x[1]) ? $x[1] : 'index');

            return $x;
        }


        // Nothing else to do at this point but show a 404
        show_404($segments[0]);
    }

    function set_directory($dir)
    {
        // Allow forward slash, but don't allow periods.
        $this->directory = str_replace('.', '', $dir).'/';
    }

}

/* End of file MY_Router.php */
/* Location: ./application/core/MY_Router.php */

That's it! CodeIgniter will automatically include this file and instantiate the class within. You should now be able to organize your controllers into as many sub folders, sub sub folders, sub sub sub folders as you like.