Welcome Guest, Not a member yet? Register   Sign In
My Codeigniter Observations
#1

[eluser]James Spibey[/eluser]
I'm almost done on my first codeigniter application and I'd just like to write a bit about my experiences with it and how I've used it. The application in question gets around 500,000 views per month, has taken nearly 6 months (part-time) to complete and has over 500 application source files (not including CI).

Firstly, this is a great, great product. The key for me with CI is that it never gets in the way of my coding. I spent almost zero time getting the framework running and since then there have been very few situations where I have had to dig into the CI code to figure out why something wasn't working the way I wanted. The great thing about CI is the wealth of libraries and helpers that you get with it for free - anything I could think of which I'd need during development was there, ready and waiting and working perfectly. And if it wasn't there, I just found an open source library which did do it and just dropped it in. Awesome.

So, if there are any tips I do have, they are focused more on application structure and MVC design.

Get your models, views and controllers straight in your mind.

For a while I couldn't understand how to structure my application because I'd not used MVC before. However, once I'd got used to it, it was pretty easy. In my opinion, your model is just a gateway to your database, it marshalls data back and forth between your database and your controller - don't try and make it do any more than this.

Your views are just that, views. Don't put any application logic in here at all. If you need to do anything in your view which is starting to look like php, check that this couldn't be better achieved by using a helper.

Your controllers are the daddy of the application. They receive input from the client, sanitize and validate it, and then pass it onto the model. The data they receive back from the model should be wrapped as php objects which then get passed onto the view (see next point).

Make your Model return a collection of typed object rather than arrays.

The most obvious way to get data in your model is to just select from the database and then return an array of results (with each result being an array of fields or an object). Here's a simple example which gets a list of customers from the database
Code:
class Customermodel extends Model {
{
  function get_customers()
  {
    $query = $this->db->getwhere('customers');
    return $query->result();
  }
}
Instead, have your model package each record returned from the database into an object (in this case a 'Customer' object).
Code:
class Customermodel extends Model {
{
  function get_customers()
  {
    $query = $this->db->getwhere('customers');
    return $this->format_customers($query);
  }

  function format_customers($query)
  {
    $customers = $query->result_array();
  
    $new_customers = array();

    foreach($customers as $customer)
    {
      $new_customers[] = new Customer($customer);
    }

    return $new_customers;
  }
}
And then have your Customer class (in your Libraries folder) look like this
Code:
class Customer {
  
  function Customer($customer = null)
  {
    if(isset($customer))
    {
      $this->_load($customer);
    }
  }
  
  function _load($customer)
  {
    while (list ($key, $val) = each ($customer))
    {
      $this->$key = stripslashes($val);
    }
  }

