3. Controlling Traffic

There are two main parts of the framework that are hit when a user visits your web site or application: the Router and a Controller. The great news is that you have control over the path which the request follows through your application.

The first part of this chapter will be a look at the Routing portion of the request. The second part will examine Controllers in all of their magical glory. Finally, we’ll look at a number of common ways I’ve found that are handy to tweak your controllers to make them a pleasure to use.

Routing Traffic

When a user visits your site, CodeIgniter will run the URI through the router, looking for any matches in the routes you specified in the routes config file at /application/config/routes.php. These routes are really just patterns to match the URI against, along with instructions of where to send the requests which match each pattern.

If no match is found, it will try to find a controller and method that match the URI.

If this fails, it will go one step further and look for a “default controller” to process the request.

Failing that, it will throw a 404 error, telling the world that the requested page cannot be found. Don’t worry, you can control how the 404 is handled if you need to.

In the following sections, we will take a detailed look at how each of the elements work, how you can take control of them, and the enourmous amount of flexibility they provide.

Default (Magic) Routing

If not given any other orders in the routes config file, the router will attempt to match the current URI against an existing controller and method. Let’s look at a few examples to make sure this is all clear. More specific details are also available in the CodeIgniter user guide.

  • /users will look for the file /application/controllers/Users.php. Since no additional segments exist, it will try to use the index() method in that controller.
  • /users/list will look for the file /application/controllers/Users.php and try to use the controller’s list() method.
  • /manage/users will also work if you have a controller in a sub-directory at /application/controllers/manage/Users.php. In this case, it will try to run the index() method, since no method is listed.
  • /admin/manage/users will work if you are even further nested in the controllers directory, looking for /application/controllers/admin/manage/Users.php. Once again, the index() method will be used.

Note: /admin/manage/users can also be found at /application/controllers/admin/Manage.php if the admin directory contains a Manage controller which has a method called users(). Similarly, /manage/users could be found at /application/controllers/Manage.php. Keep this in mind when using directories to organize your controllers and magic routing, as it can be fairly easy to inadvertently override an existing route if you’re not careful.

Default Controller

The first of the reserved routes at the top of your routes.php config file, the default_controller setting allows you to specify a controller name which the router will attempt to use if no other match can be found for the given URI. This is commonly used to determine what will be shown when the user visits your site but doesn’t supply any information for the router, like just visiting your domain at http://mydomain.com. By default this is set to the Welcome controller:

52 $route['default_controller'] = 'welcome';

So, when the user visits http://mydomain.com, the router will look for the file /application/controllers/Welcome.php and call the index() method.

While that’s quite handy, it doesn’t stop there. If you have any directory in the controllers directory that contains a controller with that name, it will also be displayed. This can be quite handy for building out the “dashboard” of an admin area, for example.

When a user visits http://mydomain.com/admin you could have a file at /application/controllers/admin/Welcome.php and the router would call its index() method.

404 Override

The 404_override setting specifies a controller to be used whenever the requested controller isn’t found.

Like the other routes, you set 404_override to the name of the controller and method that you want to use. The format should be like any other route, with the controller and method names separated by a forward slash.

$route['404_override'] = 'errors/page_missing';

This will look for a controller named Errors and a method named page_missing(). It will not pass anything to the function. You can use any of the standard URI methods to determine which route generated the error.

Here are a few ideas of how you might use this new controller to your benefit:

  • List other possible pages the user may have wanted
  • Attempt to determine whether a page has moved (if you keep track of that within the app)
  • Provide a search box to search your site.
  • Provide links to popular content on your site.
  • Log the missing pages so the admins can try to spot problems and/or patterns.
  • Try to redirect the user to a similar page (like a category page on a blog, if that can be determined). You’d want to provide them a note indicating why they wound up on that page instead of the page they requested, though.
  • Check if it’s a bot and end there, or, if not a bot, then show one of the other options. This could help to relieve a bit of server usage for bots following bad links.
  • It could probably be used as part of a hacking-detection scheme, though I’ve never actually tried that.

To help design your page, consider these links for good info:

Translate URI Dashes

