How to Write a Better Model in CodeIgniter

Posted on 28. Aug, 2009 by Shawn McCool in PHP, Web Development

Developing using an MVC framework can go a long way towards increasing the reusability and robustness of your code. In this article I focus on writing model methods for the Code Igniter PHP MVC framework. The concepts themselves are not specific to Code Igniter, but some of the code will be.

CRUD methods

CRUD is an acronym that stands for Create, Retrieve, Update, and Delete. These are the most basic types of interactions that your code will have with a data source.

A few examples:
Create – adding a user account to your website
Retrieve – getting a list of available products
Update – changing a user’s password or the name of a product
Delete – removing a user account or product

When writing models you’ll generally start by creating CRUD methods for each of your logical types of data. If you’re writing user authentication (login, logout, forgotten password, account activation) you’ll want a method to add, update, and delete users along with the ability to pull back the user account information for a single user or for a group of users.

For example:

function AddUser()
function UpdateUser()
function DeleteUser()
function GetUsers()

Most of the interactions that we have with user accounts can be handled by these four methods. Instead of creating a model method “GetActiveUsers” we can just create a parameter for GetUsers that determines the user status of the returned set, more on this below.

The Options Array

Typically parameters for methods are sent in the following manner:

function AddUser($insertData)
function UpdateUser($userId, $updateData)
function DeleteUser($userId)
function GetUsers($limit, $offset)

Naturally, (especially for GetUsers) we’ll find more and more parameters to add to our methods. This not only gets very difficult to read, but can create issues where we have to go back and alter the code that we’ve already written if we have to tack a new parameter onto the method.

For Example:

function GetUsers($limit, $offset, $status)

We would also need a singular GetUser($userId) method in addition to the GetUsers method. This is a problem because we’re creating multiple versions of the same method. If we change the way we want information to be returned then we’d need to change code in two places, the GetUser AND GetUsers methods.

To overcome these issues I propose the usage of an options array, as such:

function AddUser($options = array())
function UpdateUser($options = array())
function DeleteUser($options = array())
function GetUsers($options = array())

If, in the past, we’d use GetUsers(5, 5, ‘active’) to return a list of users, we would now use GetUsers(array(‘limit’ => 5, ‘offset’ => 5, ’status’ => ‘active’);

Parameters passed using the options array can be sent in any order and can even be completely omitted. As you need to add functional parameters you simply change the model method and will not have to update code anywhere else.

Utility Methods

In order to create properly robust methods we’re going to need to implement a few common bits of functionality. Namely, the ability to assign required fields and field defaults. For example, a required field when adding a user might be userEmail. For this we create the ‘required’ method.

/**
* _required method returns false if the $data array does not contain all of the keys assigned by the $required array.
*
* @param array $required
* @param array $data
* @return bool
*/

function _required($required, $data)
{
    foreach($required as $field) if(!isset($data[$field])) return false;
    return true;
}

In the ‘AddUser’ example our code might look like this:

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/

function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;
   
    // At this point we know that the key 'userEmail' exists in the $options array.
}

Now our AddUser method will bail out and return false if the ‘userEmail’ key was not sent in the options array. Robust!

Notice that in the PHPdoc block we mention that the default status for newly created users is ‘active’. Naturally, if we wanted to create an inactive user we would be able to pass the ‘userStatus’ parameter with the value inactive. However, we are lazy and prefer to not have to explicitly declare ‘active’.

Introducing the ‘default’ method:

/**
* _default method combines the options array with a set of defaults giving the values in the options array priority.
*
* @param array $defaults
* @param array $options
* @return array
*/

function _default($defaults, $options)
{
    return array_merge($defaults, $options);
}

That’s it, this method consists of a single command. I decided to create a method out of this because I may want to add extra functionality to the defaults method, and don’t want to have to change every single one of my model methods.

Let’s go ahead and implement the ‘default’ method:

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/

function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;
   
    // default values
    $options = $this->_default(array('userStatus' => 'active'), $options);
}

// At this point we know that the ‘userEmail’ key exists in the $options array and if no ‘userStatus’ key existed before, it does now and with the value ‘active’.

These methods go a long way towards making your code more robust while only adding a few lines to each method.

Active Record

Many database constructs (MySQL, Oracle, Microsoft SQL, PostgreSQL, etc) use variations of SQL syntax, but they all work a little bit differently. An active record class allows us to create a database query using abstraction. In other words we are able to create queries that will work on any database construct that our class supports. They also give the added benefit of allowing us to send bits of the query to the class one by one before executing the query.

A Code Igniter active record query might look something like this:

$this->db->where('userStatus', 'active');
$this->db->get('users');

Those two commands will create and execute the query, “select * from users where userStatus = ‘active’”

Putting It All Together

Using the concepts that we’ve explored let’s look at a simple example set CRUD methods.

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/

function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;
   
    // default values
    $options = $this->_default(array('userStatus' => 'active'), $options);
   
    // qualification (make sure that we're not allowing the site to insert data that it shouldn't)
    $qualificationArray = array('userEmail', 'userName', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->set($qualifier, $options[$qualifier]);
    }
   
    // MD5 the password if it is set
    if(isset($options['userPassword'])) $this->db->set('userPassword', md5($options['userPassword']));
   
    // Execute the query
    $this->db->insert('users');
   
    // Return the ID of the inserted row, or false if the row could not be inserted
    return $this->db->insert_id();
}

