Advertisement

6 CodeIgniter Hacks for the Masters

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

CodeIgniter is a simple and powerful open source web application framework for PHP. Today, we'll do some core "hacks" to this framework to change and improve its functionality. In the process, you'll gain a better understanding of the intricacies of CodeIgniter.

Disclaimer

  1. It is not recommended to apply these hacks to an existing project. Since they change some of CodeIgniter's core functionality, it can break the existing code.
  2. As of this writing, CodeIgniter 1.7.2 is the latest stable release. These hacks are not guaranteed to work for future (or past) releases.
  3. Even though CodeIgniter is designed to be PHP 4 compatible, some of these hacks are not. So you will need a server with PHP 5 installed.
  4. When you make any changes to the files inside the system folder, you should document it somewhere for future reference. Next time you upgrade CodeIgniter (even though they do not release updates very often), you may need to reapply those changes.

1. Autoloading Models PHP 5 Style

The Goal

On the left side, you see the regular way of loading a model in CodeIgniter, from within a Controller. After this hack, we will be able to create objects directly. The code is cleaner, and your IDE will be able to recognize the object types. This enables IDE features such as auto-complete, or previewing documentation.

There are two more side effects of this hack. First, you are no longer required to extend the Model class:

And you no longer have to add a require_once call before you do model class inheritance.

The Hack

All we need to do is add a PHP 5 style autoloader function.

Add this code to the bottom of system/application/config/config.php:

<?php
// ...

function __autoload($class) {
	if (file_exists(APPPATH."models/".strtolower($class).EXT)) {
		include_once(APPPATH."models/".strtolower($class).EXT);
	}
}
?>

If you are interested in applying this hack for controllers too, you can use this code instead:

<?php
// ...

function __autoload($class) {
	if (file_exists(APPPATH."models/".strtolower($class).EXT)) {
		include_once(APPPATH."models/".strtolower($class).EXT);
	} else if (file_exists(APPPATH."controllers/".strtolower($class).EXT)) {
		include_once(APPPATH."controllers/".strtolower($class).EXT);
	}
}
?>

Any time you try to use a class that is not defined, this __autoload function is called first. It takes care of loading the class file.

2. Prevent Model-Controller Name Collision

The Goal

Normally, you can not have the same class name for a Model and a Controller. Let's say you created a model name Post:

class Post extends Model {

	// ...

}

Now you can not have a URL like this:

http://www.mysite.com/post/display/13

The reason is because that would require you to also have a Controller class named 'Post.' Creating such a class would result in a fatal PHP error.

But with this hack, it will become possible. And the Controller for that URL will look like this:

// application/controllers/post.php

class Post_controller extends Controller {

	// ...

}

Note the '_controller' suffix.

The Hack

To get around this issue, normally most people add the '_model' suffix to the Model class names (eg. Post_model). Model objects are created and referenced all over the application, so it might seem a bit silly to have all of these names with '_model' floating around. I think it is better to add a suffix to the Controllers instead, since they are almost never referenced by their class names in your code.

First we need to extend the Router class. Create this file: "application/libraries/MY_Router.php"

class MY_Router extends CI_Router {
	var $suffix = '_controller';

	function MY_Router() {
		parent::CI_Router();
	}

	function set_class($class) {
		$this->class = $class . $this->suffix;
	}

	function controller_name() {

		if (strstr($this->class, $this->suffix)) {
			return str_replace($this->suffix, '', $this->class);
		}
		else {
			return $this->class;
		}

	}
}

Now edit "system/codeigniter/CodeIgniter.php" line 153:

if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT))

Same file, line 158:

include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT);

Next, edit: "system/libraries/Profiler.php", line 323:

$output .= "<div style='color:#995300;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->router->controller_name()."/".$this->CI->router->fetch_method()."</div>";

That is all. Keep in mind that with this hack you are required to put the '_controller' suffix on all of your controller class names. But not in the file names or the URL's.

3. Form Validation for Unique Values

The Goal

CodeIgniter has a nice Form_validation class. It comes with several validation rules:

These are useful, but there is an important one missing from this list: to check for unique values. For example, most user registration forms need to check that the username is not already taken, or the e-mail address is not already in the system.

With this hack, you will be able add this validation rule to your form submission handler very easily:

$this->form_validation->set_rules('username', 'Username',
		'required|alpha_numeric|min_length[6]|unique[User.username]');

Note the last part that says "unique[User.username]." This new validation rule is just called "unique," and takes a parameter inside the square brackets, which is "tablename.fieldname". So it will check the "username" column of the "User" table to make sure the submitted value does not already exist.

Similarly, you can check for duplicate e-mails:

$this->form_validation->set_rules('email', 'E-mail',
		'required|valid_email|unique[User.email]');

And your application can respond with proper error messages:

The Hack

This might be considered more of an extension than a hack. Nevertheless, we are going to take a core CodeIgniter library and improve it.

Create: "application/libraries/MY_Form_validation.php"

class MY_Form_validation extends CI_Form_validation {

	function unique($value, $params) {

		$CI =& get_instance();
		$CI->load->database();

		$CI->form_validation->set_message('unique',
			'The %s is already being used.');

		list($table, $field) = explode(".", $params, 2);

		$query = $CI->db->select($field)->from($table)
			->where($field, $value)->limit(1)->get();

		if ($query->row()) {
			return false;
		} else {
			return true;
		}

	}
}

Now you can use the unique validation rule.

4. Running CodeIgniter from the Command Line

The Goal

Just like the title says, our goal is to be able to run CodeIgniter applications from the command line. This is necessary for building cron jobs, or running more intensive operations so you don't have the resource limitations of a web script, such as maximum execution time.

