CodeIgniter for Rapid PHP Application Development — Save 50%
Improve your PHP coding productivity with the free compact open-source MVC CodeIgniter framework!
In this article by Jose Argudo Blanco and David Upton, we will see how CodeIgniter actually works, "under the hood". This article introduces a bit on object-oriented programming and how CodeIgniter makes use of it, including the use of the CI super object, and how we can modify and use. Here, we will also understand why things happen in certain ways, as opposed to just knowing that they do.
Objects confused us, when we started using CodeIgniter. Coming to CodeIgniter through PHP 4, which is a procedural language, and not an object-oriented (OO) language. We duly looked up objects and methods, properties and inheritance, and encapsulation, but our early attempts to write CI code were plagued by the error message "Call to a member function on a non-object". We saw it so often that we were thinking of having it printed on a T-shirt.
To save the world from a lot of boring T-shirts, this article covers the way in which CI uses objects, and the different ways you can write and use your own objects. Incidentally, we've used "variables/properties", and "methods/functions" interchangeably, as CI and PHP often do. You write "functions" in your controllers, for instance, when an OO purist would call them "methods". You define class "variables" when the purist would call them "properties".
We assume that you have basic knowledge of OOP. You may have learned it as an afterthought to "normal" PHP 4. PHP 4 is not an OO language, though some OO functionality has been stacked on to it. PHP 5 is much better, with an underlying engine that was written from the ground up with OO in mind.
You can do most of the basics in PHP 4, and CI manages to do everything it needs internally in either language. The key thing to remember—when an OO program is running, there is always one current object (but only one). Objects may call each other or hand over control to each other, in which case the current object changes, but only one of them can be current at any time. The current object defines the scope, in other words, the variables (properties) and methods (functions) that are available to the program at that moment. So it's important to know and control the current object.
PHP, being a mixture of functional and OO programming, also offers the possibility where no object is current. You can start off with a functional program, call an object, let it take charge for a while, and then return control to the program. Luckily, CI takes care of this for you.
The CI super-object
CI works by building one super-object—it runs the entire program as one big object, in order to eliminate scoping issues. When you start CI, a complex chain of events occurs. If you set your CI installation to create a log (in /codeigniter/application/config/config.php set $config['log_threshold'] = 4; value. This will generate a log file in /www/CI_system/logs/), you'll see something like this:
1 DEBUG - 2006-10-03 08:56:39 --> Config Class Initialized
2 DEBUG - 2006-10-03 08:56:39 --> No URI present. Default controller
3 DEBUG - 2006-10-03 08:56:39 --> Router Class Initialized
4 DEBUG - 2006-10-03 08:56:39 --> Output Class Initialized
5 DEBUG - 2006-10-03 08:56:39 --> Input Class Initialized
6 DEBUG - 2006-10-03 08:56:39 --> Global POST and COOKIE data
7 DEBUG - 2006-10-03 08:56:39 --> URI Class Initialized
8 DEBUG - 2006-10-03 08:56:39 --> Language Class Initialized
9 DEBUG - 2006-10-03 08:56:39 --> Loader Class Initialized
10 DEBUG - 2006-10-03 08:56:39 --> Controller Class Initialized
11 DEBUG - 2006-10-03 08:56:39 --> Helpers loaded: security
12 DEBUG - 2006-10-03 08:56:40 --> Scripts loaded: errors
13 DEBUG - 2006-10-03 08:56:40 --> Scripts loaded: boilerplate
14 DEBUG - 2006-10-03 08:56:40 --> Helpers loaded: url
15 DEBUG - 2006-10-03 08:56:40 --> Database Driver Class Initialized
16 DEBUG - 2006-10-03 08:56:40 --> Model Class Initialized
At start up, that is, each time a page request is received over the Internet—CI goes through the same procedure. You can trace the log through the CI files:
- The index.php file receives a page request. The URL may indicate which controller is required, if not, CI has a default controller (line 2). The index.php file makes some basic checks and calls the codeigniter.php file (codeignitercodeigniter.php).
- The codeigniter.php file instantiates the Config, Router, Input, URL, and other such, classes (see lines 1, and 3 to 9). These are called the base classes—you rarely interact directly with them, but they underlie almost everything CI does.
* Instantiate the base classes
$CFG =& load_class('Config');
$URI =& load_class('URI');
$RTR =& load_class('Router');
$OUT =& load_class('Output');
- The file codeigniter.php tests to see the version of PHP it is running on, and calls Base4 or Base5 (/codeigniter/Base4.php or codeigniter/Base5.php).
if (floor(phpversion()) < 5)
- The above snippet creates an object—one which ensures that a class has only one instance. Each has a public &get_instance() function. Note the &—this is assignment by reference. So, if you assign using &get_instance() method, it assigns to the single running instance of the class. In other words, it points to the same pigeonhole. So, instead of setting up lot of new objects, you start building one super-object, which contains everything related to the framework.
- A security check,
* Security check
* None of the functions in the app controller or the
* loader class can be called via the URI, nor can
* controller functions that begin with an underscore
$class = $RTR->fetch_class();
$method = $RTR->fetch_method();
if ( !class_exists($class)
OR $method == 'controller'
OR strncmp($method, '_', 1) == 0
OR in_array(strtolower($method), array_map('strtolower',
- The file, codeigniter.php instantiates the controller that was requested, or a default controller (line 10). The new class is called $CI.
$CI = new $class();
- The function specified in the URL (or a default) is then called and life, as we know it, starts to wake up and happen. Depending on what you wrote in your controller, CI will initialize the classes you need, and "include" functional scripts you asked for. So, in the log, the model class is initialized (line 16). The boilerplate script, which is also shown in the log (line 13), is the one we wrote to contain standard chunks of text. It's a .php file, saved in the folder called scripts. It's not a class—just a set of functions. If you were writing pure PHP you might use include or require to bring it into the namespace—CI needs to use its own load function to bring it into the super-object.
The concept of namespace or scope is crucial here. When you declare a variable, array, object, and so on, PHP holds the variable name in its memory and assigns a further block of memory to hold its contents. However, problems might arise if you define two variables with the same name. (In a complex site, this is easily done.) For this reason, PHP has several set of rules. Some of them are as listed:
- Each function has its own namespace or scope, and variables defined within a function are usually local to it. Outside the function, they are meaningless.
- You can declare global variables, which are held in a special global namespace and are available throughout the program.
- Objects have their own namespaces—variables exist inside the object as long as the object exists, and can only be referenced by using the object.
So, $variable, global $variable, and $this->variable are three different things.
Remember, $variable and global $variable can't be used in the same scope. So, inside a function you will have to decide if you want to use $variable or global $variable.
Particularly before OO, this could lead to all sort of confusions—you may have too many variables in your namespace (so that conflicting names overwrite each other). You may also find that some variables are just not accessible from whatever scope you happen to be.
Copying by reference
You may have noticed the function &get_instance() in the previous section. This is to ensure that, as the variables change, the variables of the original class also change. As assignment by reference can be confusing, so here's a short explanation. We're all familiar with simple copying in PHP:
$one = 1;
$two = $one;
The previous snippet produces 1, because $two is a copy of $one. However, suppose you reassign $one:
$one = 1;
$two = $one;
$one = 5;
This code still produces $two = 1, because changes made to $one after assigning $two have not been reflected in $two. This was a one-off assignment of the value that happened to be in variable
In effect, PHP creates two pigeonholes—called $one and $two. A separate value lives in each. You may, on any occasion, make the values equal, but after that each does its own work. PHP also allows copying by reference. If you add just a simple & to line 2 of the snippet as shown:
$one = 1;
$two =& $one;
$one = 5;
The code now echoes 5, the change we made to $one is reflected in $two. Changing the = to =& in the second line means that the assignment is "by reference". It looks as if there was only one pigeonhole, which has two names ($one<.i> and $two). Whatever happens to the contents of the pigeonhole is reflected in both $one and $two, as if they were just different names for the same variables.
The principle works for objects as well as simple string variables. You can copy or clone an object using the = operator in PHP 4. Or you can clone keyword in PHP, in which case you make a simple one-off new copy, which then leads an independent life. You can also assign one to the other by reference, so the two objects point to each other. Any changes made to one will also happen to the other. Again, think of them as two different names for the same thing.
eBook Price: $20.99
Book Price: $34.99
Adding your own code to the CI super-object
You contribute to the process of building the super-object, as you write your own code. Suppose you have written a model called status, which contains two class variables of its own, $one and $two. It also consists of a constructor that assigns the values to $one and $two respectively. Let's examine what happens when you load this model.
The instance class includes a variable load, which is a copy (by reference) of the object ci_load. So, the code you write in your controller is:
In other words, take the class variable load of the current CI super-class (this) and use its method model. This actually references the model function in the loader class (/CI_system/libraries/loader.php) and that says:
function model($model, $name = '')
if ($model == '')
$obj =& get_instance();
The $name variable in this code is there in case you want to load your model under an alias.
As you can see, the model is loaded by reference into the instance class. As get_instance() returns an instance to the CI super-object, we're always using the same CI object, instead of creating copies of it.
Note that if you use get_instance() inside class constructors and you are using PHP 4, you may have problems, as PHP 4 can't reference the CI super-object until the class is completely instantiated.
If you run the controller again the CI super-object will include an object called $status that includes the class variables you defined in your original status model, assigned to the values we set.
So we are gradually building one big CI super-object, which allows you to use any of its methods and variables without worrying much about where they come from and what namespace they are in.
To use the methods of (say) a model, you must first load the model in your controller:
This makes the model available in our controller, and we can use it through the pseudo-variable $this->. We then call a function of that model_name class from the controller, like this:
And off you go.
Problems with the CI super-object
PHP 4 handles objects less elegantly than PHP 5. You may have problems trying to call get_instance() inside class constructors, as PHP 4 will have trouble referencing the CI super-object before the class is fully instantiated. Also you can find yourself trying to work with an object that isn't available.
Let's look at these problems in turn. You remember the T-shirt we mentioned earlier—"Call to a member function on a non-object". This annoying error message often means that you tried to use a function from a class (say model_name class) that you wrote, but forgot to load. In other words, you wrote:
But forgot to precede it by:
Or some variation of this—for instance, you loaded the model inside one function of a class; this loads the model, but only inside that function. If you try to use its methods from inside another function, albeit in the same class, you get the error. It's usually best to load models, and so on, from the class constructor function so that they are available to all the other functions in the class.
The problem can also be more subtle. If you write your own classes you may wish to use them to access the database, or look up something in your config files, in other words, give them access to something that is a part of the CI super-object. Unless, your new class is a controller, a model, or a view, it doesn't get built into the CI super-object. So, you can't write things inside your new class like this:
This just won't work, because to your new class,$this->means itself, not the CI super-object. Instead, you have to build your new class into the super-class by calling the instance class (sounds familiar?), using another variable name (usually $obj).
$obj =& get_instance();
Now you can call the CI super-class as:
This time it works. However, as you write your new class, remember that it still has its own identity. Let's use a short outline example to make this clear.
You want to write a library class that prepares a URL based on the location of the server that requests the page. So, you write some code to look up the geographic location of the IP address that is calling your page (using a library like the NetGeo class available at http://www.phpclasses.org/browse/package/514.html). Then, using a switch function, you select one of the several alternative functions, and you serve up an English page to US or British requests, a German page to German or Austrian requests, and so on. Now, the full URL to your country-specific page will be made up of two parts—the base URL of your site (www.mysite.com/index.php/), plus the URL of the individual page (mypage/germanversion).
You need to get the base URL of the site from CI's config file. The second half of the URL is being generated by a switch statement in the constructor of your new class—if this client is in Germany, serve up the German page function, and so on. As this is being done in the constructor calls, you need to put the result into a class variable, so it can be used in other functions within the same class. This means:
- The first half of your URL comes from the CI config file, which can only be referenced through the super-object, to which you have linked using $obj =& get_instance(). In other words, you call it using $obj->config->item('base_url');.
- But the second half of your URL is generated inside the constructor of your new class and assigned to a class variable, $base. It has nothing to do with the CI super-object; it belongs to your new class, and is referenced as $this->base.
This can lead to using both $this-> and $obj-> references in the same line, for example:
$obj =& get_instance();
// geolocation code here, returning a value through a switch
//this value is assigned to $local_url
$this->base = $obj->config->item('base_url');
$this->base = $local_url;
Getting these confused is another fruitful source of, "Call to a member function on a non-object". In our example, you'd get this error message if you tried to call either $obj->base, or $this->config->item().
Turning to the remaining problem, you can't call methods of one controller from inside another. Why would you want to do this? Well, it depends, in one application, we wrote a series of self-test functions inside each controller. If we called $this- >selftest() inside the controller, it did various useful tests. But it seems against the principle of programming—the virtue of laziness—to have to repeatedly call the self-test method in each controller separately. We tried to write a function, in one controller that would go through all the controllers, call the self-test method in each, and amalgamate all the results, while we stared out of the window. After all that it would give you a comprehensive report in exchange for one click. Alas, no. It can't be done that way, we have to think of another way to do it.
As a general rule, if you have a piece of code that maybe needed by more than one controller, put it in a helper, plugin, or library so you can load and use it wherever you need. Also we have the option of extending CI base libraries, so you can add functions you need to these libraries. It is very easy to achieve this—you have to create a file with the same name as that of the class you want to extend, in the libraries folder, with MY_ preceding the filename. So, to extend a base class we would go to application/libraries/MY_Baseclass.php.
Inside the file we would create the class this way:
class MY_Baseclass extends CI_Baseclass
In the constructor, however, we will make reference to the base class:
class MY_Baseclass extends CI_Baseclass
This way we can easily add functions that we need to CI base classes. You may think that this is very useful but doesn't help us in the problem of having functions available throughout our application. No problem, we can achieve this by extending the base controller class—the class from which all of our controllers are extended. This is quite easy, but has a few steps; these steps are fully covered here: http://codeigniter.com/wiki/MY_Controller_-_how_to_extend_the_CI_Controller/
If you want to go deeper into extending CI it is recommended to take a look at these links, they will be of great help:
We've looked at the way CI builds up one super-object to make sure that all the methods and variables you need are automatically available to you without you having to manage them and worry about their scope.
CI makes extensive use of assignment by reference, instantiating one class after another and linking them all together so that you can access them through the super-class. We've also looked at how you can write your own classes and still have access to the CI framework.
Also, we looked at a few problems that can arise, particularly if you're not used to OO programs, and seen a few solutions to them. Lastly we looked at how we can extend core classes and even the controller class.
eBook Price: $20.99
Book Price: $34.99
About the Author :
David Upton is a director of a specialized management consultancy company, based in London but working around the world. His clients include some of the world’s largest companies. He is increasingly interested in web-enabling his work, and seeking to turn ideas into robust professional applications by the simplest and easiest route. He has so far written applications for two major companies in the UK. His other interests include simulation, on which he writes a weblog which takes up far too much of his time, and thinking.