CI _remap function - The Friend you never knew you had

By now you have (hopefully) learned to use MY_Controller to manage site-wide variables and business logic, and are inheriting from it to separate functionality like public, private and admin areas. But still...something is missing. You find yourself with a handful of methods on each controller that have ALMOST the same rules, but just different enough that you end up copy/pasting 80% of the same business logic into each one on a case-by-case basis.

Enter _remap()

This is one of those functions that SEEMS kind of cool, but you can never quite work out what to do with it. No point using it for your routes when you have the the routes config. Hooks can take care of those weird "pre" cases you might stumble on. MY_Controller is for inherited functions.

Well, here is a case you have probably had at some point where this little function is perfect. Let us make a very simple but somewhat "real" example - we have a controller where we would like to put all our methods for user projects. We have three methods for this example (but in real life much more) - an index page that is a completely open informational page, and two pages for our "Project Builder", imaginatively named "step_one" and "step_two".

Our business logic says that the project builder steps must have a project id, and that we must finish an earlier step before we can access the later ones.

So, let's build a simple controller for this:

class Remap_tut extends CI_Controller {
	public function index()
	{
		echo 'Howdy, pardner!';
	}

	public function step_one($project_id)
	{
		echo 'Welcome to the first step for Project '.$project_id;
	}

	public function step_two($project_id)
	{
		echo 'The last step for Project '.$project_id;
	}	
}

So, where do we go from here? Well, we know that the index page doesn't need anything added, just the view rendering (we'll just echo to keep this simple). We need a bit of code or a function to check if a valid project id is given, and redirect to an error page if not. We also need a function that will check what step we have completed against the step we are trying to access, and again redirect us away if we don't pass.

So let's add two functions (in an excessively simple manner) and a message page:

public function message($message_code)
{
	switch ($message_code) {
		case 1:
			echo "Where's yer id, pardner?";
			break;			
		case 2:
			echo "Gettin' a little ahead a yerself, ain't ye, pardner?";
			break;
	}
	
}

function _check_project_id($project_id)
{
	if (empty($project_id)) redirect("remap_tut/message/1");
	return true; 
} 	

function _check_step($method, $current_step)
{
	//obviously not how we'd really do this
	$method_array = array('step_one'=>1, 'step_two'=>2);
	if ($method_array[$method] > $current_step) redirect("remap_tut/message/2");
	return true;
}

So, now we just put the two function calls into each one of our step_# functions, and we're away. Just copy...paste. To every one of our functions...

Doesn't that feel wrong somehow? Of course it does! What if we have 15 steps? What if we think of another rule to add..in 15 places. What if we realize we need another parameter?

Enter  (for real this time) _remap()

Instead of copying the business logic to every place that needs it, let's force all our methods to pass through a single entry point and check them as needed. We already do something similar when we set variables or load models in the __construct(). The _remap() function ( http://codeigniter.com/user_guide/general/controllers.html#remapping ) works in a similar fashion, but has the advantage of already knowing your method and parameters. So, here we go:

public function _remap($method, $params = array())
{	    
    if (method_exists($this, $method))
    {
    	//just a dummy value we would get from our database or session
    	$current_step = 1;

    	//our logic
    	if (in_array($method, array('step_one', 'step_two')))
    	{
    		$this->_check_project_id($params);
    		$this->_check_step($method, $current_step);
    	}

        return call_user_func_array(array($this, $method), $params);
    }
    show_404();
}

// slightly reworked from our earlier version. 
function _check_project_id($params)
{
	if (empty($params[0])) redirect("remap_tut/message/1");
	return true; 
} 

 

What is this doing? It gets the method name and the parameters from the url. We check our method name and see which functions need to be applied to it before we can proceed. If everything passes, we use the php call_user_func_array function to apply the params to that method and call the page normally.

So now, we can see in one place what all of our business logic is. When we make a new page, we just add to the appropriate conditionals. If we need to change the function or remove it all together - again, one place.

Hope that helps.

Contact me