The translate_uri_dashes setting simply converts dashes (-) to underscores (_). For example, the URI articles/some-great-article is converted to a method name compatible with PHP’s naming requirements, like articles/some_great_article.

Why would this matter? A few years back this was a big deal as people were attempting to maximize their SEO rankings. It turned out that Google and Bing differed on how they handled them. As far as I can tell, this is still an issue, so using dashes could help your site’s SEO, while underscores could hurt it. Underscores can also be misinterpreted for spaces by users.

Turning this on will not magically make your links use dashes, though. You will still need to ensure your links are created apropriately for this to work for you.

Blocking Magic Routing

What do I mean when I say “Blocking magic routing”? Remember, “magic routing” is a term for CodeIgniter’s ability to look at a URI string and match it to controllers and methods based on the URI segments. In recent years, especially since Laravel came to town, the idea of using your routes as documentation and requiring that all routes are explicitly specified, rather than determined by convention, is taking hold with many developers. CodeIgniter presently has no built-in method to disable its “magic routing” feature.

If you want to turn “magic routing” off, you must resort to tricking the system a little bit. Don’t worry, though, it’s pretty simple to do.

In order to make this work for you, you need to add the following route to the bottom of your routes config file:

$route['(.+)'] = 'something';

There are two parts to this solution. The first part is the regular expression within the route’s key, (.+). This will match any character, number, etc., which means it will match every route that you pass to the application. Then, in order to get it to send us to a 404, we provide a name which doesn’t exist anywhere else on the system. It can be junk text, the name of your favorite band, or even “midi-clorians”, (unless you’re running a wiki on Star Wars).

Defining New Routes

Now that you know the basics of the routes file and how it’s used, it’s time to look at the various ways you can define new routes. We already did a quick refresher on how basic routes work, above. Now it’s time to dive into a few ways to customize the working of these routes without needing to modify any framework code, or even extend the core classes.

Wildcards

Wildcards are placeholders in your routes. They may or may not be inserted into the final method at your discretion. In essence, they are simply regular expressions that are applied to your route when trying to match it against the current URI. While the user guide already provides some great info on wildcards, we will show a few examples here as a quick refresher.

$route['product/:num'] = 'catalog/product_lookup';

The placeholder here is :num, which will match a URI segment containing only numbers. One thing to notice about this example is that the product ID that you’re matching is not passed to the final destination. Instead, you’d have to use $this->uri->segment(2) to retrieve the product ID.

If you want to pass the product ID to your method, you need to wrap the wildcard in parentheses and use it as a back reference in the destination.

$route['product/(:num)'] = 'catalog/product_lookup/$1';

// In your Catalog controller:
public function product_lookup($product_id) { . . . }

What happens if your product IDs are not just numbers, but contain some alphabetic characters as well, like pid1234 or tshirt001? Then you would use the (:any) wildcard. This actually matches any character except for the forward slash so that it restricts itself to a single URI segment.

$route['product/(:any)'] = 'catalog/product_lookup/$1';

You can use more than one placeholder, at any place in the URI, and match it in any order in your destination.

// Match /api/v1/products/ts123
$route['api/v(:num)/products/(:any)'] = 'v$1/products/$2';
Optional Wildcards

To really wrangle routes to your needs, you only have to remember that all route wildcards are simply regular expressions. As such, we can do a lot of crazy pattern matching here. The user guide covers a couple of good examples, but one thing not covered there which shows up in other frameworks is the optional segment. By crafting the regular expression to match 0 or more characters with the asterisk (*) we can easily create wildcards that will match whether the segment is there or not.

$route['product/[^/]*'] = 'catalog/product_lookup';

This converts our previous product lookup URI to match either with or without a product ID, like /product or /product/ts123.

This can be used with back references in the destination, as well, provided your method is defined with a default value.

$route['product/?([0-9]*)'] = 'catalog/product_lookup/$1';

// In your Catalog controller:
public function product_lookup($product_id = null) { . . . }

For your reference, here are the optional versions of CodeIgniter’s two provided placeholders.

  • [^/]* - optional version of :any
  • ([^/]*) - optional version of (:any)
  • [0-9]* - option version of :num
  • ([0-9]*) - optional version of (:num)