/**
* UpdateUser method alters a record in the users table.
*
* Option: Values
* --------------
* userId            the ID of the user record that will be updated
* userEmail
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
* @return int affected_rows()
*/

function UpdateUser($options = array())
{
    // required values
    if(!$this->_required(array('userId'), $options)) return false;
   
    // qualification (make sure that we're not allowing the site to update data that it shouldn't)
    $qualificationArray = array('userEmail', 'userName', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->set($qualifier, $options[$qualifier]);
    }
   
    $this->db->where('userId', $options['userId']);
   
    // MD5 the password if it is set
    if(isset($options['userPassword'])) $this->db->set('userPassword', md5($options['userPassword']));
   
    // Execute the query
    $this->db->update('users');
   
    // Return the number of rows updated, or false if the row could not be inserted
    return $this->db->affected_rows();
}

/**
* GetUsers method returns an array of qualified user record objects
*
* Option: Values
* --------------
* userId
* userEmail
* userStatus
* limit             limits the number of returned records
* offset                how many records to bypass before returning a record (limit required)
* sortBy                determines which column the sort takes place
* sortDirection     (asc, desc) sort ascending or descending (sortBy required)
*
* Returns (array of objects)
* --------------------------
* userId
* userEmail
* userName
* userStatus
*
* @param array $options
* @return array result()
*/

function GetUsers($options = array())
{
    // default values
    $options = $this->_default(array('sortDirection' => 'asc'), $options);
   
    // Add where clauses to query
    $qualificationArray = array('userId', 'userEmail', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->where($qualifier, $options[$qualifier]);
    }
   
    // If limit / offset are declared (usually for pagination) then we need to take them into account
    if(isset($options['limit']) && isset($options['offset'])) $this->db->limit($options['limit'], $options['offset']);
    else if(isset($options['limit'])) $this->db->limit($options['limit']);
   
    // sort
    if(isset($options['sortBy'])) $this->db->order_by($options['sortBy'], $options['sortDirection']);
   
    $query = $this->db->get('users');
    if($query->num_rows() == 0) return false;
   
    if(isset($options['userId']) && isset($options['userEmail']))
    {
        // If we know that we're returning a singular record, then let's just return the object
        return $query->row(0);
    }
    else
    {
        // If we could be returning any number of records then we'll need to do so as an array of objects
        return $query->result();
    }
}

/**
* DeleteUser method removes a record from the users table
*
* @param array $options
*/

function DeleteUser($options = array())
{
    // required values
    if(!$this->_required(array('userId'), $options)) return false;
   
    $this->db->where('userId', $options['userId']);
    $this->db->delete('users');
}

Here are some examples of how we can use these methods to interact with your database.

Adding a User

$userId = $this->user_model->AddUser($_POST);

if($userId)
    echo "The user you have created has been added successfully with ID #" . $userId;
else
    echo "There was an error adding your user.";

Updating a User

if($this->user_model->UpdateUser(array('userId' => 3, 'userName' => 'Shawn', 'userEmail' => 'not telling')))
    // The user has been successfully updated
else
    // The user was not updated

User Authentication (Retrieving a Single User)

$user = $this->user_model->GetUsers(array('userEmail' => $userEmail, 'userPassword' => md5($userPassword), 'userStatus' => 'active'));
if($user)
    // Log the user in
else
    // Sorry, your user / password combination isn't correct.

Retrieving a Set of Users

$users = $this->user_model->GetUsers(array('userStatus' => 'active'));

if($users)
{
    echo "Active Users<br />";
    foreach($users as $user)
    {
        echo $user->userName . "<br />";
    }
}
else
{
    echo "There are no active users.";
}

Deleting a User

$this->user_model->DeleteUser(array('userId' => $userId));

I hope that this will continue a dialog on how to better write such commonly reused bits of functionality. If you have any suggestions or comments please let me know. I’d love to hear about the algorithms that you’ve been designing to handle similar problems.

Related Posts

  1. Developing a Website with CodeIgniter Part 2: Users Database Table and the User Model
  2. Using CodeIgniter’s Active Record Class to Create Subqueries
  3. Managing Assets with CodeIgniter
  4. Understand the Structure of CodeIgniter
  5. CodeIgniter at the Nashville PHP meetup March 20th.

Share this Post

Twitter Facebook MySpace Delicious Digg


Show Your Appreciation

If you'd like to express your appreciation for the work I've done why not buy me a coffee?
Click here to donate $3 to my caffeine fund.



Tags: , , , , , ,

