Setting up a caching system with CodeIgniter

Caching for CodeIgniter is not great, the standard functionality allows you to either remove all cached files, or nothing at all. So once again it is time to look for a better alternative.

Luckily we have another great addition from Phil Sturgeon, a custom caching library.

Make sure your application/cache folder is writable – for me 755 works but this might not be the case for you. I’ve also added an .htaccess file with deny from all inside it.

Let’s build on a previous example I posted some time ago: the settings model, where site settings are loaded from the database each time a page loads. This might cause performance issues when the project gets bigger and I’m definitely not a huge fan of squandering resources, being the perfectionist that I am.

Here is our reworked Settings_model:

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

class Settings_model extends CI_Model {

    public static $db_config = array();

    public function __construct() {
        parent::__construct();

        $this->_load_settings();
    }

    /**
     *
     * _load_settings: load the settings from database
     *
     *
     */

    protected function _load_settings() {
        $this->load->library('cache');
        $data = $this->cache->get('settings');
        if (empty($data)) {
            $this->db->select('login_enabled, register_enabled, install_enabled, members_per_page, admin_email, home_page, default_theme,
            login_attempts, recaptcha_theme, email_protocol, sendmail_path, smtp_host, smtp_port, smtp_user, smtp_pass, site_title, cookie_expires,
            password_link_expires, activation_link_expires, disable_all');
            $this->db->from('settings');
            $this->db->limit(1);
            $query = $this->db->get();

            if($query->num_rows() == 1) {
                $row = $query->row();
                self::$db_config['login_enabled'] = $row->login_enabled;
                self::$db_config['registration_enabled'] = $row->register_enabled;
                self::$db_config['install_enabled'] = $row->install_enabled;
                self::$db_config['members_per_page'] = $row->members_per_page;
                self::$db_config['admin_email_address'] = $row->admin_email;
                self::$db_config['home_page'] = $row->home_page;
                self::$db_config['default_theme'] = $row->default_theme;
                self::$db_config['login_attempts'] = $row->login_attempts;
                self::$db_config['email_protocol'] = $row->email_protocol;
                self::$db_config['sendmail_path'] = $row->sendmail_path;
                self::$db_config['smtp_host'] = $row->smtp_host;
                self::$db_config['smtp_port'] = $row->smtp_port;
                self::$db_config['smtp_user'] = $row->smtp_user;
                self::$db_config['smtp_pass'] = $row->smtp_pass;
                self::$db_config['site_title'] = $row->site_title;
                self::$db_config['recaptcha_theme'] = $row->recaptcha_theme;
                self::$db_config['cookie_expires'] = $row->cookie_expires;
                self::$db_config['password_link_expires'] = $row->password_link_expires;
                self::$db_config['activation_link_expires'] = $row->activation_link_expires;
                self::$db_config['disable_all'] = $row->disable_all;

                $this->cache->write(self::$db_config, 'settings');
            }
        }else{
            self::$db_config['login_enabled'] = $data['login_enabled'];
            self::$db_config['registration_enabled'] = $data['registration_enabled'];
            self::$db_config['install_enabled'] = $data['install_enabled'];
            self::$db_config['members_per_page'] = $data['members_per_page'];
            self::$db_config['admin_email_address'] = $data['admin_email_address'];
            self::$db_config['home_page'] = $data['home_page'];
            self::$db_config['default_theme'] = $data['default_theme'];
            self::$db_config['login_attempts'] = $data['login_attempts'];
            self::$db_config['email_protocol'] = $data['email_protocol'];
            self::$db_config['sendmail_path'] = $data['sendmail_path'];
            self::$db_config['smtp_host'] = $data['smtp_host'];
            self::$db_config['smtp_port'] = $data['smtp_port'];
            self::$db_config['smtp_user'] = $data['smtp_user'];
            self::$db_config['smtp_pass'] = $data['smtp_pass'];
            self::$db_config['site_title'] = $data['site_title'];
            self::$db_config['recaptcha_theme'] = $data['recaptcha_theme'];
            self::$db_config['cookie_expires'] = $data['cookie_expires'];
            self::$db_config['password_link_expires'] = $data['password_link_expires'];
            self::$db_config['activation_link_expires'] = $data['activation_link_expires'];
            self::$db_config['disable_all'] = $data['disable_all'];
        }
    }

}

/* End of file settings_model.php */
/* Location: ./application/models/system/settings_model.php */

First of all, follow the installation instructions that come with Phil’s custom library. Basically you need to copy 2 files into your project.

We start off by loading the cache library and get the current data from our settings cache:

$this->load->library('cache');
$data = $this->cache->get('settings');

If there is data (string) then we won’t load the usual database query and load the cache variables into the static array. If there is no data then we just run the query as usual and we load the result into a cache file:

$this->cache->write(self::$db_config, 'settings');

That’s all. I remove the cache right before saving the settings in another controller class called Site_settings. I use that class to – believe it or not – show and edit the site settings. Deleting the cache:

$this->cache->delete('settings');

Very easy!

Posted in CodeIgniter, PHP | Tagged , , | Leave a comment

Extending the CodeIgniter form validation library

Form validation has many faces and the default CodeIgniter set of validation rules just doesn’t cut it for me, or anyone. Luckily it’s very easy to extend the standard library, making it possible to add any kind of validation you can imagine.

If you go to system/libraries/Form_validation.php you can have a look at all the available methods that make up of the default validation. These are off course also described here in the manual, but since we’re extending this library it could be nice to know what exactly we are extending.

When you extend a library, your new class should go in application/libraries. The file name is very important! It needs to be called exactly the same as the class name. Furthermore, you need to add MY_ before it.
My file is called MY_Form_validation.php and so is my class. Not complying with these rules will give you headackes because no error will tell you that there is an issue with your naming.

Code example:

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

class MY_Form_validation extends CI_Form_validation {