This is what it looks like on my local Windows machine:

The above code would be like calling this URL:

http://www.mysite.com/hello/world/foo

The Hack

Create a "cli.php" file at the root of your CodeIgniter folder:

if (isset($_SERVER['REMOTE_ADDR'])) {
	die('Command Line Only!');
}

set_time_limit(0);

$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = $argv[1];

require dirname(__FILE__) . '/index.php';

If you are on a Linux environment and want to make this script self executable, you can add this as the first line in cli.php:

#!/usr/bin/php

If you want a specific controller to be command line only, you can block web calls at the controller constructor:

class Hello extends Controller {

	function __construct() {
		if (isset($_SERVER['REMOTE_ADDR'])) {
			die('Command Line Only!');
		}
		parent::Controller();
	}

	// ...

}

5. Adding Doctrine ORM to CodeIgniter

The Goal

Doctrine is a popular Object Relational Mapper for PHP. By adding it to CodeIgniter, you can have a very powerful Model layer in your framework.

The Hack

Just installing Doctrine is not very "hacky" per se, as we can just add it as a plug-in. However, once added, your Model classes will need to extend the Doctrine base classes, instead of the CodeIgniter Model class. This will completely change the way the Model layer works in the framework. The objects you create will have database persistence and also will able to have database relationships with other objects.

Follow these steps:

  1. Create folder: application/plugins
  2. Create folder: application/plugins/doctrine
  3. Download Doctrine (1.2 as of this article)
  4. Copy the "lib" folder from Doctrine to: "application/plugins/doctrine"
  5. Create "application/plugins/doctrine_pi.php"
// system/application/plugins/doctrine_pi.php

// load Doctrine library
require_once APPPATH.'/plugins/doctrine/lib/Doctrine.php';

// load database configuration from CodeIgniter
require_once APPPATH.'/config/database.php';

// this will allow Doctrine to load Model classes automatically
spl_autoload_register(array('Doctrine', 'autoload'));

// we load our database connections into Doctrine_Manager
// this loop allows us to use multiple connections later on
foreach ($db as $connection_name => $db_values) {

	// first we must convert to dsn format
	$dsn = $db[$connection_name]['dbdriver'] .
		'://' . $db[$connection_name]['username'] .
		':' . $db[$connection_name]['password'].
		'@' . $db[$connection_name]['hostname'] .
		'/' . $db[$connection_name]['database'];

	Doctrine_Manager::connection($dsn,$connection_name);
}

// CodeIgniter's Model class needs to be loaded
require_once BASEPATH.'/libraries/Model.php';

// telling Doctrine where our models are located
Doctrine::loadModels(APPPATH.'/models');

Next, edit "application/config/autoload.php" to autoload this Doctrine plugin

$autoload['plugin'] = array('doctrine');

Also make sure you have your database configuration in "application/config/database.php".

That is all. Now you can create Doctrine Models within your CodeIgniter application. Read my tutorials on this subject for more information.

6. Running Multiple Sites

The Goal

This hack will make it possible for you to run multiple sites from a single install of CodeIgniter. Each website will have its own application folder, but they will all share the same system folder.

The Hack

Install CodeIgniter anywhere on the server. It doesn't need to be under a website folder. Then take the application folder out of the system folder. And make additional copies of it, as seen in the image above, for every website you want to run. You can place those application folders anywhere, like under each separate website folders.

Now copy the index.php file to the root of each website folder, and edit it as follows:

At line 26, put the full path to the system folder:

	$system_folder = dirname(__FILE__) . '../codeigniter/system';

At line 43, put the full path to the application folder:

	$application_folder = dirname(__FILE__) . '../application_site1';

Now you can have independent websites using separate application folders, but sharing the same system folder.

There is a similar implementation in the CodeIgniter User Guide you can read also.

7. Allowing All File Types for Uploads

The Goal

When using the Upload library in CodeIgniter, you must specify which file types are allowed.

$this->load->library('upload');

$this->upload->set_allowed_types('jpg|jpeg|gif|png|zip');

If you do not specify any file types, you will receive an error message from CodeIgniter: "You have not specified any allowed file types."

So, by default, there is no way to allow all file types to be uploaded. We need to do small hack to get around this limitation. After that we will be able to allow all file types by setting it to '*'.

$this->load->library('upload');

$this->upload->set_allowed_types('*');

The Hack

For this hack we are going to modify the Upload class behavior.

Create file: application/libraries/My_Upload.php

class MY_Upload extends CI_Upload {

	function is_allowed_filetype() {

		if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types))
		{
			$this->set_error('upload_no_file_types');
			return FALSE;
		}

		if (in_array("*", $this->allowed_types))
		{
			return TRUE;
		}

		$image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');

		foreach ($this->allowed_types as $val)
		{
			$mime = $this->mimes_types(strtolower($val));

			// Images get some additional checks
			if (in_array($val, $image_types))
			{
				if (getimagesize($this->file_temp) === FALSE)
				{
					return FALSE;
				}
			}

			if (is_array($mime))
			{
				if (in_array($this->file_type, $mime, TRUE))
				{
					return TRUE;
				}
			}
			else
			{
				if ($mime == $this->file_type)
				{
					return TRUE;
				}
			}
		}

		return FALSE;

	}

}

Conclusion

I hope some of these are useful to you. If not, they are still interesting to know and can help you learn more about the internal workings of a framework and some of the core PHP language features.

If you know any other cool hacks or modifications, let us know in the comments. Thank you!

Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

CodeCanyon

Advertisement