Custom Wildcards

Once you start customizing your routes with regular expressions, you will find that your routes become pretty fragile and hard to read. After all, regular expressions weren’t made for easy readability. Is there anything that you can do about this? Absolutely! You can create custom variables to hold the new custom placeholders with semantic names which make reading the routes much easier for you, your team, and whoever inherits the project.

For example, if you use a unique id in your URI instead of a user id, and this unique id can contain both letters and numbers, you can add the following line to the top of /application/config/routes.php and then use it in any of your routes.

// Define the wildcard
$uuid = '[a-zA-z0-9]*';

// Us it in your routes:
$route["users/({$uuid})"] = '/users/show/$1';

In this example I didn’t include the parentheses in the $uuid, so the identifier could be used with or without back references, but you could always include the parentheses in the variable if you intend to always use back references.

// Define the wildcard
$uuid = '([a-zA-z0-9]*)';

// Use it in your routes:
$route["users/{$uuid}"] = '/users/show/$1';
Callbacks

Callbacks allow you to modify the destination portion of your route dynamically at run time. While the use of these is probably pretty limited, you might find some great uses for them and, if you do, please let me know so that I can share them.

One of the most common uses that I can think of would be to create dynamic destinations to reduce your route creation. For example, if you’re creating an e-commerce site and you want to be able to route to all of the different categories in your site, with a separate function in your controller for each one, you could do something like:

$route['category/(:any)'] = function ($category) {
	return 'categories/' . strtolower($category) . 'List';
}

This would take any category as the back-reference, say “shirts”, and send it to the Categories controller, with a method named shirtsList. This allows you to only write a single route to handle all of the top-level categories, instead of creating 50 different routes (one for each category), or having to modify the routes if you add a new category.

Versioning Your Routes

There are times when you need to have different versions of your application. This is especially true when you have an API that is live, and you’re working on the next version. You don’t want to break anything that uses version 1, but you want to provide additional functionality for version 2 users. This can be easily handled with a little PHP in your routes file.

The first step is to determine the version of the API you’re going to use. With the changes made to the URI class from v2 to v3, this is a really simple step. At the top of your routes file, collect the current API version (you’ll need to modify this to match your route). Once you have the version, you can use that anywhere you would normally put the version number.

59 global $URI;
60 $version = $URI->segment(2);
61 if (empty($version))
62 {
63 	$version = 'v1';
64 }
65 
66 $route["{$version}/users"] = "{$version}/users/list_all";

Since higher routes take precedence, you could include another file that has your version-specific route overrides and new routes. To reduce memory usage and improve performance, you would want to only include the current version. This would change the previous code to something like this:

59 global $URI;
60 $version = $URI->segment(2);
61 if (empty($version)) 
62 {
63 	$version = 'v1';
64 }
65 
66 if ($version !== 'v1')
67 {
68 	require APPPATH . "config/routes_{$version}.php";
69 }
70 
71 $route["{$version}/users"] = "{$version}/users/list_all";

Here I don’t check to see if the file exists primarily because I want it to throw an error during development so I know when I forget the file. If we are in production, the errors won’t show up, and either the API will send back its error, or the application’s error-handling process will take over to display a nice error page.

HTTP Verb-Based Routing

There are times when you need to respond to a request based on the HTTP verb that was used, whether it is a standard one (GET, POST, PUT, DELETE, PATCH) or a custom one (like PURGE). This is most often used when creating a RESTful API, but could be useful at other times, also.

CodeIgniter has a built in method of doing this which is very simple to use.

In order to define a route that responds only to one HTTP verb, you add the verb as an array key to your route. The verb is not case-sensitive.

$route['products']['get'] = 'product/list_all';
$route['products/(:any)']['get'] = 'product/show/$1';
$route['products/(:any)']['put'] = 'product/update/$1';
$route['products/(:any)']['delete'] = 'product/delete/$1';

This makes it very easy to setup your entire API pretty quickly. If you want to speed things up even more, you can create a small helper function in your routes.php file to quickly create a set of standard resources for you.

