Extending CodeIgniter’s Validation Library To Check For Unique Values

Updated August 29, 2010: I’ve updated this post to be compatible with the latest version of CodeIgniter. It’s currently compatible with 1.7.0 and later. If you are still running on an older version and want the old version of this code to match let me know; I will send it to you. When I updated the post I also reset the discussion since most of it was specific to the old code.

February 15, 2011 Calvin Froedge rightly notes in the comments that with the release of CodeIgniter 2.0 the old PHP-4-compatible constructor style no longer works. The code below is updated to reflect that. He also has some thoughts about internationalization with language files and storing the visitor’s language in the session variables. This should be back-compatible with old versions CodeIgniter as long as you’re running PHP 5 on your server.

I’m working on small web application and I’m building it with CodeIgniter. Like most web applications, mine requires user registration and login. And like most login systems, mine is happiest if each user has a unique username. While the CodeIgniter validation library is pretty robust, it doesn’t come with a function for checking a value to see if it is unique or if it already exists in the database. Fortunately there are a couple of ways to remedy that.

The first way is outlined in the documentation for the validation library under the heading “Callbacks: Your own Validation Functions.” I tried this and it works well, but it requires you to write the functions in your controller or model. I thought that looked messy and I wanted something a little more streamlined, so I decided to extend the validation library with my own function. Extending CodeIgniter’s libraries is pretty easy. Here’s what I came up with–I’ll explain the important lines afterward:


<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
 * MY_Form_validation Class
 *
 * Extends Form_Validation library
 *
 * Adds one validation rule, "unique" and accepts a
 * parameter, the name of the table and column that
 * you are checking, specified in the forum table.column
 *
 * Note that this update should be used with the
 * form_validation library introduced in CI 1.7.0
 */
class MY_Form_validation extends CI_Form_validation {

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

	// --------------------------------------------------------------------

	/**
	 * Unique
	 *
	 * @access	public
	 * @param	string
	 * @param	field
	 * @return	bool
	 */
	function unique($str, $field)
	{
		$CI =& get_instance();
		list($table, $column) = explode('.', $field, 2);

		$CI->form_validation->set_message('unique', 'The %s that you requested is unavailable.');

		$query = $CI->db->query("SELECT COUNT(*) AS dupe FROM $table WHERE $column = '$str'");
		$row = $query->row();
		return ($row->dupe > 0) ? FALSE : TRUE;
	}
}
?>

Save the above code as system/application/libraries/MY_Form_validation.php and you’re ready to go. You can now add the “unique” rule to your existing validation, like so:


$this->form_validation->set_rules('username','User Name','required|min_length[5]|unique[users.username]');
$this->form_validation->set_rules('emailaddress','Email Address','required|valid_email|unique[users.email]');

This will check the values that entered for username and emailaddress to make sure they don’t already exist in the username and email fields in your table called users. The general format for the rule, then, is : unique[table.column]. This matches the SQL syntax for specifying fields when field names are ambiguous. Pretty cool.

So here’s what’s going on in each of the important lines.


$CI =& get_instance();

This creates a reference to the CodeIgniter super object and allows you to use it’s functions in your library. See the user guide for an explanation (under the heading “Utilizing CodeIgniter Resources within Your Library.”)


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

This line parses out the table name and column name. Note that it only returns 2 strings. This way you can specify something like unique[users.email.address], which would check the users table for a column called email.address. In mysql periods are allowed in field names but not table names, and the validation rule reflects that.


$CI->form_validation->set_message('unique', 'The %s that you requested is unavailable.');

This line sets the error message in case the value isn’t unique. %s will be replaced by the field description you enter in your validation fields. This would be better off in a language file, but for ease of use I’ve stuck it right in the library. CodeIgniter allows you to replace entire language files but not simply extend them with a single line. If that changes in the future I’ll revisit my decision.


$query = $CI->db->query("SELECT COUNT(*) AS dupe FROM $table WHERE $column = '$str'");
$row = $query->row();

Here’s where it checks the database to make sure your field is unique. In the query I assigned the row count to the keyword “dupe” (for duplicate.) I wanted to use “exists” or “match” but both are protected words in mysql. Oh well. Note the use of $CI->db->query instead of $this->db->query. That’s explained in the documentation link earlier in this post.


return ($row->dupe > 0) ? FALSE : TRUE;