    public function __construct()
    {
        parent::__construct();
    }

    /**
     *
     * is_valid_username: verify validity of username against regular expression: a-z, A-Z, 0-9, _, - are allowed
     *
     * @param string $username the username to be validated
     * @return boolean
     *
     */

    public function is_valid_username($username) {
        if (preg_match("/^[a-zA-Z0-9_-]+$/", $username)) {
            return true;
        }
        return false;
    }

     /**
     *
     * is_existing_unique_field: check for the existence of a unique field within a database table column
     *
     * @param string $field_value value to be examined
     * @param string $table database table to examine
     * @param string $column_name table column name
     * @return boolean
     *
     */

    public function is_existing_unique_field($value, $info) {

        list($table, $column) = explode('.', $info, 2);

        $this->CI->db->select($column);
        $this->CI->db->from($table);
        $this->CI->db->where($column, strtolower($value));
        $this->CI->db->limit(1);

        $query = $this->CI->db->get();

        if($query->num_rows() == 0) {
            return true;
        }
        return false;
    }

}

This example has a method that will check whether the username fits our naming convention and a method that will check whether the username is taken or not.

Then in our controller we simply add it in our form validation with set_rules():

$this->form_validation->set_rules('username', 'username',
'trim|required|max_length[16]|min_length[6]|is_valid_username|is_existing_unique_field[user.username]');

The first function, is_valid_username, is very simple. The second one, is_existing_unique_field uses a technique I found on some website, I don't know where I found it so sadly I can't show you the original post. Since we can send only one parameter through to the method the trick is to split a string up into usable chunks (explode) and then use these to query the database.

All in all very easy, in case it's not clear just leave a comment.

Posted in CodeIgniter, PHP | Tagged , , | Leave a comment

Making global config data available from database in Codeigniter

It’s been a while since I wrote my last article, mostly because I’m working hard on this private project and have to spend all my time on it. This article discusses a way of setting global configuration options from database, available through the whole website.

I’d still like to share with you this interesting bit of coding. I’m using an auto-loaded model to set some static variables which have been pulled from the database. This database has a table with one row of data which holds all config data.

Have a look at the Settings_model:

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

class Settings_model extends CI_Model {