59 function map_resource($resource) 
60 {
61 	echo "$route['{$resource}']['get'] = '{$resource}/list_all'\n";
62 	echo "$route['{$resource}']['post'] = '{$resource}/create'\n";
63 	echo "$route['{$resource}/(:any)']['get'] = '{$resource}/show/$1'\n";
64 	echo "$route['{$resource}/(:any)']['put'] = '{$resource}/update/$1'\n";
65 	echo "$route['{$resource}/(:any)']['delete'] = '{$resource}/delete/$1'\n";
66 }
67 
68 map_resource('products');
69 map_resource('photos');
70 map_resource('users');

Controllers

At their core, controllers are very simple to understand, and the user guide does a great job of telling you the basics. However, it doesn’t talk much about the practicalities of their usage, so this chapter will focus on understanding and using controllers as you will likely use them in your applications.

No matter how deep your understanding of their inner workings are, though, knowing the best ways to organize your controllers, and distribute the methods throughout them, is essential to create a pleasant project to work in. If you have not read Chapter 1: Where Does This Go? yet, then I highly recommend you read that to get a better understanding. Especially the section on Controller Design.

CodeIgniter’s Magic Instance

The first thing you need to understand about controllers is that they form the central hub of all of the “magic” that happens in your application. Whenever you call $this->load or get_instance() you are actually working with an instance of the active controller. This can be crucial to understand if you want to take full advantage the system, or just debug some especially problematic cases.

Whenever you call get_instance() it is grabbing a reference to the CI_Controller class that’s loaded into memory. Your controllers will extend CI_Controller, or one of its children. This means that get_instance() is not providing you with some stand-alone singleton class that handles loading, etc. Nope. It means that it is returning your controller.

What ramifications does this have? Any library, model, or helper that uses get_instance() has full access to any public properties in the controller, and can call any public methods in the controller. Most of the time, you are going to want to avoid using the controller’s properties and methods just to keep things clean. There may be times that you could take advantage of this, though. Let’s take a look at a couple of them.

“Global” methods

You can use MY_Controller to implement some methods that can then be used in any controller, library, or model. While this is similar to loading up a helper with some common functions in it, it has the benefit of still having direct access to information in the controller that might be protected.

You might use this within an API to provide a method to get the current user’s permissions, role, paired client companies, etc. The controller might verify information with the Auth module, check any paired client access, verify role, etc. during instantiation. It could then provide a method that can be used pretty much anywhere to retrieve that information.

“Global” Objects

Any class that has been loaded through $this->load is available within your other classes. The best use I have found for this is being able to access authentication classes. I actually discovered this by accident one time while debugging why the heck a call to get the current user’s id within a model was working, even though I had never loaded the appropriate class within the model.


I do want to warn you, again, to be very careful when you decide to use these types of “global” capabilities. They can be handy, but also have the potential to cause confusion and make things harder to debug. They can also complicate the testing process dramatically. Only use this functionality after carefully weighing the pros and cons.

While you can use these features, do it rarely. It’s not the cleanest method, and can cause complications down the road. It is usually better to have a function as part of a library so you can pass the dependencies (classes) in as part of the params array.

More than anything, this section should serve as a set of examples that you can look for in your code when things are misbehaving. Then, armed with the understanding of why your application is doing what it’s doing, you can decide what to do about it.

Remapping Methods

Controllers can define one special method that CodeIgniter will look for to help you wield some additional control over how your controllers handle a request. If the _remap() method exists, it will always be called and override anything that the URI might otherwise specify. The _remap() method has the ability to route a request however you wish.

The parameters passed to the _remap() method can be very helpful in determining how the request should be resolved. The first parameter is the name of the method specified in the URI. The second parameter is an array of the remaining segments in the URI. Together, these parameters can be used to emulate CodeIgniter’s default behavior, or do something completely different.

public function _remap($method, $params = array())
{
    if (method_exists($this, $method))
    {
        return call_user_func_array(array($this, $method), $params);
    }
	show_404();
}

So why use the _remap() method instead of routing? In many cases, it is a matter of preference, since they can both handle many of the same things, as long as the action can get to the controller. They both have their special uses, though. The router can direct to different controllers. The _remap() method, though, can also manipulate the parameters being passed to the class.