9 Responses to “How to Write a Better Model in CodeIgniter”

  1. Amit says:

    Hi Shawn,
    Just saw your tutorials on youtube. Just getting started with CI myself – so they really help. Your code is actually pretty complex for beginners like me. Could you explain how the defaults method is working to set status? Also why do we need the $qualificationArray? What does that actually do or prevent from happening? It says in your comments “make sure that we’re not allowing the site to insert data that it shouldn’t” – why would it do that? Is that a security thing?

    Hope you finish the series btw – looking forward to see how jquery is integrated into CI applications.

  2. Shawn McCool says:

    Thank you for the reply! I appreciate the points that you’re making and am very eager to update the article to address your concerns and questions. I will do so as quickly as I can.

    For now I’ll say that the array_merge method takes any number of parameters, each being an array. It combines the arrays and returns the combination as an array that contains the keys and values of both arrays. If an array key is not set in your options array yet is set in your defaults array then the returned combined array will contain that key and the value from the defaults array. If the key is set in both arrays the options array takes precedence and the value from the defaults array is completely ignored.

    I wish that I could write up more, but I have to be going. I’ll update the article soon.

  3. Jon Z says:

    Shawn just wanted to let you know that when I use this method to write models I like to include any alternate returns after these lines:

    $query = $this->db->get(‘users’);
    if($query->num_rows() == 0) return false;

    and create come code like this:

    // alternate returns
    if(isset($options['count'])) return $query->num_rows;
    if(isset($options['dropDown']))
    {
    $dropdowns = $query->results();
    foreach($dropdowns as $dropdown)
    {
    $dropDownList[$dropDown->userId] = $dropDown->userName;
    }
    $dropDownOptions = array(” => ‘SELECT’, ‘0′ => ‘None’);
    $finalDropDown = $dropDownOptions + $dropDownList;
    return $finalDropDown;
    }

    Now when GetUsers(array(‘dropDown’ => true)); OR
    GetUsers(array(‘count’ => true)); is called

    the function calls will return either their respected record
    count number or drop down list.
    The count option could be used for pagination and the dropDown
    list could now be used for the Codigniter form helper:

    populating the drop down with the user name and userId in the
    option value.

    This way I dont have to write a completely separate function in my module to return some slightly different.

  4. Shawn McCool says:

    Jon, that makes sense. Especially for the count parameter. You might create a utility method that would allow you to return an array structured for a form_dropdown() method that could be used throughout your method without duplicating the code.

    I would go so far as to suggest that this went into a helper.

  5. Steve says:

    Very professional code. I do like this style.

    I’d like to hear your opinion, in MVC style do functions like “ValidateUser” belong in the model or the controller?

    I can see benefits to both, but it should never have to talk to the database directly. The functions in the user model provide all the abstraction they should ever care about. However, the higher level ideas of “valid” and “logged in” are likely to be needed across an entire application.

    Right now I’m leaning towards a User Model just like you have here, and a separate Membership Model to handle the ideas of valid and logged in.

  6. Shawn McCool says:

    Steve, recently I created a library called “passport” that handles user accounts, profile information, and authentication. It uses 2 database tables “passport_users” and “passport_user_profiles.”

    passport_users contains only the authentication information that will be the same with every website (email, password, add datetime, last login datetime, active/inactive status, usertype, etc). The user_profiles table contains everything else. userProfileRealName, userProfileDisplayName, userProfileSocialTwitter, userProfileSocialFacebook, etc. The user_profiles table is what you’d change from project to project.

    My goal is rapid development. So, I try to write very modularly. Passport library for users, user profiles, authentication. Dropbox library to create an upload repository. Epoch library for managing events. Memento library for creating lightweight blogging functionality (so as not to have to style up WordPress). Crossover library for directly interacting with WordPress data. Boutique library for creating flexible eCommerce solutions.

    I simply copy the libraries and models to a new site, then duplicate the database structure into the new site’s database.

    These libraries and models are all part of a toolkit that I’ve been developing as part of a design firm over the last half year.

    In reference to your question over preference regarding the ‘validateuser’ method. I would generally place the callback method in the controller and have it tunnel data to the model or library as you mentioned. In this way the callback method can be customized on a per controller basis (which is something that frequently comes up, for example with callback__do_upload and the parameter for image scaling and into which path the file should me stored).

    In the past I have created a separate auth model from the user model. I don’t see any reason NOT to do this. When I decided to create a library to handle user management and authentication it just seemed appropriate to bundle the functionality together.

  7. Richard says:

    using your method for update how do you handle populating the update form? I’m new at codeigniter and trying to figure out the update form stuff. I get how the data to update but not sure how to get it in the view. Can you cover that in a post for forms?

  8. Shawn McCool says:

    I was hoping to complete a new screencast today, but alas a client needed some attention and this site comes second after paying work, naturally.

    The short answer is:

    $data['user'] = $this->user_model->GetUsers(array("userId" => $userId));

    then pass the data array to the view. In the view..

    <?=form_open(blah blah)?>
    <fieldset>
    <legend><span>*</span>Required fields</legend>
    <ul>
    <li>
    <label>Email<span>*</span></label>
    <?=form_input('userEmail', set_value('userEmail', $user->userEmail))?>
    </li>
    </ul>
    </fieldset>
    <?=form_close()?>
  9. Mahbub says:

    Nice. This is how i write models in CI in fact. Most of the vars are prepared from the controller and model does a generalized thing just taking an array. Although in update and delete, i do pass the id :)

Leave a Reply