CodeIgniter and Objects

Exclusive offer: get 50% off this eBook here
CodeIgniter for Rapid PHP Application Development

CodeIgniter for Rapid PHP Application Development — Save 50%

Improve your PHP coding productivity with the free compact open-source MVC CodeIgniter framework!

$20.99    $10.50
by David Upton | December 2007 | MySQL Open Source PHP

Objects confused me when I started to use CodeIgniter. I came to CodeIgniter via PHP 4, which is a procedural language, not really an Object-Oriented (OO) language. I duly looked up objects and methods, properties and inheritance, and encapsulation, but my early attempts to write CI code were plagued by the error message "Call to a member function on a non-object". I saw it so often that I was thinking of having it printed on a t-shirt: it has a mysteriously libertarian, anarchist tone, and I could see myself wearing it at a modern art exhibition.

This is the geek article. It describes the way CodeIgniter actually works, 'under the hood'. If you are new to CI, you may want to skip it. However, sooner or later, you may want to understand why things happen in certain ways—as opposed to just knowing that they do.

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, I've used 'variables/properties', and 'methods/functions' interchangeably, as CI and PHP often do. You write 'functions' in your controllers for instance, when the OO purist would call them 'methods'. You define class 'variables' when the purist would call them 'properties'.

Object-Oriented Programming

I'm assuming you—like me—have a basic knowledge of OOP, but may have learned it as an afterthought to 'normal' PHP 4. PHP 4 is not an OO language, though some OO functionality has been tacked on to it. PHP 5 is much better, with an underlying engine that was written from the ground up with OO in mind.

But 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 is that, when an OO program is running, there is always one current object (but only one). Objects may call each other and hand over control to each other, in which case the current object changes; but only one of them can be current at any one time. The current object defines the 'scope'—in other words, which variables (properties) and methods (functions) are available to the program at that moment. So it's important to know, and control, which object is current. Like police officers and London buses, variables and methods belonging to objects that aren't current just aren't there for you when you most need them.

PHP, being a mixture of functional and OO programming, also offers you the possibility that no object is current! You can start off as a functional program, call an object, let it take charge for a while, and then let it return control to the program. Luckily, CI takes care of this for you.

Working of the CI 'Super-Object'

CI works by building one 'super-object': it runs your whole 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, 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
    set.
    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
    sanitized
    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

