Sie sind auf Seite 1von 17

6

CodeIgniter

Hacks

for

the

Masters

Burak Guzel on Dec 29th 2009 with 89 comments

CodeIgniter is a simple and powerful open source web application framework for PHP. Today, well do some core hacks to this framework to change and improve its functionality. In the process, youll 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 CodeIgniters 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:
1. <?php
2. // ... 3.

4. function __autoload($class) { 5. 6.
7. 8. } 9. ?> } if (file_exists(APPPATH."models/".strtolower($class).EXT)) { include_once(APPPATH."models/".strtolower($class).EXT);

<?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:

1. <?php
2. // ... 3.

4. function __autoload($class) { 5. 6. 7. 8.
9. 10. } 11. ?> } 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);

<?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. Lets say you created a model name Post:
1. class Post extends Model { 2. 3. 4. 5. } // ...

class Post extends Model { // ... }

Now you can not have a URL like this:


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

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:
1. // application/controllers/post.php 2.

3. class Post_controller extends Controller {


4. 5. 6. 7. } // ...

// 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
1. class MY_Router extends CI_Router { 2.
3. var $suffix = '_controller';

4. 5.
6. 7.

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

8. 9.
10. 11.

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

12.
13.

function controller_name() {

14.
15. 16. 17. 18. 19. 20. 21. 22. } }

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

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:


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

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

Same file, line 158:


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

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

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


1. $output .= " 2. <div style="color: rgb(153, 83, 0); font-weight: normal; padding: 4px 0pt;">".
$this->CI->router->controller_name()."/".$this->CI->router>fetch_method()."</div> 3. 4. ";

$output .= " ".$this->CI->router->controller_name()."/".$this->CI->router->fetch_method()." ";

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 URLs.

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:
1. $this->form_validation->set_rules('username', 'Username',
2. 'required|alpha_numeric|min_length[6]|unique[User.username]');

$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:
1. $this->form_validation->set_rules('email', 'E-mail', 2.
'required|valid_email|unique[User.email]');

$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
1. class MY_Form_validation extends CI_Form_validation {
2.

3.
4.

function unique($value, $params) {

5.
6. 7.

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

8.

$CI->form_validation->set_message('unique',

9. 10.

'The %s is already being used.');

11.
12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. } }

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; }

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 dont 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:


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

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

The Hack Create a cli.php file at the root of your CodeIgniter folder:
1. if (isset($_SERVER['REMOTE_ADDR'])) { 2.
3. } 4. die('Command Line Only!');

5. set_time_limit(0);
6.

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


8.

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


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:
1. #!/usr/bin/php
#!/usr/bin/php

If you want a specific controller to be command line only, you can block web calls at the controller constructor:
1. class Hello extends Controller { 2. 3. function __construct() { if (isset($_SERVER['REMOTE_ADDR'])) {

4.

5.
6. 7. 8. 9. 10. 11. 12. } // ... } }

die('Command Line Only!'); parent::Controller();

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 1. // system/application/plugins/doctrine_pi.php
2. 3. // load Doctrine library

4. require_once APPPATH.'/plugins/doctrine/lib/Doctrine.php';
5.

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


8. 9. // this will allow Doctrine to load Model classes automatically

10. spl_autoload_register(array('Doctrine', 'autoload'));


11.

12. // we load our database connections into Doctrine_Manager


13. // this loop allows us to use multiple connections later on

14. foreach ($db as $connection_name => $db_values) {


15.

16. 17. 18. 19. 20. 21.


22.

// 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'];

23.
24. } 25.

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

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


28. 29. // telling Doctrine where our models are located

30. Doctrine::loadModels(APPPATH.'/models');
// 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


1. $autoload['plugin'] = array('doctrine');
$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 doesnt 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:
1. $system_folder = dirname(__FILE__) . '../codeigniter/system';
$system_folder = dirname(__FILE__) . '../codeigniter/system';

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


1. $application_folder = dirname(__FILE__) . '../application_site1';
$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.
1. $this->load->library('upload'); 2.

3. $this->upload->set_allowed_types('jpg|jpeg|gif|png|zip');
$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 *.

1. $this->load->library('upload'); 2.

3. $this->upload->set_allowed_types('*');
$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
1. class MY_Upload extends CI_Upload {
2.

3.
4. 5. 6.

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; }

7.
8. 9. 10.

11.
12. 13. 14. 15. 16. 17.

if (in_array("*", $this->allowed_types)) { return TRUE; } $image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');

18.
19.

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; } }

20.
21. 22.

23.
24.

25.
26. 27. 28. 29. 30.

31.

if (is_array($mime))

32.

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

33.
34. 35. 36. 37. 38. 39.

40.
41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. }

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!

Das könnte Ihnen auch gefallen