    public static $db_config;

    public function __construct() {
        parent::__construct();
        $this->_load_settings();
    }

    /**
     *
     * _load_settings: load the settings from database
     *
     *
     */

    protected function _load_settings() {
        $this->db->select('login_enabled, register_enabled, members_per_page, admin_email, home_page, default_theme,
        login_attempts, email_protocol, sendmail_path, smtp_host, smtp_port, smtp_user, smtp_pass, site_title');
        $this->db->from('settings');
        $this->db->limit(1);
        $query = $this->db->get();

        if($query->num_rows() == 1) {
            $row = $query->row();
            self::$db_config['login_enabled'] = $row->login_enabled;
            self::$db_config['registration_enabled'] = $row->register_enabled;
            self::$db_config['members_per_page'] = $row->members_per_page;
            self::$db_config['admin_email_address'] = $row->admin_email;
            self::$db_config['home_page'] = $row->home_page;
            self::$db_config['default_theme'] = $row->default_theme;
            self::$db_config['login_attempts'] = $row->login_attempts;
            self::$db_config['email_protocol'] = $row->email_protocol;
            self::$db_config['sendmail_path'] = $row->sendmail_path;
            self::$db_config['smtp_host'] = $row->smtp_host;
            self::$db_config['smtp_port'] = $row->smtp_port;
            self::$db_config['smtp_user'] = $row->smtp_user;
            self::$db_config['smtp_pass'] = $row->smtp_pass;
            self::$db_config['site_title'] = $row->site_title;
        }
    }

}

/* End of file settings_model.php */
/* Location: ./application/models/settings_model.php */

As you can see I’m using a static array called $db_config and to make sure it is available I’m also autoloading it in config/autoload.php.

$autoload['model'] = array('system/settings_model');

Now if you need to access a variable all you need to do is:

print "<div class=\"input_div\">Site title &raquo; ".
form_input(array('name' => 'site_title', 'id' => 'site_title', 'class' => 'input_text', 'value' => Settings_model::$db_config['site_title'])) ."</div>\r\n";

This should be straight-forward enough but do comment in case of any questions or remarks.

Posted in CodeIgniter, PHP | Tagged , | Leave a comment

jQuery.getJSON() and sending special characters to PHP through a URL

A quick word on sending JSON requests that might possibly contain special characters. We’re going to send this data through a URL to our PHP script so we need to have some kind of encoding/decoding structure in place for it to work.

First off, the encodeURIComponent() JavaScript function takes care of converting special characters to their %-based equivalent. This means that we can safely use them inside our getJSON function like this:

$.getJSON('register/is_valid_email/' + encodeURIComponent($("#email").val()), function(data) {
    // do stuff
});

I’m calling a PHP function here (CodeIgniter) where the first and only parameter is a string containing an e-mail address.
This function might look like this:

public function is_valid_email($email) {
    $email = rawurldecode($email);
    //validate email address any way you prefer
}

The rawurldecode is being used to safely convert the encoded e-mail address back to normal. Conclusion: encodeURIComponent is the JavaScript counterpart of the PHP rawurldecode function?

Well, not quite. As Javier states in his 2008 post on php.net, some characters are not decoded on the PHP side and you should use a custom function to deal with those. As far as I know you can’t use the characters he mentions in an e-mail address (yet) but validating e-mail addresses is one of the most complicated tasks and the RFCs change from time to time making it wise to keep track of this evolution. It proves again that there’s always something around the corner and programming is not always an exact science.
Also, check this post where it states: “encodeURIComponent escapes all characters except the following: alphabetic, decimal digits, – _ . ! ~ * ‘ ( )“.
After some careful testing I came up with the characters that are not processed by both encodeURIComponent () and PHP’s rawurldecode(): ! * ( ) % /, or at least these are the ones where the PHP function did not return an answer, indicating something went wrong. I hope my conclusion is complete but will edit this post should I find holes in my research.

Posted in jQuery, JSON | Tagged , , , | Leave a comment