On startup—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:

        
  1. 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). Index.php makes some basic checks and calls the codeigniter.php file (codeignitercodeigniter.php).
  2.     

  3. The codeigniter.php file instantiates the Config, Router, Input, URL, (etc.) classes (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.
  4.     

  5. codeigniter.php tests to see which version of PHP it is running on, and calls Base4 or Base5 (/codeigniter/Base4(or 5).php). These create a 'singleton' 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 to the &get_instance() method, it assigns to the single running instance of the class. In other words, it points you to the same pigeonhole. So, instead of setting up lots of new objects, you are starting to build up one 'super-object', which contains everything related to the framework.
  6.     

  7. After a security check, codeigniter.php instantiates the controller that was requested, or a default controller (line 10). The new class is called $CI. 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 then initialize any other classes you need, and 'include' functional scripts you asked for. So in the log above, the model class is initialized. (line 16) The 'boilerplate' script, on the other hand, which is also shown in the log (line 13), is one I wrote to contain standard chunks of text. It's a .php file, saved in the scripts folder, but 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, etc., 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 sets of rules. For example:

        
  • Each function has its own namespace or scope, and variables defined within a function are usually 'local' to it. Outside the function, these 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 for as long as the object exists, but can only be referenced through the object.

So $variable, global $variable, and $this->variable are three different things.

Particularly, before OO, this could lead to all sorts of confusion: you may have too many variables in your namespace (so that conflicting names overwrite each other), or you may find that some variables are just not accessible from whatever scope you happen to be in. CI offers a clever way of sorting this out for you.

So, now you've started CI, using the URL www.mysite.com/index.php/welcome/ index, which specifies that you want the index function of the welcome controller.

If you want to see what classes and methods are now in the current namespace and available to you, try inserting this 'inspection' code in the welcome controller:

    $fred = get_declared_classes();
    foreach($fred as $value)
    {$extensions = get_class_methods($value);
    print "class is $value, methods are: ";
    print_r($extensions);}

When I ran this just now, it listed 270 declared classes. Most are other libraries declared in my installation of PHP. The last 11 came from CI: ten were the CI base classes (config, router, etc.) and last of all came the controller class I had called. Here's the last 11, with the methods omitted from all but the last two:

    258: class is CI_Benchmark
    259: class is CI_Hooks,
    260: class is CI_Config,
    261: class is CI_Router,
    262: class is CI_Output,
    263: class is CI_Input,
    264: class is CI_URI,
    265: class is CI_Language,
    266: class is CI_Loader,
    267: class is CI_Base,
    268: class is Instance,
    269: class is Controller, methods are: Array ( [0] => Controller [1]
    => _ci_initialize [2] => _ci_load_model [3] => _ci_assign_to_models
    [4] => _ci_autoload [5] => _ci_assign_core [6] => _ci_init_scaffolding
    [7] => _ci_init_database [8] => _ci_is_loaded [9] => _ci_scaffolding
    [10] => CI_Base )
    270: class is Welcome, methods are: Array ( [0] => Welcome [1] =>
    index [2] => Controller [3] => _ci_initialize [4] => _ci_load_model
    [5] => _ci_assign_to_models [6] => _ci_autoload [7] => _ci_assign_core
    [8] => _ci_init_scaffolding [9] => _ci_init_database [10] => _ci_is_
    loaded [11] => _ci_scaffolding [12] => CI_Base ).

Notice—in parentheses as it were—that the Welcome class (number 270: the controller I'm using) has all the methods of the Controller class (number 269). This is why you always start off a controller class definition by extending the controller class—you need your controller to inherit these functions. (And similarly, models should always extend the model class.) Welcome has two extra methods: Welcome and index. So far, out of 270 classes, these are the only two functions I wrote!

Notice also that there's an Instance class. If you inspect the class variables of the 'Instance' class, you will find there are a lot of them! Just one class variable of the Instance class, taken almost at random, is the array input:

    ["input"]=> &object(CI_Input)#6 (4) { ["use_xss_clean"]=> bool(false)
    ["ip_address"]=> bool(false) ["user_agent"]=> bool(false) ["allow_get_
    array"]=> bool(false) }

Remember when we loaded the input file and created the original input class? Its class variables were:

    use_xss_clean is bool(false)
    ip_address is bool(false)
    user_agent is bool(false)
    allow_get_array is bool(false)

As you see, they have now all been included within the 'instance' class.

All the other CI 'base' classes (router, output, etc.) are included in the same way. You are unlikely to need to write code referencing these base classes directly, but CI itself needs them to make your code work.

Copying by Reference

You may have noticed that the CI_Input class is assigned by reference (["input"]=> &object(CI_Input)). This is to ensure that as its variables change, so will the variables of the original class. As assignment by reference can be confusing, here's a short explanation. We're all familiar with simple copying in PHP:

    $one    =    1;
    $two    =    $one;
    echo $two;

produces 1, because $two is a copy of $one. However, if you re-assign $one:

    $one    =    1;
    $two    =    $one;
    $one    =    5;
    echo $two;

This code still produces 1, because changes to $one after $two has been assigned aren't reflected in $two. This was a one-off assignment of the value that happened to be in variable $one at the time, to a new variable $two, but once it was done, the two variables led separate lives. (In just the same way, if I alter $two, $one doesn't change.)

In effect, PHP creates two pigeonholes: one called $one, one called $two. A separate value lives in each. You may, on any one occasion, make the values equal, but after that they each do their own thing.

PHP also allows copying 'by reference'. If you add just a simple & to line 2 of the code:

    $one = 1;
    $two =& $one;
    $one = 5;
    echo $two;

Then the code now echoes 5: the change we made to $one has also happened to $two. Changing the = to =& in the second line means that the assignment is 'by reference'. Now, it's as if there was only one pigeonhole, which has two names ($one and $two). Whatever happens to the contents of the pigeonhole happens both to $one and to $two, as if they were just different names for the same thing.

The principle works for objects as well as simple string variables. You can copy or clone an object using the = operator, in which case you make a simple one-off new copy, which then leads an independent life. Or, you can assign one to the other by reference: now the two objects point to each other, so any changes made to the one will also happen to the other. Again, think of them as two different names for the same thing.

CodeIgniter for Rapid PHP Application Development Improve your PHP coding productivity with the free compact open-source MVC CodeIgniter framework!
Published: July 2007
eBook Price: $20.99
Book Price: $34.99
See more
Select your format and quantity:

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, and a constructor that assigns them values of 1 and 2 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_Loader. So the code you write in your controller is: $this->load->model($status) 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 (/system/libraries/loader.php) and that says:

    function model($model, $name = '')
    {
        if ($model == '')
        return;
        $obj =& get_instance();
        $obj->_ci_load_model($model, $name);
    }

(The $name variable in this code is there in case you want to load your model under an alias. I don't know why you should want to do this; perhaps it's wanted by the police in several other namespaces.)

As you can see, the model is loaded by reference into the Instance class. Because get_instance() is a singleton method, you're always referencing the same instance of the Instance class.

If you run the controller again, using our 'inspect' code modified to show class variables, you'll now see that the instance class contains a new class variable:

    ["status"]=> object(Status)#12 (14) { ["one"]=> int(1) ["two"]=>
    int(2) ... (etc)

In other words, the CI 'super-object' now includes 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 up the one big CI 'super-object', which allows you to use any of its methods and variables without worrying too much about where they came from and what namespace they might be in.

This is the reason for the CI arrow syntax. To use the methods of (say) a model, you must first load the model in your controller:

    $this->load->model('Model_name');

This makes the model into a class variable of $this->, the current (controller) class. You then call a function of that class variable from the controller, like this:

    $this->Model_name->function();

and off you go.

Problems with the CI 'Super-Object'

There was one big problem for Rick Ellis when he wrote the original code. PHP 4 handles objects less elegantly than PHP 5, so he had to introduce a 'really ugly hack' (his words) into the Base4 file. Ugly or not, the hack works, and so we don't need to worry about it. It just means that CI works as well on PHP 4 systems as it does on PHP 5.

There are two other issues worth mentioning here:

        
  • You can find yourself trying to work with an object that isn't available.
  •     

  • You have to structure your site carefully, because you can't call methods of one controller from inside another.

Let's look at these two problems in turn. You remember the t-shirt I mentioned above: "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 a model class that you wrote) but forgot to load the class. In other words, you wrote:

    $this->Model_name->function();

but forgot to precede it by:

    $this->load->model('Model_name');

Or some variation of this: for instance, you loaded the model inside one function of a class, which loads the model, but only inside that function, and then you tried to use its methods from inside another function, albeit in the same class. It's usually best to load models, etc., from the class constructor function: then they are available to all the other functions in the class.

The problem can also be more subtle. If you write your own classes, for instance, you may wish to use them to access the database, or to look up something in your config  files—in other words, to give them access to something that is part of the CI 'superobject'. (There's a fuller discussion of how to add your own classes or libraries in Chapter 13.) To summarize, unless your new class is a controller, a model, or a view, it doesn't get built in to the CI super-object. So you can't write things inside your new class like this:

    $this->config->item('base_url);

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 (sound familiar?) using another variable name (usually $obj)

    $obj =&get_instance();

Now you can write that call to the CI superclass as:

    $obj->config->item('base_url);

and 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 clearer.

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 from http://www.phpclasses.org/browse/package/514.html). Then, using a switch function, you select one of 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, etc. 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 that:

        
  • The first half of your URL comes from the CI config file, which can only be referenced through the superobject, 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—e.g.:

    class my_new_class{
    var $base;
    My_new_class()
    {
    $obj =& get_instance();
    // geolocation code here, returning a value through a switch statement
    //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 that 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, I wrote a series of self-test functions inside each controller. If I called $this->selftest() inside the controller, it did various useful tests. But it seemed against the principle programming virtue of laziness to have to repeatedly call the self-test method in each controller separately. I tried to write one function, in one controller, that would go through all the controllers, call the self-test method in each, amalgamate all the results while I stared out of the window, and then give me a comprehensive report in exchange for only one mouse click. Alas, no. Can't be done.

As a general rule, if you have code that may be needed by more than one controller, put it in a model or a separate script of some sort. Then they can both use it. (Of course, this doesn't help with my self-test problem, because the code to test the controllers has to be in the controllers!)

But these are minor problems. As Rick Ellis put it to me:

"I wanted to arrive at something more simple so I decided to make one big controller object containing lots of other object instances:…when a user creates their own controllers they can easily access anything that has been instantiated just by calling it, without worrying about scope".

That's pretty well how it works, most of the time, efficiently, and completely in the background. So I never did get that t-shirt printed.

Summary

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'. Most of the time, you don't need to know what the 'super-class' is doing, provided that you use CI's 'arrow' notation correctly.

We've also looked at how you can write your own classes and still have access to the CI framework.

Lastly, we looked at a few problems that can arise, particularly if you're not used to OO programs, and suggested a few solutions.

CodeIgniter for Rapid PHP Application Development Improve your PHP coding productivity with the free compact open-source MVC CodeIgniter framework!
Published: July 2007
eBook Price: $20.99
Book Price: $34.99
See more
Select your format and quantity:

About the Author :


David Upton

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.

Books From Packt

Drupal 6 Themes
Drupal 6 Themes

Drupal 6 JavaScript and jQuery
Drupal 6 JavaScript and jQuery

Django 1.0 Website Development
Django 1.0 Website Development

Magento: Beginner's Guide
Magento: Beginner's Guide

Drupal 6 Site Builder Solutions
Drupal 6 Site Builder Solutions

jQuery UI 1.6: The User Interface Library for jQuery
jQuery UI 1.6: The User Interface Library for jQuery

Choosing an Open Source CMS: Beginner's Guide
Choosing an Open Source CMS: Beginner's Guide

Learning Joomla! 1.5 Extension Development
Learning Joomla! 1.5 Extension Development

 

 

 

 

Your rating: None Average: 5 (1 vote)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
a
y
v
q
A
h
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software