  // Example function
  function get_full_name()
  {
    return $this->fore_name + ' ' + $this->last_name;
  }

Now, this might seem a little long-winded but it means that you can wrap the data from the database into an object and then have specific logic attached to the data to control how it is displayed etc in a consistent way. So, if I wanted to change the way a customer's name is displayed throughout the application, I only need to change the get_full_name() function and it is instantly changed everywhere (a simple example but you get the idea). When you are dealing with lots of complicated data this will simplify the code in your views and controllers no end and will make your code more robust.

Other Bits
- Have all your controllers inherit from a common base class (called something like BaseController) which in turn inherits from Controller. This makes it much easier to share code between controllers and pages (such as common login/logout and page access code).

- If you are creating a website with user authentication, encapsulate the current logged in user in a class (like the Customer one above) and store that in session (database or PHP sessions). Then have this loaded automatically in your basecontroller so that every controller has access to it as soon as they are loaded. This will let you manage access to pages and identify what privileges the currently logged in user has much more simply and consistently.

There's tonnes more things I've found along the way but this is far too long already. This is just the way I've done it and you have found a different route but I've found that I've barely had to change my architecture at all throughout the project to accommodate the client using the design above so that makes it perfect for me!
#2

[eluser]Beren[/eluser]
Great post!! Very useful tips, they seem sensible and simple ideas, especially switching to returning objects (which I shall definitively be implementing).

One question for someone coming off a large CI project - how did you manage with finding bugs. The biggest 'problem' I've been having with CI is the lack of a 'stacktrace' type error report. If I have an error in my code I just get a pretty general error page and then have to go through the code and basically manually bug hunt. OK I can put log_message() calls in to try and narrow down where but do you have any other tips or techniques that might help?

Cheers for the great post - will be bookmarking this one!
#3

[eluser]James Spibey[/eluser]
The first thing to do is override the exceptions library from codeigniter with your own and then override each of the methods in there (show_error(), show_php_error() etc). What I do is capture the stacktrace and some client info and then send it to me via email. You could then either call the standard codeigniter error handler (parent:Confusedhow_php_error()Wink or do as I do and forward the user onto a controller in my app which shows a nice error page using my template formatting.
#4

[eluser]Lone[/eluser]
Great post and contribution there - out of interest are you able to post the URL of the site you used CI for?

I am one for doing things in a similar way to you with the 'format_' use and used to do it in our old framework which became very tiresome compared to doing it in CI.
#5

[eluser]Beren[/eluser]
[quote author="spib" date="1200941174"]The first thing to do is override the exceptions library from codeigniter with your own and then override each of the methods in there (show_error(), show_php_error() etc).[/quote]

Thanks very much for this, didn't know I could do that - took your advice yesterday and now I have a pretty little backtrace being output on my error pages whenever I have my debug constant TRUE. Awesome! Many, many thanks!
#6

[eluser]Référencement Google[/eluser]
Beren, can you post the code for it ?
#7

[eluser]Negligence[/eluser]
I don't use CI any longer, but at one time I did use it extensively for developing enterprise applications. As a note to your general MVC recommendations, I take issue with a few of your points. I write this because I want to ensure that new developers reading this take your suggestions into consideration, along with alternatives.

Quote:Your views are just that, views. Don't put any application logic in here at all. If you need to do anything in your view which is starting to look like php, check that this couldn't be better achieved by using a helper.

You can't get away from putting application logic/PHP code within a View, it's the nature of the beast. Helpers don't really change this, they only add to the complexity and the creation of generalized functions. Don't shy away from using code in a view, it is not a bad thing. Of course, if you're executing queries from a view, then we've got an issue.

Quote:Your controllers are the daddy of the application. They receive input from the client, sanitize and validate it, and then pass it onto the model. The data they receive back from the model should be wrapped as php objects which then get passed onto the view (see next point).

Two things:
- I use the Model to validate data, since a Model's sole purpose is to deal directly with data. The Model validates and returns the results back to the controller. But this is just how I do it.

- You're over-complicating things significantly by suggesting that you should return an array of objects, instead an array of records. Not only are you creating extra overhead that isn't required, but you are requiring an additional library just to use the model.

In fact, the Customer library you suggested is doing the job of the Model, so you've kind of invalidated the concept of a separation of duties.

It is a better approach to keep things stored in arrays for most people. All the advantages you claim to gain by using a wrapper, you could just as easily do within the Model, and you still only have to change it once. You're abstracting things to the point where you've gained nothing more than what you started with.

Otherwise, you've given some good advice here.
#8

[eluser]Beren[/eluser]
[quote author="elitemedia" date="1201026274"]Beren, can you post the code for it ?[/quote]

@spib - sorry for the quick hijack of your thread, but seemed simpler than starting a new one just to post this code, hope you don't mind :o)

@elitemedia - Here you go, just put this code in a file called MY_Exceptions.php in your 'application/libraries' directory and set a boolean DEBUG_BACKTRACE constant somewhere in your code (I recommend in config.php) and you're good to go. DEBUG_BACKTRACE set to FALSE removes any change from CI standard behaviour.

It's nothing too special but I hope it's of some use to you, if you run into any problems just PM me.

Code:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
* Extended CodeIgniter Exception class
*
* Compatible with PHP versions 4.3.0+ and 5
*
* Will display a backtrace list on the error page if the constant
* DEBUG_BACKTRACE has been set to TRUE. False disables all change
* from default CI behaviour. Please add a constant definition to your
* config.php, I recommend just below log_threshold since they are related
* Example: define('DEBUG_BACKTRACE', TRUE);
*
* Remember to set it to FALSE on your live site!
*
* @version   0.1.0
*/

class MY_Exceptions extends CI_Exceptions {
  
  /**
   * Generates a pretty backtrace for display in the browser
   *
   * Backtrace processing inspired by Kohana project (kohanaphp.com)
   *
   * @access public
   * @return string  full HTML string ready to be output
   */
  function generate_backtrace()
  {
      // the first two results are this function and $this->show_error() call
      // so we'll ignore them
      $backtrace = array_slice(debug_backtrace(), 2);
      
      $trace_output = array();
      
      foreach($backtrace as $entry)
      {
        $html = '<li>';
        
        if( isset($entry['file']))
        {
          $html .= 'Line #<strong>' . $entry['line'] . '</strong> of <strong>' . $entry['file'] . '</strong>';
        }
        
        $html .= '<pre>';
      
        if( isset($entry['class']))
        {
          $html .= $entry['class'].$entry['type'];
        }
        
        if( isset($entry['function']))
        {
          $html .= $entry['function'] . '(';
          
          if( isset($entry['args']))
          {
            if( isset($entry['args'][0]) AND is_array($entry['args'][0]))
            {
              $seperator = '';
              
              foreach($entry['args'] as $argument)
              {
                $html .= $seperator . strval($argument);
                $seperator = ', ';
              }
            }
            else
            {
              $html .= implode(', ', $entry['args']);  
            }
          }
          
          $html .= ')';
        }
        
        $html .= '</li>';
        
        $trace_output[] = $html;
      }
      echo '<h2 style="font-weight:normal">Backtrace</h2><ul>' . implode("\n", $trace_output) . '</ul>';
  }


    /**
     * General Error Page
     *
     * This function takes an error message as input
     * (either as a string or an array) and displays
     * it using the specified template.
     *
     * @access    private
     * @param    string    the heading
     * @param    string    the message
     * @param    string    the template name
     * @return    string
     */
    function show_error($heading, $message, $template = 'error_general')
    {
        $message = '<p>'.implode('</p><p>', ( ! is_array($message)) ? array($message) : $message).'</p>';

        if (ob_get_level() > $this->ob_level + 1)
        {
            ob_end_flush();    
        }
        ob_start();
        include(APPPATH.'errors/'.$template.EXT);
        
        if (DEBUG_BACKTRACE)
        {
          echo $this->generate_backtrace();
        }
    
        $buffer = ob_get_contents();
        ob_end_clean();
        return $buffer;
    }


    /**
     * Native PHP error handler
     *
     * @access    private
     * @param    string    the error severity
     * @param    string    the error string
     * @param    string    the error filepath
     * @param    string    the error line number
     * @return    string
     */
    function show_php_error($severity, $message, $filepath, $line)
    {    
        $severity = ( ! isset($this->levels[$severity])) ? $severity : $this->levels[$severity];

        $filepath = str_replace("\\", "/", $filepath);

        // For safety reasons we do not show the full file path
        if (FALSE !== strpos($filepath, '/'))
        {
            $x = explode('/', $filepath);
            $filepath = $x[count($x)-2].'/'.end($x);
        }

        if (ob_get_level() > $this->ob_level + 1)
        {
            ob_end_flush();    
        }
        ob_start();
        include(APPPATH.'errors/error_php'.EXT);
        
        if (DEBUG_BACKTRACE)
        {
          echo $this->generate_backtrace();
        }
        
        $buffer = ob_get_contents();
        ob_end_clean();
        echo $buffer;
    }

}
#9

[eluser]Référencement Google[/eluser]
Beren, thanks a lot.
You should post it to the wiki, that would really interrest more than 1 developper !
#10

[eluser]Edemilson Lima[/eluser]
Quote:You can’t get away from putting application logic/PHP code within a View, it’s the nature of the beast.

I agree with that. You can use how much PHP code you want in a view, since you keep only presentation logic on it. The viewers can manipulate the data in any way to print it as you wish, but they never can produce the data. This is the models duty.

Quote:I use the Model to validate data, since a Model’s sole purpose is to deal directly with data.

Again I do agree. It is always easy to validate data at the model once than at the controllers many times.

Quote:You’re over-complicating things significantly by suggesting that you should return an array of objects, instead an array of records.

I agree too. Isn't that exactly what the db->result_object() does?

Code:
while ($row = $this->_fetch_object())
{
    $this->result_object[] = $row;
}
return $this->result_object;




Theme © iAndrew 2016 - Forum software by © MyBB