Here are a few example uses.

RESTful Methods

If you want to simplify your routes file, and not specify every single variation of HTTP verb and action for every controller, you could remap your methods based on the method name and HTTP verb being used.

public function _remap($method, $params = array())
{
	$method = strtolower($_SERVER['REQUEST_METHOD']) . '_' . $method;
    if (method_exists($this, $method))
    {
        return call_user_func_array(array($this, $method), $params);
    }
	show_404();
}

This would look for methods like get_list, get_show, put_update, etc.

Infinite Methods

Imagine you’re creating an e-commerce store. You have a categories controller that you are using to display all of the products based on a category. That should be simple enough - just one method would handle all of it, right? In some cases, absolutely, but what happens when you have a few categories that need to pull or display different information, while the rest of them can happily run through the single method? In that case you’re going to need new methods in the Categories controller for each of those special cases.

You could create a new route for each category that mapped to the new methods, but that could quickly get out of hand if you have many of them. Plus, that uses additional memory that really isn’t needed. Instead, we can use the _remap() method to check if the category exists as a method and run that method, or run the generic index() method for all other categories.

public function _remap($category, $params = array())
{
    if (method_exists($this, $category))
    {
    	return call_user_func_array(array($this, $category), $params);
    }
	$this->index($params);
}

MY_Controller

CodeIgniter allows you to extend any of the core classes by attaching a prefix to the classname. By default, this prefix is MY_, though it can be changed in /application/config/config.php. Using a MY_Controller class is the easiest way to create a custom workspace for your application. Since all of your other controllers will extend from MY_Controller, you can setup some utility methods to use across your site, load common classes, configure pagination, and the list goes on. I find a reason to use it for every project that I’ve worked on, so I make it a point to create one from the beginning so that it’s ready.

class MY_Controller extends CI_Controller {
	public function __construct()
	{
		parent::__construct();
	}
}

Some of the items that are handled well in MY_Controller include:

  • Load and setup the Cache library so that it’s always available. You can use properties and/or methods to make it simple to override on a per-controller basis.
  • Setup a way to turn on/off profiler, but only in development environments.
  • Setup a simple theme system.
  • Attempt to authorize the current user through “Remember Me” functionality, and setup a $this->current_user property.
  • Integrate flash messages into custom JSON-rendering methods for use in AJAX.
  • For API-only sites, I’ve added handy methods for working with Guzzle and handling oAuth authentication.
  • Provide per-controller auto-load functionality for language and model files.
  • Provide the ability to automatically run migrations to the latest version.
  • Provide simple methods to output different content types, like text, javascript, JSON, external files, and HTML, and ensure the content type is set correctly.
  • Provide methods to redirect out of AJAX calls by sending javascript to redirect the browser.

True, some of these items could be handled as libraries, but sometimes it’s much simpler to provide that functionality across all controllers, where it’s going to be used anyway. Once a feature reaches a decent size, though, it’s best to move it to its own library for maintainability. If you are using PHP 5.4+ some of these could also work well in a Trait.

We will step through some of these items in detail later in this chapter. We will even create a simple, but very flexible, template system in Chapter 5: Showing Your Work.

Multiple Base Controllers

While MY_Controller is extremely handy, I often find that it makes sense to include several base controller classes to help provide different types of functionality. These different classes are used as base controllers across the site. The most common base controllers are:

  • AuthenticatedController - Loads the authentication/authorization classes and ensures that the user is logged in.
  • AdminController - Extends the AuthenticatedController, and additionally confirms that the user has administrative privileges. Frequently, this will also setup various items like the correct theme, some pagination defaults so the pagination library can have different front-end and back-end configurations, and more.
  • ThemedController - Sets up the theme engine and provides a number of handy methods for working with the themes. Then, I’ll use a trait I’ve created to handle auth functions so they can be used across multiple types of controllers.
  • APIController - Provides helpful setup for an API, detects request-specific information, and provides a number of utility methods for returning data in success and failure states, and more.
  • CLIController - Useful for making command-line-only controllers, used with CLI tools or cron jobs.