Finally, this line returns the result of the validation check. In case you’re not accustomed to using ternary operators, here it is as an if/else:


if ($row->dupe > 0)
{
	return FALSE; // a match was found, validation fails
}
else
{
	return TRUE; // value is unique, validation passed
}

So there you have it. A quick extension to CodeIgniter’s validation library that allows you to check your database for unique values.

36 thoughts on “Extending CodeIgniter’s Validation Library To Check For Unique Values

  1. k gud….
    But I’m also tried to something similar that…
    its not working properly. could you help me…

    here is the code
    class control extends Controller
    {
    function control()
    {
    parent::Controller();

    load->library(“MY_User_manage”);
    }

    function register()
    {
    $this->my_user_manage->register();
    }
    ……
    class MY_User_manage
    {
    function MY_User_manage()
    {

    CI = &get_instance();
    $this->CI->load->library(‘form_validation’);
    $this->CI->load->library(‘validation’);
    $this->CI->load->library(‘my_validation’);
    $this->CI->load->model(‘user_model’);
    }

    function register()
    {
    $this->CI->form_validation->set_rules(‘username’, ‘Name’, ‘trim|required|max_length[15]|name_check[username]‘);
    $this->CI->form_validation->set_rules(‘email’,’Email’,’trim|required|callback_email_check|valid_email|email_available’);
    $this->CI->form_validation->set_rules(‘password’,’Password’,’trim|required|min_length[6]|max_length[12]|matches[confirm_password]‘);
    if ($this->CI->form_validation->run() == FALSE)
    …….

    class MY_Validation extends CI_Form_validation
    {
    function MY_Form_Validation()
    {
    parent::CI_Form_Validation();
    CI = &get_instance();
    }
    function email_check($str)
    {
    if (!strcasecmp($str,’Email Id’))
    {
    $this->CI->form_validation->set_message(‘email_check’, ‘The %s field is required’);
    return FALSE;
    }
    else
    {
    return TRUE;
    }
    }
    function email_available($email)
    {
    if($this->CI->user_model->validEmail($email))
    {
    $this->CI->form_validation->set_message(‘email_available’, ‘”‘.$email.’” is already in use…’);
    return FALSE;
    }
    else
    {
    return TRUE;
    }
    }
    function name_check($str,$value)
    { if ((!strcasecmp($value,’Name’)) || (!strcasecmp($value,’Guest’)))
    {
    $this->CI->form_validation->set_message(‘name_check’, ‘The %s field can not be the word “‘.$value.’”‘);
    return FALSE;
    }
    else
    {
    return TRUE;
    }
    }
    }
    ….

  2. A nice addition.

    Only one remarks one it comes to the update of an existing record. The validation will then fail and it should not

  3. Brilliant! Thanks for the tut.

    I’ve also passed on a language key for the error message:

    unique[user.email.validation_email_registered]

    In the form validation file:
    list($table, $column, $error_lang_key) = explode(‘.’, $field.’.’);

    $CI->lang->load(‘some_validation_file’, ‘english’); // Load language files.

    if(empty($error_lang_key)){ // Check if error msg key exists, or just use default.
    $error = $CI->lang->line(‘validation_unique’);
    } else {
    $error = $CI->lang->line($error_lang_key);
    }
    $CI->form_validation->set_message(‘unique’, $error);

    Probably a better way of doing this. But just a little expansion on your expansion.

  4. Thanks very much for this. I’m a CI/OOPHP noob and articles like this are super helpful for me at this point. Thanks for taking the time to write this up!

  5. Hi. Thanks for post this. I haven’t try it yet but it seems very useful.

    There’s 1 question I like to ask though.

    U’ve this “function unique($str, $field)”

    and then u set rules

    $this->form_validation->set_rules(‘username’,’User Name’,’required|min_length[5]|unique[users.username]‘);

    I can see u passing the table and column but what about $str?

    That’s the part I don’t understand.

    Please let us know.

    Thanks.

  6. Hi Mike. When you call set_rules it passes each of the rules on to the _execute function of the form validation library. This function automatically passes the posted value of whatever field your testing to whatever function is associated with the rule. It also passes a parameter to that function if one is specified, as it is with the unique validation rule. This happens on line 589 of libraries/Form_validation.php (as of CodeIgniter 1.7.3)

  7. Nice.

    Two questions:

    1. How to deal with what Gazelle already noted: in case of update of an existing record. The validation will then fail and it should not.

    2. How to deal with when a combination of fields must be unique, for example the combination of firstname and lastname.

    Thanks

  8. Hi Koen,
    1. You’ll probably have to update you code to run different validation rules depending on if you’re updating a record or creating a new one. You’re probably already doing tests in your controllers to determine which route to take, so you can put the rules in there.

    2. As far as I can remember (and honestly, I haven’t used codeigniter much lately) the form validation library only processes one form input at a time. So I guess you’d have to have the user input their full name and then split it on a space. You’d also have to update the part of the library extension that splits the table and field names to accept multiple field names. Then you might have $column1 and $column2, and you could update the query to match “WHERE $column1 = {firstname} AND $column2 = {lastname}”

    Ultimately, I don’t think you’d want to enforce unique first and last names since some names are very common, but you may have another case where a combination of fields should be unique. If so, you could certainly play around with the ideas above to get it working.

  9. Please note that for this to work w/ CodeIgniter Reactor you’ll need to use the new style constructor, ie change:
    function My_Form_validation()
    {
    parent::CI_Form_validation();
    }

    to:

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

  10. Please note that I made some other modifications as well (adding the ability to use language files, for example). Here is the updated code which is fully supported in CodeIgniter Reactor (assuming you are storing language in the current session, which requires that your base controllers have already set the language before the functions which require validation are accessed):

    class MY_Form_validation extends CI_Form_validation {

    function __construct()
    {
    parent::__construct();
    $CI =& get_instance();
    $CI->load->helper(‘language’);
    $CI->load->library(‘session’);
    $this->current_language = $CI->session->userdata(‘language’);
    $CI->lang->load(‘common/validation’, $this->current_language);
    }

    // ——————————————————————–

    /**
    * Unique
    *
    * @access public
    * @param string
    * @param field
    * @return bool
    */
    function unique($str, $field)
    {
    list($table, $column) = explode(‘.’, $field, 2);

    $this->CI->form_validation->set_message(‘unique’, ‘%s is unavailable’);

    $query = $this->CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str'”);
    $row = $query->row();
    return ($row->dupe > 0) ? FALSE : TRUE;
    }
    }

  11. Hey Scott, one more change here = ) I added support to check for the current value. Note that the third parameter is optional. Here is how it happens:

    Set your validation rules in controller and add another . and the field you want to check:

    $this->form_validation->set_rules(‘coupon_code’, $this->lang->line(‘coupons_form-add-coupon-label-code’), ‘trim|required|min_length[5]|max_length[45]|xss_clean|unique[subscription_coupons.coupon_code.’.$this->data['coupon_info']->coupon_code.’]’);

    If you DON’T want to check a unique field, just leave it empty (but keep the dot there so the explode function doesn’t break):

    $this->form_validation->set_rules(‘coupon_code’, $this->lang->line(‘coupons_form-add-coupon-label-code’), ‘trim|required|min_length[5]|max_length[45]|xss_clean|unique[subscription_coupons.coupon_code.]‘);

    Here are the modifications to the validation unique function:

    Change

    list($table, $column) = explode(‘.’, $field, 2);

    to

    list($table, $column, $current) = explode(‘.’, $field, 3);

    AND

    $query = $this->CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str’”);

    TO

    ($current == ”) ? $query = $this->CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str'”) : $query = $this->CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str’ AND $column != ‘$current'”);

    Now you can check for unique values = )

  12. One more bit…you need to make sure this is in the constructor so validation rules set in a config file will work:

    function __construct($rules = array())

    // Validation rules can be stored in a config file.
    $this->_config_rules = $rules;

    This one had me tearing my hair out

  13. Scott –

    Thanks for the great library extension – this is exactly what I was looking for!

    Calvin –

    Thanks for the addition to allow current values! I’ve made a slight change so you don’t have to have the trailing dot:

    $parts = explode(‘.’, $field);

    $table = $parts[0];
    $column = $parts[1];
    $current = (isset($parts[2])) ? $parts[2] : ” ;

    … and updated the function to use Active Record methods:

    if($current == ”)
    {
    $count = $CI->db->from($table)->where($column, $str)->count_all_results();
    }
    else
    {
    $count = $CI->db->from($table)->where($column, $str)->where($column.’ !=’, $current)->count_all_results();
    }

    return ($count > 0) ? false : true ;

    You guys rock!!

  14. And I also realized my change has a major flaw – the explode function fails if you pass in an email address…

    : /

    You guys still rock though

  15. i use num_rows() function
    http://pastebin.com/uVzSfdKt

    load->database();
    list($table, $column) = split(“.”, $field, 2);

    $CI->form_validation->set_message(‘unique’, ‘The %s that you requested is unavailable.’);

    $query = $CI->db->query(“SELECT $column FROM $table WHERE $column = ‘$str’ limit 1″);
    return ($query->num_rows() > 0) ? FALSE : TRUE;

    /**
    * $query = $CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str'”);
    * $row = $query->row();
    * return ($row->dupe > 0) ? FALSE : TRUE;
    */
    }
    }
    ?>

  16. Awesome! Thanks very much – I had been trying to figure out how to do this for hours when I came across this article. It cleared things up for me in under a minute!

  17. Excellent extension.

    A few of my additions:

    1) Using the db->dbprefix() function for the table name in your query.

    2) Splitting the second parameter into 4 variables:
    $table, $column, $primarykey, $primarykeyvalue

    It is then possible to check for duplicates excluding the record you are trying to update:
    WHERE $column = ‘$str’ and $primarykey (int)$primarykeyvalue

    Your form_validation rule will then look something like:
    unique[users.userEmail.userID.4]

  18. There’s an issue with compatibility using form_validation.php external rules – I had to make the following change (http://stikked.com/view/7fc106ca).

    //OLD
    class MY_Form_validation extends CI_Form_validation {

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

    //NEW
    class MY_Form_validation extends CI_Form_validation {

    function __construct($rules = array())
    {
    parent::__construct();
    $this->_config_rules = $rules;
    }
    }

  19. Thanks for this librairy.
    Make sure to use the right Class Extension Prefix
    (set in your config file).
    Kind Regards
    Koudjo

  20. I also noticed the problem when updating a record. Anyone has a solution for that case?

    By the way well done, I hope anyone can guide me to solve it.

  21. Hi all im running the latest version of CI

    i added the My_Form_validation to libraries that looks like this http://pastebin.com/YWnMu6fe

    my form validation looks like this:
    $this->form_validation->set_rules(‘username’, ‘Username’, ‘required|min_length[4]|unique[members.username]‘);

    but the username still gets inserted with a dupe. Can someone help?

  22. nevermind he states to place it in:
    “system/application/libraries/”

    works after its placed in:
    “applicationlibraries”

  23. one observation : you can also have protection for duplicates right from the database by setting the field unique, even in the case of a pair (firstname,lastname) setting the index on multiple columns like this :

    ALTER TABLE users ADD UNIQUE INDEX(firstname, lastname);

  24. Hi everyone,

    Script is way better that I used before, but now I faced a problem, that my register form does not show a message which I set. I t works, but message does not appear. My script:

    <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
    /**
    * MY_Form_validation Class
    *
    * Extends Form_Validation library
    *
    * Adds one validation rule, "email_check" and accepts a
    * parameter, the name of the table and column that
    * you are checking, specified in the forum table.column
    *
    * Note that this update should be used with the
    * form_validation library introduced in CI 1.7.0
    */
    class MY_Form_validation extends CI_Form_validation {

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

    // ——————————————————————–

    /**
    * Email_check
    *
    * @access public
    * @param string
    * @param field
    * @return bool
    */

    function email_check($str, $field)
    {
    $CI =& get_instance();
    list($table, $column) = explode(‘.’, $field, 2);

    $CI->form_validation->set_message(‘email_check’, ‘The %s that you requested is already in use.’);

    $query = $CI->db->query(“SELECT COUNT(*) AS dupe FROM $table WHERE $column = ‘$str'”);
    $row = $query->row();
    return ($row->dupe > 0) ? FALSE : TRUE;
    }
    }
    ?>

    And I used:
    $this->my_form_validation->set_rules(‘email_address’, ‘Email Address’, ‘email_check[reg_users.email]‘);

    I did it this way, because your exact code did not worked. I use 2.x version

  25. Hi,

    Very useful thanks,

    However you stated that including the validation callbacks in your controllers/models looked messy. I agree with this but I don’t extend the form_validation library, I just create a form_validation helper that contains all the additional callback functions.

    I know both ways are valid, but its always nice to have more than one way to achieve a task :D

Comments are closed.