Step 9 – No more MY_Controller monopoly. How you can create more than one base controller

(created at: December 8, last update: May 19, 2015)

Creating a controller is simple: We just extend CI_Controller. But there are moments when we are faced with dealing with repetitive tasks like a method that is the same on two different controllers.

Please allow me to start with a poll (which is related to the content of the tutorial), because is important to me to find out what methods are mostly used.

[poll id=”1″]

For these situations we can extend the base controllers. We can do this by creating a MY_Controller.php inside application/core and every controller that we create afterwards will extend MY_Controller:

<?php

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

But what if we want to extend different controllers depending on the situation the user is in: like, for example, do A_Controller for an unregistered user and Another_MY_Controller for a registered user.

On this application we will create an administration controller and a public controller.

So let’s get to work

We will create the two controllers inside application/core directory. They will both extend MY_Controller:

Admin_Controller:

<?php

class Admin_Controller extends MY_Controller
{
  function __construct()
  {
    parent::__construct();
    echo 'This is from admin controller';
  }
}

…and the Public_Controller:

<?php
class Public_Controller extends MY_Controller
{
  function __construct()
  {
    parent::__construct();
    echo 'This is from public controller';
  }
}

As you can see, we are only echoing something just to see if it works.

Now, to see if all works, just go to application/controllers/Welcome.php and replace: class Welcome extends CI_Controller { with: class Welcome extends Admin_Controller {

After this, visit your site http://localhost/. If all went “well” you should see something like:

Fatal error: Class ‘Admin_Controller’ not found in C:\xampp\htdocs\application\controllers\Welcome.php on line 3

OK. We are ready to work from here.

There are more ways to do this:

0. Jim Parry’s way (or CodeIgniter site’s way): Just put your base controllers in there

Well… I don’t know if this is Jim Parry’s way, but that is what I can see from the Github repo of the official CodeIgniter site.

Is actually a down to earth approach of the so called “problem”. If the CodeIgniter framework always asks for MY_Controller.php if it finds it inside the application/core directory, then why not just write all the controllers inside the MY_Controller.php file?

Is as simple as that. People may cry foul, because the classes are not defined inside the confinement of their own files, but if you already know that MY_Controller.php is requested by the CI, then why not just respect that. Everyone that knows how to work with CodeIgniter is usually looking for the MY_Controller to see what is in there.

So you simply create a MY_Controller.php and put the controllers in there:

<?php

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

class Admin_Controller extends MY_Controller
{
  function __construct()
  {
    parent::__construct();
    echo 'This is from admin controller';
  }
}

class Public_Controller extends MY_Controller
{
  function __construct()
  {
    parent::__construct();
    echo 'This is from public controller';
  }
}

Now you can extend any of the controllers found in MY_Controller.php.

1. Simply include the controller before requesting it

That would be the “Duh!!!” moment, but it should be mentioned nonetheless. You simply do an include_once() or require_once() just before extending the Admin_Controller.

So, if we are inside Welcome.php, we write:

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

include_once(APPPATH.'core/Admin_Controller.php');

class Welcome extends Admin_Controller {

  public function index()
  {
    $this->load->view('welcome_message');
  }
}

Now, if you visit http://localhost/, you should see the message coming from your Admin_Controller.php. Same goes for extending Public_Controller.

2. Using an autoload function (the fast and dirty way)

By using an autoload function we can simply tell PHP that if a class that hasn’t been declared is required to look for a file that has that class’ name inside a certain directory.

We can insert the function inside the application/config/development/config.php. So, at the end of the file we can write the following lines:

/*
* Load My own MY_Controllers
*/
function my_own_controllers($class) {
  if (strpos($class, 'CI_') !== 0)
  {
    if (is_readable(APPPATH . 'core/' . $class . '.php'))
    {
      require_once(APPPATH . 'core/' . $class . '.php');
    }
  }
}

spl_autoload_register('my_own_controllers');

As you can see, we also made sure that the file required is present.

Now, if you visit http://localhost/, you should see the message coming from your Admin_Controller.php. Same goes for extending Public_Controller.

3. Using an autoload function with hooks (the slow and right way)

I would like to start by thanking Ivan Tcholakov by pointing out in a comment that, being a bit hacky, autoloading inside config.php wouldn’t be advisable. He told me that a better way to do it would be by using hooks (http://www.codeigniter.com/userguide3/general/hooks.html).

CodeIgniter’s Hooks allows us to modify the inner workings of the framework without hacking the core files. Of course, using the hooks instead of putting the autoloader in the config.php, is not really bad, but is a good practice to keep everything where it should be. Configuration data in config.php and flow of execution in other places. The Hooks allow us to establish the flow of our app.

So, let’s get to it. First of all we must make sure that we have Hooks enabled, so go to your config.php (in our case, we can find it in application/config/development directory), and make sure of that:

$config['enable_hooks'] = TRUE;

Now, once we’ve enabled hooks, let’s create a file that will keep our autoloader. Inside application/hooks directory, create a file named App_controllers.php. In it we will write:

<?php
function load_app_controllers()
{
  spl_autoload_register('my_own_controllers');
}
function my_own_controllers($class)
{
  if (strpos($class, 'CI_') !== 0)
  {
    if (is_readable(APPPATH . 'core/' . $class . '.php'))
    {
      require_once(APPPATH . 'core/' . $class . '.php');
    }
  }
}

After this, all we have to do is call the load_app_controllers() with a hook. For this, we must open application/config/hooks.php and add the following lines:

$hook['pre_system'][] = array(
  'class' => '',
  'function' => 'load_app_controllers',
  'filename' => 'App_controllers.php',
  'filepath' => 'hooks'
);

Let’s explain here. As you can see, we will do the autoload of the controllers just before the system loads. We will not load a class, as we have only a function, load_app_controllers(). That function will be defined in App_controllers.php which can be found inside application’s hooks directory.

Now, if you visit http://localhost/, you should see the message coming from your Admin_Controller.php. Same goes with Public_Controller.

4. Using Composer’s autoloader with namespacing

This one gave me a lot of trouble, not knowing where the problem was when I used the ‘composer_autoload’ parameter set to true in the config.php file. Later, I found out that the default composer autoloader is loaded too early in the application’s process, so that is why it didn’t work.

As a consequence I won’t use the composer_autoload parameter of the config.php file.

First of all, we should move Admin_Controller.php and Public_Controller.php inside a directory specific to our own application. I will call mine avenirer. This way, we can later use the same base controllers in other applications by just copying the directory in question.

So, now the structure would be:

application
-avenirer
- -BaseControllers
- - -Admin_Controller.php
- - -Public_Controller.php

After we’ve done this, we make sure we have a composer.json file inside our application directory. If you’ve followed all the steps I’ve written (specifically step 5), you should already have a composer.json file in application directory.

The file should now look like this:

{
  "description" : "My great CodeIgniter application",
  "name" : "Avenirer/myApp",
  "require": {
    "php": ">=5.3"
  },
  "autoload": {
    "psr-4": {
      "Avenirer\\":"avenirer"
    }
  }
}

Now, some explaining. To use namespacing, you have to set the php version to be bigger or equal to 5.3 (so make sure you have a PHP version bigger or equal to 5.3). Also, we’ve namespaced Avenirer.

After you’ve saved composer.json you would have to either do a “composer install” if you’ve just created the composer.json (and don’t have a vendor directory inside application directory) or a “composer update” if you’ve only edited an existing composer.json file. I sure hope you know how to use Composer PHP. If not, is time for you to learn.

If you do either of those to commands, you should now have inside application/vendor/composer/autoload_psr4.php something like:

<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
  'Avenirer\\' => array($baseDir . '/avenirer'),
);

That means that the autoload file was created successfully.

As we will be using namespaces we will also have to set them inside the Admin_Controller.php and Public_Controller.php:

<?php namespace Avenirer\BaseControllers;

class Admin_Controller extends \MY_Controller
{
  function __construct()
  {
    parent::__construct();
    echo 'This is from admin controller';
  }
}
<?php namespace Avenirer\BaseControllers;

class Public_Controller extends \MY_Controller
{
function __construct()
{
parent::__construct();
echo 'This is from from public controller';
}
}

Now we only need to make sure the Composer’s autoloader is… well… loaded… by our CodeIgniter application. As setting $config[‘composer_autoload’] = TRUE;  inside the config.php file didn’t work (AND I DON’T KNOW WHY IT DIDN’T WORK), we will have to require the autoloader:

$config['composer_autoload'] = TRUE;
require_once APPPATH.'vendor/autoload.php';

After this, we put the controllers to the trial. Instead of extending Admin_Controller or Public_Controller, we will now extend a Avenirer\BaseControllers\Admin_Controller or Avenirer/BaseControllers\Public_Controller:

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

class Welcome extends Avenirer\BaseControllers\Admin_Controller {

  public function index()
  {
    echo 'hello from your controller';
  }
}

Now, if all went well, you should see a hello from the Admin_Controller and another hello from your controller when you visit http://localhost.

If you know another way to load the base controllers, please post a comment below.

More reading:

https://philsturgeon.uk/blog/2010/02/CodeIgniter-Base-Classes-Keeping-it-DRY/

20 comments

  1. Placing the autoloader within config.php works of course, it suits for testing, but it seems as a durty hack. For a final product I would place the autoloader within a ‘pre_system’ hook. And after that the autoloader could be gradually extended/upgraded without the need to touch config.php file which is unique for every site.

  2. Hello Adrian,

    Thanks for good pointing on ‘how things work’.
    However, when used in 2.x.x version, I was able to use Way No. 2 with autoload function in config.php file, although it was Phill Sturgeon simplified version of snippet you are using here.
    Now, I am trying to use it in 3.0-dev and getting error of not found MY_Session_database_driver.php file in application/core folder. I made simple overriding with that class extending CI_Session_database_driver class and parent::__construct() line in __construct method, but I am also curious is this normal behaviour now, like would there be more request from application to do similar with core classes included?

    Goran

    1. Hello, considering the name MY_Session… it seems that that is application specific (and not framework related). Also, the session library is not included by default (as I remember), so it shouldn’t be required for CodeIgniter to work. I encourage you to re-download a fresh copy of CI 3.0, because there has been a lot of changes regarding the session library (which is not a driver anymore but is a library again…). Also, I encourage you to use Jim Parry’s way. I’ve used it once and never looked back 🙂

      1. Thank you for advice. This is today’s copy that has no error messages if not used (i.e.) Some_Controller.php in core folder and other code depending on it.
        I thought of Jim’s way or even some other like including file just before class in file itself like your way No. 1.
        Thank you again.

  3. I tried option 3 (slow right way) but I am getting an errors related to class loading:
    The file/controller MY_Ion_auth.php does not exist in /Applications/MAMP/application/core directory

    When I traced the section Apps_controllers:
    function my_own_controllers($class)
    {
    if (strpos($class, ‘CI_’) !== 0)
    —-

    It doesn’t complain about other CI classes–just Ion_Auth

    1. Thank you for your interest. Actually, this is for version 3 and up. Unless you find that is buggy, in which case I would more than happily correct it if you tell me about it in a comment.

  4. In method 1. Simply include the controller before requesting it, it is giving me error ‘ Fatal error: Cannot redeclare class Admin_Controller in /var/www/CodeIgniter-develop/application/core/Admin_Controller.php on line 14′.

    But changing include_once(APPPATH.’core/Admin_Controller.php’);
    to
    include_once(APPPATH.’core/MY_Controller.php’);
    worked ! I can’t understand why ? Can you elaborate something about that ?
    Thanks.
    Prakhar

    1. Did you put an Admin_Controller class inside the MY_Controller too? If you did that, then there is your problem. MY_Controller is automatically loaded by the framework, so when you loaded the Admin_Controller.php, the framework already loaded the MY_Controller which, I guess, already has an Admin_Controller defined in there…

  5. Another option that is sorta a hybrid of #0 and #1:
    Create separate controller files and then include_once after the MY_Controller class in MY_Controller.php

  6. I’m getting a Fatal error: “Class ‘Zaman\BaseControllers\Admin_Controller’ not found in D:\xampp\htdocs\ci303\application\controllers\Welcome.php on line 7” when I’m Using Composer’s autoloader with namespacing. I have followed all your provided steps but could not understand what’s the problem. I appreciate your help.

    1. What I see there is that there is a “Zaman” directory that is not in the physical location on your application… Or am I wrong?

  7. What about adding

    include_once(‘Admin_Controller.php’);
    include_once(‘Public_Controller.php’);

    to MY_Controller.php? This way we can keep Admin and Public controlers in their own files..

Leave a Reply

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.