These controller examples are just ideas of common uses. Each project may benefit from very different base controllers, but they demonstrate a couple of common solutions to common problems. They might create a more enjoyable experience when developing a specific type of controller, like an API or an admin area. Additionally, they might add new functions which are available site-wide, or help with integration, like integrating theme engines.

The biggest problem then, is how to conveniently load these additional controllers. Unlike MY_Controller, these classes would not get auto-loaded on each request. There are a few different solutions, each with its pros and cons.

Single File Solution

The first solution is to simply include each of them in MY_Controller.php. It’s fine, it works, and I’ve done it a number of times. I don’t recommend it anymore, though. It’s harder to maintain, and it takes up extra memory by loading classes which are not used within the current request.

class MY_Controller extends CI_Controller { . . . }

class AuthenticatedController extends MY_Controller { . . . }

class AdminController extends AuthenticatedController { . . . }
Multiple File Solution

The next step would be to move each class into its own file. This matches what CodeIgniter’s style guide requires, and the current best practices across the PHP community.

The method to load the additional class which results in the best performance is to simply require() the file at the top of each controller which uses it. This ensures that only the required classes are loaded, and minimizes both the time it takes to instantiate classes and memory usage. However, it does mean that you need to do it on every controller. In a very performance-oriented situation, it might be worth it.

More often, though, I think simply including the different controllers from within the MY_Controller is the best solution, because you don’t have to think about it once you set it up. That means no mistakes, and fewer errors caused by forgetting to include the class.

include APPPATH . 'libraries/AuthenticatedController.php';
include APPPATH . 'libraries/AdminController.php';

class MY_Controller extends CI_Controller { . . . }
Composer-based Solution

If you are integrating Composer into your workflow, then you already have the perfect solution at hand. Composer allows you to place the files anywhere you want to store them and load them only when needed. It solves both of the problems with the other methods discussed above.

We will go into much more detail about using Composer in later chapters, but we will quickly cover the basics of setting this up so you can get up and running with this method. It’s my preferred method and extremely handy when you start using it. This does assume that you already have Composer working on your machine and are familiar with the basics. If you’re not, then head over to Composer’s site and familiarize yourself.

The first thing to do is decide where to put the new controllers. I typically place them in /application/libraries, though it might also make sense to put them under /application/core so that they’ll be next to MY_Controller.

Next, we need to tell Composer how to find them. To do this, we need to edit the composer.json file that CodeIgniter ships with. This file currently just holds some basic information about CodeIgniter itself, and one requirement for use by the test suite. We need to add a new section to the file, called “autoload”.

20 "autoload": {
21 	"psr-4": {
22 		"App\\": ["application/core", "application/libraries", "application/models"]
23 	}	
24 }

This sets up a new namespace called “App” that you can use in your classes. When looking for class with that namespace, Composer will look in /application/core, /application/libraries, and /application/models for the file. You can always customize the directories it looks in to fit your situation.

If you are going to use namespaces that match up to the directories, like App\Libraries or App\Models, you can optimize the autoloader performance by as much as 35% by simply being more specific with the namespace mapping. Instead of using an array of directories, we can split each one into its own listing:

20 "autoload": {
21 	"psr-4": {
22 		"App\\Core\\": "application/core",
23 		"App\\Libraries\\": "application/libraries",
24 		"App\\Models\\": "application/models" 
25 	}	
26 }

The last thing to do before we can successfully extend one of our custom controllers is to actually create the base controllers. So, create a file at /application/libraries/BaseController.php:

1 <?php namespace App;
2 
3 class BaseController extends \CI_Controller {
4 	// Your custom stuff goes here
5 }

This new class extends CodeIgniter’s own CI_Controller to ensure we still use the same get_instance() and don’t run into any conflicts.

Finally, you just need your new controllers to extend this BaseController.

1 <?php
2 
3 class Welcome extends \App\BaseController {
4 	// Your custom stuff goes here
5 }

Any class that doesn’t have a specific namespace set is considered part of the global namespace, so you do need to make sure to prefix the App namespace here with a backwards slash so that it can find it.